feat(table): sortable and load-more examples added

This commit is contained in:
Junior Garcia 2023-04-23 01:28:07 -03:00
parent 8500329f9c
commit 316acb48c5
34 changed files with 402 additions and 107 deletions

View File

@ -95,7 +95,7 @@ export function useAccordionItem<T extends object = {}>(props: UseAccordionItemP
],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getBaseProps = useCallback<PropGetter>(
(props = {}) => {

View File

@ -150,7 +150,7 @@ export function useAvatar(props: UseAvatarProps) {
[color, radius, size, isBordered, isDisabled, isInGroup, groupContext?.isGrid],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const canBeFocused = useMemo(() => {
return isFocusable || as === "button";

View File

@ -77,7 +77,7 @@ export function useCard(originalProps: UseCardProps) {
const domRef = useDOMRef<HTMLDivElement>(ref);
const Component = as || (originalProps.isPressable ? "button" : "div");
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const {onClick: onDripClickHandler, drips} = useDrip();

View File

@ -105,7 +105,7 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps) {
const slots = useMemo(() => checkboxGroup(), []);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getGroupProps: PropGetter = () => {
return {

View File

@ -219,7 +219,7 @@ export function useCheckbox(props: UseCheckboxProps) {
[color, size, radius, lineThrough, isDisabled, disableAnimation],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getBaseProps: PropGetter = () => {
return {

View File

@ -76,7 +76,7 @@ export function useChip(originalProps: UseChipProps) {
const domRef = useDOMRef(ref);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const isCloseable = !!onClose;
const isDotVariant = originalProps.variant === "dot";

View File

@ -113,7 +113,7 @@ export function useDropdownItem<T extends object>(originalProps: UseDropdownItem
[...Object.values(variantProps), isDisabled, disableAnimation],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getItemProps: PropGetter = (props = {}) => ({
ref: domRef,

View File

@ -142,7 +142,7 @@ export function useModal(originalProps: UseModalProps) {
focusProps: closeButtonFocusProps,
} = useFocusRing();
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const slots = useMemo(
() =>

View File

@ -155,7 +155,7 @@ export function useNavbar(originalProps: UseNavbarProps) {
[...Object.values(variantProps), shouldHideOnScroll],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
useScrollPosition({
elementRef: parentRef,

View File

@ -185,7 +185,7 @@ export function usePagination(originalProps: UsePaginationProps) {
[...Object.values(variantProps)],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const onNext = () => {
if (loop && activePage === total) {

View File

@ -179,7 +179,7 @@ export function usePopover(originalProps: UsePopoverProps) {
[...Object.values(variantProps)],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getPopoverProps: PropGetter = (props = {}) => ({
ref: popoverRef,

View File

@ -72,7 +72,7 @@ export function useCircularProgress(originalProps: UseCircularProgressProps) {
const domRef = useDOMRef(ref);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const [, isMounted] = useIsMounted({
rerender: true,
delay: 100,

View File

@ -68,7 +68,7 @@ export function useProgress(originalProps: UseProgressProps) {
const domRef = useDOMRef(ref);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const [, isMounted] = useIsMounted({
rerender: true,
delay: 100,

View File

@ -131,7 +131,7 @@ export function useRadioGroup(props: UseRadioGroupProps) {
const slots = useMemo(() => radioGroup(), []);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getGroupProps: PropGetter = () => {
return {

View File

@ -164,7 +164,7 @@ export function useRadio(props: UseRadioProps) {
[color, size, radius, isDisabled, isInvalid, disableAnimation],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getBaseProps: PropGetter = (props = {}) => {
return {

View File

@ -154,7 +154,7 @@ export function useSnippet(originalProps: UseSnippetProps) {
return str ? `${str} ` : "";
}, [symbol]);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getSnippetProps = useCallback<PropGetter>(
() => ({

View File

@ -41,7 +41,7 @@ export function useSpinner(originalProps: UseSpinnerProps) {
const slots = useMemo(() => spinner({...variantProps}), [...Object.values(variantProps)]);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const label = labelProp || children;

View File

@ -180,7 +180,7 @@ export function useSwitch(originalProps: UseSwitchProps) {
[...Object.values(variantProps)],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getBaseProps: PropGetter = (props) => {
return {

View File

@ -38,23 +38,27 @@
},
"dependencies": {
"@nextui-org/checkbox": "workspace:*",
"@nextui-org/spacer": "workspace:*",
"@nextui-org/dom-utils": "workspace:*",
"@nextui-org/shared-utils": "workspace:*",
"@nextui-org/shared-icons": "workspace:*",
"@nextui-org/spacer": "workspace:*",
"@nextui-org/system": "workspace:*",
"@nextui-org/theme": "workspace:*",
"@react-aria/focus": "^3.12.0",
"@react-aria/interactions": "^3.15.0",
"@react-aria/table": "^3.9.0",
"@react-aria/utils": "^3.16.0",
"@react-aria/visually-hidden": "^3.8.0",
"@react-aria/interactions": "^3.15.0",
"@react-stately/table": "^3.9.0"
},
"devDependencies": {
"@nextui-org/user": "workspace:*",
"@nextui-org/tooltip": "workspace:*",
"@nextui-org/chip": "workspace:*",
"@nextui-org/shared-icons": "workspace:*",
"@nextui-org/button": "workspace:*",
"@nextui-org/spinner": "workspace:*",
"@nextui-org/pagination": "workspace:*",
"@nextui-org/tooltip": "workspace:*",
"@nextui-org/user": "workspace:*",
"@react-stately/data": "^3.9.1",
"@react-types/grid": "^3.1.7",
"@react-types/table": "^3.6.0",
"clean-package": "2.2.0",

View File

@ -1,9 +1,14 @@
import {HTMLNextUIProps} from "@nextui-org/system";
import {TableBody as TableBodyBase} from "@react-stately/table";
import {TableBodyProps as TableBodyBaseProps} from "@react-types/table";
import {ReactNode} from "react";
export type TableBodyProps<T> = TableBodyBaseProps<T> &
Omit<HTMLNextUIProps<"tbody">, keyof TableBodyBaseProps<T>>;
export interface TableBodyProps<T>
extends TableBodyBaseProps<T>,
Omit<HTMLNextUIProps<"tbody">, keyof TableBodyBaseProps<T>> {
/** Provides content to display when there are no rows in the table. */
renderEmptyState?: () => ReactNode;
}
const TableBody = TableBodyBase as <T>(props: TableBodyProps<T>) => JSX.Element;

View File

@ -1,23 +1,26 @@
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx} from "@nextui-org/shared-utils";
import {clsx, dataAttr} from "@nextui-org/shared-utils";
import {useTableRowGroup} from "@react-aria/table";
import {useMemo} from "react";
import {filterDOMProps, mergeProps} from "@react-aria/utils";
import TableRow from "./table-row";
import TableCell from "./table-cell";
import TableRowGroup from "./table-row-group";
import TableCheckboxCell from "./table-checkbox-cell";
import {useTableContext} from "./table-context";
const TableBody = forwardRef<HTMLNextUIProps, "tbody">((props, ref) => {
const {as: asProp, className, ...otherProps} = props;
const {as, className, ...otherProps} = props;
const as = asProp || "tbody";
const Component = as || "tbody";
const domRef = useDOMRef(ref);
const {slots, collection, classNames} = useTableContext();
const {rowGroupProps} = useTableRowGroup();
const tbodyStyles = clsx(classNames?.tbody, className);
const bodyProps = collection.body.props;
const renderRows = useMemo(() => {
return [...collection.body.childNodes].map((row) => (
@ -33,15 +36,32 @@ const TableBody = forwardRef<HTMLNextUIProps, "tbody">((props, ref) => {
));
}, [collection.body.childNodes]);
let emptyState;
if (collection.size === 0 && bodyProps.renderEmptyState) {
emptyState = (
<tr role="row">
<td
className={slots?.emptyWrapper({class: classNames?.emptyWrapper})}
colSpan={collection.columnCount}
role="gridcell"
>
{bodyProps.renderEmptyState()}
</td>
</tr>
);
}
return (
<TableRowGroup
<Component
ref={domRef}
as={as}
{...otherProps}
{...mergeProps(rowGroupProps, filterDOMProps(bodyProps, {labelable: true}), otherProps)}
className={slots.tbody?.({class: tbodyStyles})}
data-empty={dataAttr(collection.size === 0)}
>
{renderRows}
</TableRowGroup>
{emptyState}
</Component>
);
});

View File

@ -2,11 +2,13 @@ import type {GridNode} from "@react-types/grid";
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
import {useDOMRef} from "@nextui-org/dom-utils";
import {clsx} from "@nextui-org/shared-utils";
import {clsx, dataAttr} from "@nextui-org/shared-utils";
import {useTableColumnHeader} from "@react-aria/table";
import {filterDOMProps, mergeProps} from "@react-aria/utils";
import {ChevronDownIcon} from "@nextui-org/shared-icons";
import {useFocusRing} from "@react-aria/focus";
import {VisuallyHidden} from "@react-aria/visually-hidden";
import {useHover} from "@react-aria/interactions";
import {useTableContext} from "./table-context";
@ -30,43 +32,36 @@ const TableColumnHeader = forwardRef<TableColumnHeaderProps, "th">((props, ref)
const thStyles = clsx(classNames?.th, className, node.props?.className);
const {isFocusVisible, focusProps} = useFocusRing();
const {isHovered, hoverProps} = useHover({});
const {hideHeader, ...columnProps} = node.props;
let arrowIcon = state.sortDescriptor?.direction === "ascending" ? "▲" : "▼";
const allowsSorting = columnProps.allowsSorting;
return (
<Component
ref={domRef}
colSpan={node.colspan}
data-focus-visible={isFocusVisible}
data-focus-visible={dataAttr(isFocusVisible)}
data-hover={dataAttr(isHovered)}
data-sortable={dataAttr(allowsSorting)}
{...mergeProps(
columnHeaderProps,
focusProps,
filterDOMProps(columnProps, {labelable: true}),
allowsSorting ? hoverProps : {},
otherProps,
)}
className={slots.th?.({class: thStyles})}
>
{hideHeader ? <VisuallyHidden>{node.rendered}</VisuallyHidden> : node.rendered}
{columnProps.allowsSorting && (
<span
{allowsSorting && (
<ChevronDownIcon
aria-hidden="true"
style={{
padding: "0 2px",
visibility: state.sortDescriptor?.column === node.key ? "visible" : "hidden",
}}
>
{arrowIcon}
</span>
// <TableSortIcon
// ascending={state.sortDescriptor?.direction === "ascending"}
// css={{
// position: "absolute",
// m: "0 $2",
// bottom: `calc(50% - ${ICON_SIZE / 2}px)`,
// }}
// visible={state.sortDescriptor?.column === column.key}
// />
className={slots.sortIcon?.({class: classNames?.sortIcon})}
data-direction={state.sortDescriptor?.direction}
data-visible={dataAttr(state.sortDescriptor?.column === node.key)}
strokeWidth={3}
/>
)}
</Component>
);

View File

@ -22,6 +22,7 @@ const TableRow = forwardRef<TableRowProps, "tr">((props, ref) => {
const {as, className, children, node, ...otherProps} = props;
const Component = as || "tr";
const domRef = useDOMRef(ref);
const {slots, state, isSelectable, classNames} = useTableContext();

View File

@ -14,43 +14,57 @@ export interface TableProps
extends Omit<UseTableProps, "ref" | "isSelectable" | "isMultiSelectable"> {}
const Table = forwardRef<TableProps, "table">((props, ref) => {
const {Component, collection, context, removeWrapper, getBaseProps, getTableProps} = useTable({
const {
BaseComponent,
Component,
collection,
context,
topContent,
bottomContent,
removeWrapper,
getBaseProps,
getTableProps,
} = useTable({
ref,
...props,
});
const Wrapper = useCallback(
const BaseWrapper = useCallback(
({children}: {children: JSX.Element}) => {
if (removeWrapper) {
return children;
}
return <div {...getBaseProps()}>{children}</div>;
return <BaseComponent {...getBaseProps()}>{children}</BaseComponent>;
},
[removeWrapper, getBaseProps],
);
return (
<TableProvider value={context}>
<Wrapper>
<Component {...getTableProps()}>
<TableRowGroup>
{collection.headerRows.map((headerRow) => (
<TableHeaderRow key={headerRow?.key} node={headerRow}>
{[...headerRow.childNodes].map((column) =>
column?.props?.isSelectionCell ? (
<TableSelectAllCheckbox key={column?.key} node={column} />
) : (
<TableColumnHeader key={column?.key} node={column} />
),
)}
</TableHeaderRow>
))}
<Spacer as="tr" y={0.4} />
</TableRowGroup>
<TableBody />
</Component>
</Wrapper>
<BaseWrapper>
<>
{topContent}
<Component {...getTableProps()}>
<TableRowGroup>
{collection.headerRows.map((headerRow) => (
<TableHeaderRow key={headerRow?.key} node={headerRow}>
{[...headerRow.childNodes].map((column) =>
column?.props?.isSelectionCell ? (
<TableSelectAllCheckbox key={column?.key} node={column} />
) : (
<TableColumnHeader key={column?.key} node={column} />
),
)}
</TableHeaderRow>
))}
<Spacer as="tr" y={0.4} />
</TableRowGroup>
<TableBody />
</Component>
{bottomContent}
</>
</BaseWrapper>
</TableProvider>
);
});

View File

@ -23,32 +23,28 @@ interface Props extends HTMLNextUIProps<"table"> {
* Ref to the DOM node.
*/
ref?: ReactRef<HTMLElement | null>;
/** The elements that make up the table. Includes the TableHeader, TableBody, Columns, and Rows. */
/*
* The elements that make up the table. Includes the TableHeader, TableBody, Columns, and Rows.
*/
children?: ReactNode;
/**
* Whether the table container should not be rendered.
* A custom wrapper component for the table.
* @default "div"
*/
BaseComponent?: React.ComponentType<any>;
/**
* A property to include a component in the top of the table.
*/
topContent?: ReactNode;
/**
* A property to include a component in the bottom of the table.
*/
bottomContent?: ReactNode;
/**
* Whether the table base container should not be rendered.
* @default false
*/
removeWrapper?: boolean;
/**
* Classname or List of classes to change the classNames of the element.
* if `className` is passed, it will be added to the base slot.
*
* @example
* ```ts
* <Table classNames={{
* base:"base-classes", // table wrapper
* table: "table-classes",
* thead: "thead-classes",
* tbody: "tbody-classes",
* tr: "tr-classes",
* th: "th-classes",
* td: "td-classes",
* tfoot: "tfoot-classes",
* }} />
* ```
*/
classNames?: SlotsToClasses<TableSlots>;
/**
* How multiple selection should behave in the collection.
* The selection behavior for the table. If selectionMode is `"none"`, this will be `null`.
@ -70,6 +66,27 @@ interface Props extends HTMLNextUIProps<"table"> {
onRowAction?: (key: Key) => void;
/** Handler that is called when a user performs an action on the cell. */
onCellAction?: (key: Key) => void;
/**
* Classname or List of classes to change the classNames of the element.
* if `className` is passed, it will be added to the base slot.
*
* @example
* ```ts
* <Table classNames={{
* base:"base-classes", // table wrapper
* table: "table-classes",
* thead: "thead-classes",
* tbody: "tbody-classes",
* tr: "tr-classes",
* th: "th-classes",
* td: "td-classes",
* tfoot: "tfoot-classes",
* sortIcon: "sort-icon-classes",
* emptyWrapper: "empty-wrapper-classes",
* }} />
* ```
*/
classNames?: SlotsToClasses<TableSlots>;
}
export type UseTableProps<T = object> = Props &
@ -108,6 +125,9 @@ export function useTable<T extends object>(originalProps: UseTableProps<T>) {
disabledBehavior = "selection",
showSelectionCheckboxes = selectionMode === "multiple" && selectionBehavior !== "replace",
disableAnimation = false,
BaseComponent = "div",
topContent,
bottomContent,
onRowAction,
onCellAction,
...otherProps
@ -140,7 +160,7 @@ export function useTable<T extends object>(originalProps: UseTableProps<T>) {
[...Object.values(variantProps), isSelectable, isMultiSelectable],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const context = useMemo<ContextType<T>>(
() => ({
@ -190,11 +210,14 @@ export function useTable<T extends object>(originalProps: UseTableProps<T>) {
});
return {
BaseComponent,
Component,
children,
state,
collection,
context,
topContent,
bottomContent,
removeWrapper,
selectionMode,
getBaseProps,

View File

@ -3,8 +3,11 @@ import {ComponentStory, ComponentMeta} from "@storybook/react";
import {table} from "@nextui-org/theme";
import {User} from "@nextui-org/user";
import {Chip, ChipProps} from "@nextui-org/chip";
import {Button} from "@nextui-org/button";
import {Spinner} from "@nextui-org/spinner";
import {Tooltip} from "@nextui-org/tooltip";
import {EditIcon, DeleteIcon, EyeIcon} from "@nextui-org/shared-icons";
import {useAsyncList} from "@react-stately/data";
import {Table, TableHeader, TableColumn, TableBody, TableCell, TableRow, TableProps} from "../src";
@ -104,6 +107,13 @@ const columns = [
},
];
type SWCharacter = {
name: string;
height: string;
mass: string;
birth_year: string;
};
const isObject = (target: unknown) => target && typeof target === "object";
const getKeyValue = (obj: any, key: Key) => {
@ -145,6 +155,17 @@ const StaticTemplate: ComponentStory<typeof Table> = (args: TableProps) => (
</Table>
);
const EmptyTemplate: ComponentStory<typeof Table> = (args: TableProps) => (
<Table aria-label="Example empty table" {...args}>
<TableHeader>
<TableColumn>NAME</TableColumn>
<TableColumn>ROLE</TableColumn>
<TableColumn>STATUS</TableColumn>
</TableHeader>
<TableBody renderEmptyState={() => "No rows to display."}>{[]}</TableBody>
</Table>
);
const DynamicTemplate: ComponentStory<typeof Table> = (args: TableProps) => (
<Table aria-label="Example table with dynamic content" {...args}>
<TableHeader columns={columns}>
@ -220,14 +241,16 @@ const CustomCellTemplate: ComponentStory<typeof Table> = (args: TableProps) => {
},
];
type User = typeof users[0];
const statusColorMap: Record<string, ChipProps["color"]> = {
active: "success",
paused: "danger",
vacation: "warning",
};
const renderCell = React.useCallback((user, columnKey) => {
const cellValue = user[columnKey];
const renderCell = React.useCallback((user: User, columnKey: React.Key) => {
const cellValue = user[columnKey as keyof User];
switch (columnKey) {
case "name":
@ -253,7 +276,6 @@ const CustomCellTemplate: ComponentStory<typeof Table> = (args: TableProps) => {
{cellValue}
</Chip>
);
case "actions":
return (
<div className="relative flex items-center gap-2">
@ -299,6 +321,125 @@ const CustomCellTemplate: ComponentStory<typeof Table> = (args: TableProps) => {
);
};
const SortableTemplate: ComponentStory<typeof Table> = (args: TableProps) => {
let list = useAsyncList<SWCharacter>({
async load({signal}) {
let res = await fetch(`https://swapi.py4e.com/api/people/?search`, {
signal,
});
let json = await res.json();
return {
items: json.results,
};
},
async sort({items, sortDescriptor}) {
return {
items: items.sort((a, b) => {
let first = a[sortDescriptor.column!];
let second = b[sortDescriptor.column!];
let cmp = (parseInt(first) || first) < (parseInt(second) || second) ? -1 : 1;
if (sortDescriptor.direction === "descending") {
cmp *= -1;
}
return cmp;
}),
};
},
});
return (
<Table
aria-label="Example table with client side sorting"
sortDescriptor={list.sortDescriptor}
onSortChange={list.sort}
{...args}
>
<TableHeader>
<TableColumn key="name" allowsSorting>
Name
</TableColumn>
<TableColumn key="height" allowsSorting>
Height
</TableColumn>
<TableColumn key="mass" allowsSorting>
Mass
</TableColumn>
<TableColumn key="birth_year" allowsSorting>
Birth year
</TableColumn>
</TableHeader>
<TableBody items={list.items}>
{(item) => (
<TableRow key={item.name}>
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>
</Table>
);
};
const LoadMoreTemplate: ComponentStory<typeof Table> = (args: TableProps) => {
const [page, setPage] = React.useState(1);
let list = useAsyncList<SWCharacter>({
async load({signal, cursor}) {
if (cursor) {
// write this /^http:\/\//i using RegExp
const regex = "/^http:///i";
cursor = cursor.replace(regex, "https://");
setPage((prev) => prev + 1);
}
let res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
let json = await res.json();
return {
items: json.results,
cursor: json.next,
};
},
});
const hasMore = page < 9;
return (
<Table
aria-label="Example table with client side sorting"
bottomContent={
hasMore ? (
<Button isDisabled={list.isLoading} variant="flat" onPress={list.loadMore}>
{list.isLoading && <Spinner color="white" size="sm" />}
Load More
</Button>
) : null
}
sortDescriptor={list.sortDescriptor}
onSortChange={list.sort}
{...args}
>
<TableHeader>
<TableColumn key="name">Name</TableColumn>
<TableColumn key="height">Height</TableColumn>
<TableColumn key="mass">Mass</TableColumn>
<TableColumn key="birth_year">Birth year</TableColumn>
</TableHeader>
<TableBody items={list.items}>
{(item) => (
<TableRow key={item.name}>
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
</TableRow>
)}
</TableBody>
</Table>
);
};
export const Static = StaticTemplate.bind({});
Static.args = {
...defaultProps,
@ -309,6 +450,17 @@ Dynamic.args = {
...defaultProps,
};
export const EmptyState = EmptyTemplate.bind({});
EmptyState.args = {
...defaultProps,
};
export const NoHeader = StaticTemplate.bind({});
NoHeader.args = {
...defaultProps,
hideHeader: true,
};
export const CustomCells = CustomCellTemplate.bind({});
CustomCells.args = {
...defaultProps,
@ -360,6 +512,17 @@ DisallowEmptySelection.args = {
selectionMode: "multiple",
};
export const Sortable = SortableTemplate.bind({});
Sortable.args = {
...defaultProps,
};
export const LoadMore = LoadMoreTemplate.bind({});
LoadMore.args = {
...defaultProps,
className: "max-w-3xl max-h-auto",
};
export const DisableAnimation = StaticTemplate.bind({});
DisableAnimation.args = {
...defaultProps,

View File

@ -190,7 +190,7 @@ export function useTooltip(originalProps: UseTooltipProps) {
[...Object.values(variantProps)],
);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getTriggerProps = useCallback<PropGetter>(
(props = {}, _ref: Ref<any> | null | undefined = null) => ({

View File

@ -76,7 +76,7 @@ export function useUser(props: UseUserProps) {
const slots = useMemo(() => user(), []);
const baseStyles = clsx(classNames?.base, className);
const baseStyles = clsx(className, classNames?.base);
const getUserProps = useCallback<PropGetter>(
() => ({

View File

@ -27,6 +27,7 @@ const button = tv({
"inline-flex",
"items-center",
"justify-center",
"max-w-fit",
"box-border",
"appearance-none",
"outline-none",

View File

@ -48,12 +48,13 @@ const focusRing = [
*/
const table = tv({
slots: {
base: "p-4 border border-neutral-100",
base: "flex flex-col items-center p-4 gap-4 border border-neutral-100 overflow-auto",
table: "",
thead: "",
tbody: "",
tr: ["group", "outline-none", ...focusRing],
th: [
"group",
"px-3",
"h-10",
"text-left",
@ -65,6 +66,9 @@ const table = tv({
"first:rounded-l-lg",
"last:rounded-r-lg",
"outline-none",
"data-[sortable=true]:transition-colors",
"data-[sortable=true]:cursor-pointer",
"data-[hover=true]:text-neutral-400",
...focusRing,
],
td: [
@ -88,6 +92,19 @@ const table = tv({
"group-data-[disabled=true]:text-neutral-300",
],
tfoot: "",
sortIcon: [
"ml-2",
"mb-px",
"opacity-0",
"text-inherit",
"inline-block",
"transition-transform-opacity",
"data-[visible=true]:opacity-100",
"group-data-[hover=true]:opacity-100",
"data-[direction=descending]:rotate-0",
"data-[direction=ascending]:rotate-180",
],
emptyWrapper: "text-neutral-300 align-middle text-center h-36",
},
variants: {
color: {
@ -165,6 +182,11 @@ const table = tv({
base: "shadow-inner",
},
},
hideHeader: {
true: {
thead: "hidden",
},
},
isStriped: {
true: {
td: [
@ -208,6 +230,7 @@ const table = tv({
shadow: "lg",
radius: "xl",
color: "neutral",
hideHeader: false,
isStriped: false,
fullWidth: true,
},

View File

@ -136,5 +136,5 @@ import {link, button} from "@nextui-org/theme";
<br />
<div class="block text-xs text-neutral-400">
Last updated on <time datetime="2023-03-07">April 19, 2023</time>
Last updated on <time datetime="2023-03-07">April 22, 2023</time>
</div>

View File

@ -0,0 +1,23 @@
import {IconSvgProps} from "./types";
export const ChevronDownIcon = ({strokeWidth = 1.5, ...otherProps}: IconSvgProps) => (
<svg
aria-hidden="true"
fill="none"
focusable="false"
height="1em"
role="presentation"
viewBox="0 0 24 24"
width="1em"
{...otherProps}
>
<path
d="m19.92 8.95-6.52 6.52c-.77.77-2.03.77-2.8 0L4.08 8.95"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeMiterlimit={10}
strokeWidth={strokeWidth}
/>
</svg>
);

View File

@ -5,6 +5,7 @@ export * from "./avatar";
export * from "./close";
export * from "./close-filled";
export * from "./chevron";
export * from "./chevron-down";
export * from "./ellipsis";
export * from "./forward";
export * from "./sun";

26
pnpm-lock.yaml generated
View File

@ -1481,6 +1481,9 @@ importers:
'@nextui-org/dom-utils':
specifier: workspace:*
version: link:../../utilities/dom-utils
'@nextui-org/shared-icons':
specifier: workspace:*
version: link:../../utilities/shared-icons
'@nextui-org/shared-utils':
specifier: workspace:*
version: link:../../utilities/shared-utils
@ -1512,18 +1515,27 @@ importers:
specifier: ^3.9.0
version: 3.9.0(react@18.2.0)
devDependencies:
'@nextui-org/button':
specifier: workspace:*
version: link:../button
'@nextui-org/chip':
specifier: workspace:*
version: link:../chip
'@nextui-org/shared-icons':
'@nextui-org/pagination':
specifier: workspace:*
version: link:../../utilities/shared-icons
version: link:../pagination
'@nextui-org/spinner':
specifier: workspace:*
version: link:../spinner
'@nextui-org/tooltip':
specifier: workspace:*
version: link:../tooltip
'@nextui-org/user':
specifier: workspace:*
version: link:../user
'@react-stately/data':
specifier: ^3.9.1
version: 3.9.1(react@18.2.0)
'@react-types/grid':
specifier: ^3.1.7
version: 3.1.7(react@18.2.0)
@ -6702,6 +6714,16 @@ packages:
react: 18.2.0
dev: false
/@react-stately/data@3.9.1(react@18.2.0):
resolution: {integrity: sha512-UClgI8jQTF3hVR/WLa2ht7Gjd2x2PRnYycDmfY+mfbd+ONBD7rX/m3KWGgrR8AvO05qSpQoSlab8D+cfLXvgWA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0
dependencies:
'@react-types/shared': 3.18.0(react@18.2.0)
'@swc/helpers': 0.4.14
react: 18.2.0
dev: true
/@react-stately/grid@3.6.0(react@18.2.0):
resolution: {integrity: sha512-Sq/ivfq9Kskghoe6rYh2PfhB9/jBGfoj8wUZ4bqHcalTrBjfUvkcWMSFosibYPNZFDkA7r00bbJPDJVf1VLhuw==}
peerDependencies: