From b5128c85c6dbb823d8fc1ff14267d5722277a0d6 Mon Sep 17 00:00:00 2001 From: wangdicoder Date: Sun, 29 Mar 2026 10:48:40 +1100 Subject: [PATCH 1/5] fix(table): use Checkbox/Radio components, fix selection bugs, improve types - Replace native with Checkbox and Radio components for consistent styling - Fix pagination index bug where page-relative indices caused key collisions - Fix handleSelectRow filtering against unsorted dataSource - Add columns to defaultSortOrder effect dependency array - Use paginationConfig for proper type narrowing in handlePageChange - Constrain ColumnType.dataIndex to keyof T & string - Change RowSelection from type alias to interface - Remove unused fixed prop from ColumnType - Document missing props (rowClassName, onRow, defaultSortOrder, className) --- packages/react/src/table/index.md | 4 +++ packages/react/src/table/index.zh_CN.md | 4 +++ packages/react/src/table/table.tsx | 40 +++++++++++++++---------- packages/react/src/table/types.ts | 7 ++--- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/packages/react/src/table/index.md b/packages/react/src/table/index.md index 1c7aef5d..9a11bf63 100644 --- a/packages/react/src/table/index.md +++ b/packages/react/src/table/index.md @@ -120,6 +120,8 @@ Render large tables efficiently with virtual scrolling. The header stays fixed w | onChange | sort/pagination change callback | (pagination, sorter) => void | | | emptyText | empty state text | ReactNode | No Data | | showHeader | show table header | boolean | true | +| rowClassName | custom row class name | string | (record, index) => string | | +| onRow | row event handler | (record, index) => HTMLAttributes | | ### ColumnType @@ -132,7 +134,9 @@ Render large tables efficiently with virtual scrolling. The header stays fixed w | align | text alignment | `left` | `center` | `right` | left | | sorter | enable sorting | boolean | (a, b) => number | | | render | custom cell renderer | (value, record, index) => ReactNode | | +| defaultSortOrder | default sort order | `ascend` | `descend` | | | ellipsis | truncate long content | boolean | false | +| className | custom column class name | string | | ### RowSelection diff --git a/packages/react/src/table/index.zh_CN.md b/packages/react/src/table/index.zh_CN.md index cf1454af..b8869859 100644 --- a/packages/react/src/table/index.zh_CN.md +++ b/packages/react/src/table/index.zh_CN.md @@ -120,6 +120,8 @@ import { Table } from 'tiny-design'; | onChange | 排序/分页变化回调 | (pagination, sorter) => void | | | emptyText | 空状态文本 | ReactNode | 暂无数据 | | showHeader | 显示表头 | boolean | true | +| rowClassName | 自定义行类名 | string | (record, index) => string | | +| onRow | 行事件处理 | (record, index) => HTMLAttributes | | ### ColumnType @@ -132,7 +134,9 @@ import { Table } from 'tiny-design'; | align | 对齐方式 | `left` | `center` | `right` | left | | sorter | 排序功能 | boolean | (a, b) => number | | | render | 自定义渲染 | (value, record, index) => ReactNode | | +| defaultSortOrder | 默认排序方向 | `ascend` | `descend` | | | ellipsis | 文本溢出省略 | boolean | false | +| className | 自定义列类名 | string | | ### RowSelection diff --git a/packages/react/src/table/table.tsx b/packages/react/src/table/table.tsx index 299173e4..0375ba62 100644 --- a/packages/react/src/table/table.tsx +++ b/packages/react/src/table/table.tsx @@ -4,6 +4,8 @@ import { ConfigContext } from '../config-provider/config-context'; import { getPrefixCls } from '../_utils/general'; import { useVirtualScroll } from '../_utils/use-virtual-scroll'; import Pagination from '../pagination'; +import Checkbox from '../checkbox'; +import Radio from '../radio'; import { TableProps, ColumnType, SortOrder } from './types'; const ROW_HEIGHT_MAP = { sm: 40, md: 48, lg: 56 } as const; @@ -81,7 +83,7 @@ const Table = React.forwardRef((props, ref) => { break; } } - }, []); + }, [columns]); const sortedData = useMemo(() => { if (!sortField || !sortOrder) return [...dataSource]; @@ -143,13 +145,14 @@ const Table = React.forwardRef((props, ref) => { const handlePageChange = (page: number) => { setCurrentPage(page); - pagination && pagination.onChange?.(page, pageSize); + paginationConfig?.onChange?.(page, pageSize); onChange?.({ current: page, pageSize }, { field: sortField, order: sortOrder }); }; const handleSelectAll = () => { if (!rowSelection) return; - const allKeys = paginatedData.map((record, i) => getRowKey(record, rowKey, i)); + const offset = (!isVirtual && pagination !== false) ? (activePage - 1) * pageSize : 0; + const allKeys = paginatedData.map((record, i) => getRowKey(record, rowKey, offset + i)); const allSelected = allKeys.every((k) => selectedKeys.includes(k)); const newKeys = allSelected ? [] : allKeys; if (!rowSelection.selectedRowKeys) { @@ -173,7 +176,7 @@ const Table = React.forwardRef((props, ref) => { if (!rowSelection.selectedRowKeys) { setSelectedKeys(newKeys); } - const newRows = dataSource.filter((r, i) => newKeys.includes(getRowKey(r, rowKey, i))); + const newRows = sortedData.filter((r, i) => newKeys.includes(getRowKey(r, rowKey, i))); rowSelection.onChange?.(newKeys, newRows); }; @@ -195,7 +198,8 @@ const Table = React.forwardRef((props, ref) => { wrapperStyle.overflowY = 'auto'; } - const allPageKeys = paginatedData.map((r, i) => getRowKey(r, rowKey, i)); + const pageOffset = (!isVirtual && pagination !== false) ? (activePage - 1) * pageSize : 0; + const allPageKeys = paginatedData.map((r, i) => getRowKey(r, rowKey, pageOffset + i)); const allSelected = allPageKeys.length > 0 && allPageKeys.every((k) => selectedKeys.includes(k)); const someSelected = allPageKeys.some((k) => selectedKeys.includes(k)); @@ -212,12 +216,19 @@ const Table = React.forwardRef((props, ref) => { {rowSelection && ( - handleSelectRow(record, key)} - aria-label={`Select row ${rowIndex + 1}`} - /> + {rowSelection.type === 'radio' ? ( + handleSelectRow(record, key)} + aria-label={`Select row ${rowIndex + 1}`} + /> + ) : ( + handleSelectRow(record, key)} + aria-label={`Select row ${rowIndex + 1}`} + /> + )} )} {columns.map((col, colIndex) => { @@ -294,7 +305,7 @@ const Table = React.forwardRef((props, ref) => { ); } - return paginatedData.map((record, rowIndex) => renderRow(record, rowIndex)); + return paginatedData.map((record, i) => renderRow(record, pageOffset + i)); }; const theadCls = classNames(`${prefixCls}__thead`, { @@ -317,10 +328,9 @@ const Table = React.forwardRef((props, ref) => { {rowSelection && ( {rowSelection.type !== 'radio' && ( - { if (el) el.indeterminate = someSelected && !allSelected; }} + indeterminate={someSelected && !allSelected} onChange={handleSelectAll} aria-label="Select all" /> diff --git a/packages/react/src/table/types.ts b/packages/react/src/table/types.ts index fa28caa2..3ebc209e 100644 --- a/packages/react/src/table/types.ts +++ b/packages/react/src/table/types.ts @@ -7,11 +7,10 @@ export type ColumnAlign = 'left' | 'center' | 'right'; export interface ColumnType { title: React.ReactNode; - dataIndex?: string; + dataIndex?: keyof T & string; key?: string; width?: number | string; align?: ColumnAlign; - fixed?: 'left' | 'right'; sorter?: boolean | ((a: T, b: T) => number); defaultSortOrder?: SortOrder; render?: (value: any, record: T, index: number) => React.ReactNode; @@ -19,11 +18,11 @@ export interface ColumnType { className?: string; } -export type RowSelection = { +export interface RowSelection { selectedRowKeys?: React.Key[]; onChange?: (selectedRowKeys: React.Key[], selectedRows: T[]) => void; type?: 'checkbox' | 'radio'; -}; +} export interface TablePaginationConfig extends Pick { current?: number; From 5a494b7889a0807345e43ef8111fb2572fcdf8e2 Mon Sep 17 00:00:00 2001 From: wangdicoder Date: Sun, 29 Mar 2026 10:48:50 +1100 Subject: [PATCH 2/5] chore: add changeset for table selection and type fixes --- .changeset/fix-table-selection-types.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-table-selection-types.md diff --git a/.changeset/fix-table-selection-types.md b/.changeset/fix-table-selection-types.md new file mode 100644 index 00000000..4d8dcc24 --- /dev/null +++ b/.changeset/fix-table-selection-types.md @@ -0,0 +1,5 @@ +--- +"@tiny-design/react": patch +--- + +Fix Table selection bugs, use Checkbox/Radio components, and improve type definitions From 7ec964c4393fd948b12ca6cf6ae9f92d4b143bd1 Mon Sep 17 00:00:00 2001 From: wangdicoder Date: Sun, 29 Mar 2026 10:49:53 +1100 Subject: [PATCH 3/5] fix(react): improve List semantics, redesign Loader spinner, fix button icon shrink List: - Use semantic
    /
  • elements instead of
    for list items - Add forwardRef to ListItemMeta - Extend ListItemMetaProps with div intrinsic props - Change ListItemProps to extend
  • intrinsic elements - Fix renderItem index to use global index when paginated - Add aria-busy, role="status", and aria-live attributes - Preserve generic type T through List export Loader: - Replace dot-based indicator with CSS-only spinning border animation - Remove 4 dot spans, use single div with ::before/::after pseudo-elements - Use CSS custom property --ty-loader-border-width for size variants - Simplify SCSS by removing size-specific nested selectors Button: - Add flex-shrink: 0 to icon container to prevent shrinking - Shorten loading demo button labels useVirtualScroll: - Add enabled option to skip scroll tracking when not in virtual mode --- .../react/src/_utils/use-virtual-scroll.ts | 9 +- packages/react/src/button/demo/Loading.tsx | 4 +- packages/react/src/button/style/_index.scss | 1 + .../__snapshots__/list.test.tsx.snap | 57 ++++----- packages/react/src/list/index.tsx | 7 +- packages/react/src/list/list-item-meta.tsx | 16 ++- packages/react/src/list/list-item.tsx | 8 +- packages/react/src/list/list.tsx | 37 ++++-- packages/react/src/list/style/_index.scss | 7 ++ packages/react/src/list/types.ts | 6 +- .../__snapshots__/loader.test.tsx.snap | 15 +-- packages/react/src/loader/indicator.tsx | 9 +- packages/react/src/loader/style/_index.scss | 109 +++++------------- 13 files changed, 131 insertions(+), 154 deletions(-) diff --git a/packages/react/src/_utils/use-virtual-scroll.ts b/packages/react/src/_utils/use-virtual-scroll.ts index de381fee..222a6f50 100644 --- a/packages/react/src/_utils/use-virtual-scroll.ts +++ b/packages/react/src/_utils/use-virtual-scroll.ts @@ -5,6 +5,7 @@ export interface UseVirtualScrollOptions { itemHeight: number; containerHeight: number; overscan?: number; + enabled?: boolean; } export interface UseVirtualScrollResult { @@ -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) => { - setScrollTop(e.currentTarget.scrollTop); - }, []); + if (enabled) { + setScrollTop(e.currentTarget.scrollTop); + } + }, [enabled]); const totalHeight = itemCount * itemHeight; diff --git a/packages/react/src/button/demo/Loading.tsx b/packages/react/src/button/demo/Loading.tsx index d00c5c06..de9addfc 100644 --- a/packages/react/src/button/demo/Loading.tsx +++ b/packages/react/src/button/demo/Loading.tsx @@ -4,8 +4,8 @@ import { Button, Flex } from '@tiny-design/react'; export default function LoadingDemo() { return ( - - + + ); diff --git a/packages/react/src/button/style/_index.scss b/packages/react/src/button/style/_index.scss index 31563757..d0f33273 100755 --- a/packages/react/src/button/style/_index.scss +++ b/packages/react/src/button/style/_index.scss @@ -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; diff --git a/packages/react/src/list/__tests__/__snapshots__/list.test.tsx.snap b/packages/react/src/list/__tests__/__snapshots__/list.test.tsx.snap index affa4673..3f7de8af 100644 --- a/packages/react/src/list/__tests__/__snapshots__/list.test.tsx.snap +++ b/packages/react/src/list/__tests__/__snapshots__/list.test.tsx.snap @@ -3,50 +3,55 @@ exports[` should match the snapshot 1`] = `
    -
    -
    - Item 1 +
    + Item 1 +
    -
    -
    -
    -
    +
  • - Item 2 +
    + Item 2 +
    - - -
    -
    +
  • - Item 3 +
    + Item 3 +
    - - +
  • +
diff --git a/packages/react/src/list/index.tsx b/packages/react/src/list/index.tsx index 40655b0b..43503268 100644 --- a/packages/react/src/list/index.tsx +++ b/packages/react/src/list/index.tsx @@ -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 = (( + props: ListProps & React.RefAttributes +) => React.ReactElement | null) & { Item: typeof ListItem; ItemMeta: typeof ListItemMeta; + displayName?: string; }; const DefaultList = List as IList; diff --git a/packages/react/src/list/list-item-meta.tsx b/packages/react/src/list/list-item-meta.tsx index b1f86a3a..9c741ffe 100644 --- a/packages/react/src/list/list-item-meta.tsx +++ b/packages/react/src/list/list-item-meta.tsx @@ -4,14 +4,22 @@ 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((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 ( -
+
{avatar &&
{avatar}
}
{title &&
{title}
} @@ -19,7 +27,7 @@ const ListItemMeta = (props: ListItemMetaProps): React.ReactElement => {
); -}; +}); ListItemMeta.displayName = 'ListItemMeta'; export default ListItemMeta; diff --git a/packages/react/src/list/list-item.tsx b/packages/react/src/list/list-item.tsx index a1a6688f..d45f8925 100644 --- a/packages/react/src/list/list-item.tsx +++ b/packages/react/src/list/list-item.tsx @@ -4,7 +4,7 @@ import { ConfigContext } from '../config-provider/config-context'; import { getPrefixCls } from '../_utils/general'; import { ListItemProps } from './types'; -const ListItem = React.forwardRef((props, ref) => { +const ListItem = React.forwardRef((props, ref) => { const { extra, actions, @@ -20,11 +20,11 @@ const ListItem = React.forwardRef((props, ref) => const cls = classNames(prefixCls, className); return ( -
+
  • {children}
    {actions && actions.length > 0 && ( -
      +
        {actions.map((action, i) => (
      • {action} @@ -34,7 +34,7 @@ const ListItem = React.forwardRef((props, ref) => )}
    {extra &&
    {extra}
    } -
  • + ); }); diff --git a/packages/react/src/list/list.tsx b/packages/react/src/list/list.tsx index d16a0a2f..dcab7f63 100644 --- a/packages/react/src/list/list.tsx +++ b/packages/react/src/list/list.tsx @@ -52,6 +52,7 @@ const List = React.forwardRef((props, ref) => { itemCount: dataSource.length, itemHeight, containerHeight: height ?? 0, + enabled: isVirtual, }); const cls = classNames(prefixCls, className, { @@ -78,7 +79,7 @@ const List = React.forwardRef((props, ref) => { if (isVirtual) { if (dataSource.length === 0) { return ( -
    +
    {locale?.emptyText ?? 'No Data'}
    ); @@ -89,10 +90,16 @@ const List = React.forwardRef((props, ref) => { {renderItem(item, start + i)} )); return ( -
    -
    +
    +
      {visibleItems} -
    +
    ); } @@ -102,18 +109,22 @@ const List = React.forwardRef((props, ref) => { const items = paginatedData(); if (items.length === 0 && !children) { return ( -
    +
    {locale?.emptyText ?? 'No Data'}
    ); } if (renderItem) { + const page = pagination ? (pagination.current ?? currentPage) : 1; + const startIndex = pagination ? (page - 1) * pageSize : 0; const rendered = items.map((item, index) => ( - {renderItem(item, index)} + + {renderItem(item, startIndex + index)} + )); if (grid) { return ( -
    ((props, ref) => { }} > {rendered} -
    + ); } - return rendered; + return
      {rendered}
    ; } return children; }; @@ -144,11 +155,11 @@ const List = React.forwardRef((props, ref) => { : undefined; return ( -
    +
    {header &&
    {header}
    }
    {loading ? ( -
    Loading...
    +
    Loading...
    ) : ( renderItems() )} @@ -171,4 +182,6 @@ const List = React.forwardRef((props, ref) => { }); List.displayName = 'List'; -export default List; +export default List as ( + props: ListProps & React.RefAttributes +) => React.ReactElement | null; diff --git a/packages/react/src/list/style/_index.scss b/packages/react/src/list/style/_index.scss index e5f2a990..af0985a1 100644 --- a/packages/react/src/list/style/_index.scss +++ b/packages/react/src/list/style/_index.scss @@ -49,6 +49,13 @@ } } + &__items, + &__grid { + list-style: none; + margin: 0; + padding: 0; + } + &__empty { padding: 24px; text-align: center; diff --git a/packages/react/src/list/types.ts b/packages/react/src/list/types.ts index d76e7ac0..3a8d8b04 100644 --- a/packages/react/src/list/types.ts +++ b/packages/react/src/list/types.ts @@ -36,13 +36,15 @@ export interface ListPaginationProps extends Pick { + React.PropsWithoutRef { extra?: React.ReactNode; actions?: React.ReactNode[]; children?: React.ReactNode; } -export interface ListItemMetaProps extends BaseProps { +export interface ListItemMetaProps + extends BaseProps, + React.PropsWithoutRef { avatar?: React.ReactNode; title?: React.ReactNode; description?: React.ReactNode; diff --git a/packages/react/src/loader/__tests__/__snapshots__/loader.test.tsx.snap b/packages/react/src/loader/__tests__/__snapshots__/loader.test.tsx.snap index 685f4382..87ab9793 100644 --- a/packages/react/src/loader/__tests__/__snapshots__/loader.test.tsx.snap +++ b/packages/react/src/loader/__tests__/__snapshots__/loader.test.tsx.snap @@ -12,20 +12,7 @@ exports[` should match the snapshot 1`] = ` >
    - - - - -
    + />
    diff --git a/packages/react/src/loader/indicator.tsx b/packages/react/src/loader/indicator.tsx index 6c9b6339..b34eb177 100644 --- a/packages/react/src/loader/indicator.tsx +++ b/packages/react/src/loader/indicator.tsx @@ -8,14 +8,7 @@ type Props = { const Indicator = (props: Props): React.ReactElement => { const { size, className } = props; - return ( -
    - - - - -
    - ); + return
    ; }; export default Indicator; diff --git a/packages/react/src/loader/style/_index.scss b/packages/react/src/loader/style/_index.scss index 87cda31a..b9a6ab96 100755 --- a/packages/react/src/loader/style/_index.scss +++ b/packages/react/src/loader/style/_index.scss @@ -9,44 +9,44 @@ &__indicator { position: relative; display: inline-block; - font-size: 20px; - width: 1em; - height: 1em; + aspect-ratio: 1; + border-radius: 50%; + border: var(--ty-loader-border-width) solid transparent; + border-right-color: currentcolor; margin: 5px; - animation: ty-rotate 1s linear infinite; - } + animation: ty-loader-spin 1s infinite linear; - &__indicator-dot { - position: absolute; - display: block; - transform: scale(0.75); - background-color: currentcolor; - border-radius: 50%; - transform-origin: 50% 50%; - opacity: 0.5; - animation: dot-opacity 2s linear infinite; + &::before, + &::after { + content: ''; + position: absolute; + inset: calc(var(--ty-loader-border-width) * -1); + border-radius: 50%; + border: inherit; + animation: inherit; + animation-duration: 2s; + } - &:nth-child(1) { - top: 0; - left: 0; + &::after { + animation-duration: 4s; } - &:nth-child(2) { - top: 0; - right: 0; - animation-delay: .4s; + &_sm { + --ty-loader-border-width: 2px; + + width: 16px; } - &:nth-child(3) { - right: 0; - bottom: 0; - animation-delay: .8s; + &_md { + --ty-loader-border-width: 3px; + + width: 24px; } - &:nth-child(4) { - bottom: 0; - left: 0; - animation-delay: 1.2s; + &_lg { + --ty-loader-border-width: 4px; + + width: 36px; } } @@ -57,45 +57,6 @@ font-size: 16px; } - &_md { - .#{$prefix}-loader{ - &__indicator{ - font-size: 20px; - } - - &__indicator-dot { - width: 9px; - height: 9px; - } - } - } - - &_lg { - .#{$prefix}-loader{ - &__indicator{ - font-size: 32px; - } - - &__indicator-dot { - width: 14px; - height: 14px; - } - } - } - - &_sm { - .#{$prefix}-loader{ - &__indicator{ - font-size: 14px; - } - - &__indicator-dot { - width: 6px; - height: 6px; - } - } - } - &__loader-container { width: 100%; height: 100%; @@ -127,16 +88,8 @@ } } -@keyframes dot-opacity { - 0% { - opacity: 0.2; - } - - 50% { - opacity: 0.9; - } - +@keyframes ty-loader-spin { 100% { - opacity: 0.2; + transform: rotate(1turn); } } From cd6e7b37703fbd992e91e07c49c9976b4b0f388b Mon Sep 17 00:00:00 2001 From: wangdicoder Date: Sun, 29 Mar 2026 10:50:08 +1100 Subject: [PATCH 4/5] chore: update changeset to include all component changes --- .changeset/fix-table-selection-types.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.changeset/fix-table-selection-types.md b/.changeset/fix-table-selection-types.md index 4d8dcc24..fba7f0e4 100644 --- a/.changeset/fix-table-selection-types.md +++ b/.changeset/fix-table-selection-types.md @@ -1,5 +1,9 @@ --- -"@tiny-design/react": patch +"@tiny-design/react": minor --- -Fix Table selection bugs, use Checkbox/Radio components, and improve type definitions +- Table: use Checkbox/Radio components, fix selection bugs with pagination and sorting, improve type definitions +- List: use semantic `
      `/`
    • ` 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 From 756f42ea63479463bbfb6d3c8707e3d901b3b12d Mon Sep 17 00:00:00 2001 From: wangdicoder Date: Sun, 29 Mar 2026 10:55:11 +1100 Subject: [PATCH 5/5] fix(list): omit conflicting title from ListItemMetaProps div extension --- packages/react/src/list/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react/src/list/types.ts b/packages/react/src/list/types.ts index 3a8d8b04..66e9a373 100644 --- a/packages/react/src/list/types.ts +++ b/packages/react/src/list/types.ts @@ -44,7 +44,7 @@ export interface ListItemProps export interface ListItemMetaProps extends BaseProps, - React.PropsWithoutRef { + Omit, 'title'> { avatar?: React.ReactNode; title?: React.ReactNode; description?: React.ReactNode;