mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(table): big improvements
This commit is contained in:
parent
4309dbf161
commit
fbbedfbd2d
@ -124,6 +124,7 @@ export function useAvatar(props: UseAvatarProps) {
|
||||
const {isHovered, hoverProps} = useHover({isDisabled});
|
||||
|
||||
const imageStatus = useImage({src, onError, ignoreFallback});
|
||||
|
||||
const isImgLoaded = imageStatus === "loaded";
|
||||
|
||||
/**
|
||||
@ -149,13 +150,6 @@ export function useAvatar(props: UseAvatarProps) {
|
||||
[color, radius, size, isBordered, isDisabled, isInGroup, groupContext?.isGrid],
|
||||
);
|
||||
|
||||
const buttonStyles = useMemo(() => {
|
||||
if (as !== "button") return "";
|
||||
|
||||
// reset button classNames
|
||||
return "appearance-none outline-none border-none cursor-pointer";
|
||||
}, [as]);
|
||||
|
||||
const baseStyles = clsx(classNames?.base, className);
|
||||
|
||||
const canBeFocused = useMemo(() => {
|
||||
@ -163,25 +157,25 @@ export function useAvatar(props: UseAvatarProps) {
|
||||
}, [isFocusable, as]);
|
||||
|
||||
const getAvatarProps = useCallback<PropGetter>(
|
||||
() => ({
|
||||
(props = {}) => ({
|
||||
ref: domRef,
|
||||
tabIndex: canBeFocused ? 0 : -1,
|
||||
"data-hover": dataAttr(isHovered),
|
||||
"data-focus": dataAttr(isFocused),
|
||||
"data-focus-visible": dataAttr(isFocusVisible),
|
||||
className: slots.base({
|
||||
class: clsx(baseStyles, buttonStyles),
|
||||
class: clsx(baseStyles, props?.className),
|
||||
}),
|
||||
...mergeProps(otherProps, hoverProps, canBeFocused ? focusProps : {}),
|
||||
}),
|
||||
[canBeFocused, slots, baseStyles, buttonStyles, focusProps, otherProps],
|
||||
[canBeFocused, slots, baseStyles, focusProps, otherProps],
|
||||
);
|
||||
|
||||
const getImageProps = useCallback<PropGetter>(
|
||||
() => ({
|
||||
ref: imgRef,
|
||||
src: src,
|
||||
"data-loaded": isImgLoaded,
|
||||
"data-loaded": dataAttr(isImgLoaded),
|
||||
className: slots.img({class: classNames?.img}),
|
||||
}),
|
||||
[slots, isImgLoaded, src, imgRef],
|
||||
|
||||
@ -226,7 +226,7 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
ref: domRef,
|
||||
className: slots.base({class: baseStyles}),
|
||||
"data-disabled": dataAttr(isDisabled),
|
||||
"data-checked": dataAttr(isSelected),
|
||||
"data-checked": dataAttr(isSelected || isIndeterminate),
|
||||
"data-invalid": dataAttr(isInvalid),
|
||||
"data-hover": dataAttr(isHovered),
|
||||
"data-focus": dataAttr(isFocused),
|
||||
|
||||
24
packages/components/spacer/README.md
Normal file
24
packages/components/spacer/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
# @nextui-org/spacer
|
||||
|
||||
A Quick description of the component
|
||||
|
||||
> This is an internal utility, not intended for public usage.
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
yarn add @nextui-org/spacer
|
||||
# or
|
||||
npm i @nextui-org/spacer
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
Yes please! See the
|
||||
[contributing guidelines](https://github.com/nextui-org/nextui/blob/master/CONTRIBUTING.md)
|
||||
for details.
|
||||
|
||||
## Licence
|
||||
|
||||
This project is licensed under the terms of the
|
||||
[MIT license](https://github.com/nextui-org/nextui/blob/master/LICENSE).
|
||||
19
packages/components/spacer/__tests__/spacer.test.tsx
Normal file
19
packages/components/spacer/__tests__/spacer.test.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
|
||||
import {Spacer} from "../src";
|
||||
|
||||
describe("Spacer", () => {
|
||||
it("should render correctly", () => {
|
||||
const wrapper = render(<Spacer />);
|
||||
|
||||
expect(() => wrapper.unmount()).not.toThrow();
|
||||
});
|
||||
|
||||
it("ref should be forwarded", () => {
|
||||
const ref = React.createRef<HTMLDivElement>();
|
||||
|
||||
render(<Spacer ref={ref} />);
|
||||
expect(ref.current).not.toBeNull();
|
||||
});
|
||||
});
|
||||
58
packages/components/spacer/package.json
Normal file
58
packages/components/spacer/package.json
Normal file
@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "@nextui-org/spacer",
|
||||
"version": "2.0.0-beta.1",
|
||||
"description": "A flexible spacer component designed to create consistent spacing and maintain alignment in your layout.",
|
||||
"keywords": [
|
||||
"spacer"
|
||||
],
|
||||
"author": "Junior Garcia <jrgarciadev@gmail.com>",
|
||||
"homepage": "https://nextui.org",
|
||||
"license": "MIT",
|
||||
"main": "src/index.ts",
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/nextui-org/nextui.git",
|
||||
"directory": "packages/components/spacer"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextui-org/nextui/issues"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src --dts",
|
||||
"build:fast": "tsup src",
|
||||
"dev": "yarn build:fast -- --watch",
|
||||
"clean": "rimraf dist .turbo",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prepack": "clean-package",
|
||||
"postpack": "clean-package restore"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"clean-package": "2.2.0",
|
||||
"react": "^18.0.0"
|
||||
},
|
||||
"clean-package": "../../../clean-package.config.json",
|
||||
"tsup": {
|
||||
"clean": true,
|
||||
"target": "es2019",
|
||||
"format": [
|
||||
"cjs",
|
||||
"esm"
|
||||
]
|
||||
}
|
||||
}
|
||||
10
packages/components/spacer/src/index.ts
Normal file
10
packages/components/spacer/src/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import Spacer from "./spacer";
|
||||
|
||||
// export types
|
||||
export type {SpacerProps} from "./spacer";
|
||||
|
||||
// export hooks
|
||||
export {useSpacer} from "./use-spacer";
|
||||
|
||||
// export component
|
||||
export {Spacer};
|
||||
15
packages/components/spacer/src/spacer.tsx
Normal file
15
packages/components/spacer/src/spacer.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
|
||||
import {UseSpacerProps, useSpacer} from "./use-spacer";
|
||||
|
||||
export interface SpacerProps extends Omit<UseSpacerProps, "ref"> {}
|
||||
|
||||
const Spacer = forwardRef<SpacerProps, "span">((props, ref) => {
|
||||
const {Component, getSpacerProps} = useSpacer({ref, ...props});
|
||||
|
||||
return <Component {...getSpacerProps()} />;
|
||||
});
|
||||
|
||||
Spacer.displayName = "NextUI.Spacer";
|
||||
|
||||
export default Spacer;
|
||||
68
packages/components/spacer/src/use-spacer.ts
Normal file
68
packages/components/spacer/src/use-spacer.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import type {SpacerVariantProps} from "@nextui-org/theme";
|
||||
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {spacer} from "@nextui-org/theme";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, dataAttr, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useMemo} from "react";
|
||||
|
||||
export interface UseSpacerProps extends HTMLNextUIProps<"span", SpacerVariantProps> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLElement | null>;
|
||||
/**
|
||||
* The x-axis margin.
|
||||
* @default 1
|
||||
*/
|
||||
x?: number;
|
||||
/**
|
||||
* The y-axis margin.
|
||||
* @default 1
|
||||
*/
|
||||
y?: number;
|
||||
}
|
||||
|
||||
export const getMargin = (num: number): string => {
|
||||
return `calc(${num * 15.25}pt + 1px * ${num - 1})`;
|
||||
};
|
||||
|
||||
export function useSpacer(originalProps: UseSpacerProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, spacer.variantKeys);
|
||||
|
||||
const {ref, as, className, x = 1, y = 1, ...otherProps} = props;
|
||||
|
||||
const Component = as || "span";
|
||||
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const styles = useMemo(
|
||||
() =>
|
||||
spacer({
|
||||
...variantProps,
|
||||
className,
|
||||
}),
|
||||
[...Object.values(variantProps), className],
|
||||
);
|
||||
|
||||
const marginLeft = getMargin(x);
|
||||
const marginTop = getMargin(y);
|
||||
|
||||
const getSpacerProps: PropGetter = (props = {}) => ({
|
||||
ref: domRef,
|
||||
...props,
|
||||
...otherProps,
|
||||
"aria-hidden": dataAttr(true),
|
||||
className: clsx(styles, props.className),
|
||||
style: {
|
||||
...props.style,
|
||||
...otherProps.style,
|
||||
marginLeft,
|
||||
marginTop,
|
||||
},
|
||||
});
|
||||
|
||||
return {Component, getSpacerProps};
|
||||
}
|
||||
|
||||
export type UseSpacerReturn = ReturnType<typeof useSpacer>;
|
||||
46
packages/components/spacer/stories/spacer.stories.tsx
Normal file
46
packages/components/spacer/stories/spacer.stories.tsx
Normal file
@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import {ComponentStory, ComponentMeta} from "@storybook/react";
|
||||
import {spacer} from "@nextui-org/theme";
|
||||
|
||||
import {Spacer, SpacerProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Components/Spacer",
|
||||
component: Spacer,
|
||||
argTypes: {
|
||||
color: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["neutral", "primary", "secondary", "success", "warning", "danger"],
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["none", "base", "sm", "md", "lg", "xl", "full"],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["xs", "sm", "md", "lg", "xl"],
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Spacer>;
|
||||
|
||||
const defaultProps = {
|
||||
...spacer.defaultVariants,
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof Spacer> = (args: SpacerProps) => <Spacer {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
10
packages/components/spacer/tsconfig.json
Normal file
10
packages/components/spacer/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"tailwind-variants": ["../../../node_modules/tailwind-variants"]
|
||||
}
|
||||
},
|
||||
"include": ["src", "index.ts"]
|
||||
}
|
||||
@ -38,6 +38,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/checkbox": "workspace:*",
|
||||
"@nextui-org/spacer": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/system": "workspace:*",
|
||||
@ -46,9 +47,14 @@
|
||||
"@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:*",
|
||||
"@react-types/grid": "^3.1.7",
|
||||
"@react-types/table": "^3.6.0",
|
||||
"clean-package": "2.2.0",
|
||||
|
||||
11
packages/components/table/src/base/index.ts
Normal file
11
packages/components/table/src/base/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export {default as TableBody} from "./table-body";
|
||||
export {default as TableCell} from "./table-cell";
|
||||
export {default as TableColumn} from "./table-column";
|
||||
export {default as TableHeader} from "./table-header";
|
||||
export {default as TableRow} from "./table-row";
|
||||
|
||||
// export types
|
||||
export type {TableBodyProps} from "./table-body";
|
||||
export type {TableCellProps} from "./table-cell";
|
||||
export type {TableColumnProps} from "./table-column";
|
||||
export type {TableHeaderProps} from "./table-header";
|
||||
10
packages/components/table/src/base/table-body.tsx
Normal file
10
packages/components/table/src/base/table-body.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {TableBody as TableBodyBase} from "@react-stately/table";
|
||||
import {TableBodyProps as TableBodyBaseProps} from "@react-types/table";
|
||||
|
||||
export type TableBodyProps<T> = TableBodyBaseProps<T> &
|
||||
Omit<HTMLNextUIProps<"tbody">, keyof TableBodyBaseProps<T>>;
|
||||
|
||||
const TableBody = TableBodyBase as <T>(props: TableBodyProps<T>) => JSX.Element;
|
||||
|
||||
export default TableBody;
|
||||
9
packages/components/table/src/base/table-cell.tsx
Normal file
9
packages/components/table/src/base/table-cell.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {Cell} from "@react-stately/table";
|
||||
import {CellProps} from "@react-types/table";
|
||||
|
||||
export type TableCellProps = CellProps & HTMLNextUIProps<"td">;
|
||||
|
||||
const TableCell = Cell as (props: TableCellProps) => JSX.Element;
|
||||
|
||||
export default TableCell;
|
||||
10
packages/components/table/src/base/table-column.tsx
Normal file
10
packages/components/table/src/base/table-column.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {Column} from "@react-stately/table";
|
||||
import {SpectrumColumnProps} from "@react-types/table";
|
||||
|
||||
export type TableColumnProps<T> = Omit<SpectrumColumnProps<T>, "showDivider"> &
|
||||
Omit<HTMLNextUIProps<"th">, keyof SpectrumColumnProps<T>>;
|
||||
|
||||
const TableColumn = Column as <T>(props: TableColumnProps<T>) => JSX.Element;
|
||||
|
||||
export default TableColumn;
|
||||
10
packages/components/table/src/base/table-header.tsx
Normal file
10
packages/components/table/src/base/table-header.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {TableHeader as TableHeaderBase} from "@react-stately/table";
|
||||
import {TableHeaderProps as TableHeaderBaseProps} from "@react-types/table";
|
||||
|
||||
export type TableHeaderProps<T> = TableHeaderBaseProps<T> &
|
||||
Omit<HTMLNextUIProps<"thead">, keyof TableHeaderBaseProps<T>>;
|
||||
|
||||
const TableHeader = TableHeaderBase as <T>(props: TableHeaderProps<T>) => JSX.Element;
|
||||
|
||||
export default TableHeader;
|
||||
9
packages/components/table/src/base/table-row.tsx
Normal file
9
packages/components/table/src/base/table-row.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {Row} from "@react-stately/table";
|
||||
import {RowProps} from "@react-types/table";
|
||||
|
||||
export type TableRowProps = RowProps & Omit<HTMLNextUIProps<"tr">, keyof RowProps>;
|
||||
|
||||
const TableRow = Row as (props: TableRowProps) => JSX.Element;
|
||||
|
||||
export default TableRow;
|
||||
@ -1,5 +1,3 @@
|
||||
import Table from "./table";
|
||||
|
||||
// export types
|
||||
export type {TableProps} from "./table";
|
||||
|
||||
@ -7,4 +5,5 @@ export type {TableProps} from "./table";
|
||||
export {useTable} from "./use-table";
|
||||
|
||||
// export component
|
||||
export {Table};
|
||||
export {default as Table} from "./table";
|
||||
export * from "./base";
|
||||
|
||||
50
packages/components/table/src/table-body.tsx
Normal file
50
packages/components/table/src/table-body.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import {useMemo} from "react";
|
||||
|
||||
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 = asProp || "tbody";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, collection, classNames} = useTableContext();
|
||||
|
||||
const tbodyStyles = clsx(classNames?.tbody, className);
|
||||
|
||||
const renderRows = useMemo(() => {
|
||||
return [...collection.body.childNodes].map((row) => (
|
||||
<TableRow key={row.key} node={row}>
|
||||
{[...row.childNodes].map((cell) =>
|
||||
cell.props.isSelectionCell ? (
|
||||
<TableCheckboxCell key={cell.key} node={cell} rowKey={row.key} />
|
||||
) : (
|
||||
<TableCell key={cell.key} node={cell} rowKey={row.key} />
|
||||
),
|
||||
)}
|
||||
</TableRow>
|
||||
));
|
||||
}, [collection.body.childNodes]);
|
||||
|
||||
return (
|
||||
<TableRowGroup
|
||||
ref={domRef}
|
||||
as={as}
|
||||
{...otherProps}
|
||||
className={slots.tbody?.({class: tbodyStyles})}
|
||||
>
|
||||
{renderRows}
|
||||
</TableRowGroup>
|
||||
);
|
||||
});
|
||||
|
||||
TableBody.displayName = "NextUI.TableBody";
|
||||
|
||||
export default TableBody;
|
||||
58
packages/components/table/src/table-cell.tsx
Normal file
58
packages/components/table/src/table-cell.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import type {GridNode} from "@react-types/grid";
|
||||
|
||||
import {Key} from "react";
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, dataAttr} from "@nextui-org/shared-utils";
|
||||
import {useTableCell} from "@react-aria/table";
|
||||
import {filterDOMProps, mergeProps} from "@react-aria/utils";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
|
||||
import {useTableContext} from "./table-context";
|
||||
|
||||
export interface TableCellProps<T = object> extends HTMLNextUIProps<"td"> {
|
||||
/**
|
||||
* The key of the table row.
|
||||
*/
|
||||
rowKey: Key;
|
||||
/**
|
||||
* The table cell.
|
||||
*/
|
||||
node: GridNode<T>;
|
||||
}
|
||||
|
||||
const TableCell = forwardRef<TableCellProps, "td">((props, ref) => {
|
||||
const {as, className, node, rowKey, ...otherProps} = props;
|
||||
|
||||
const Component = as || "td";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, state, classNames} = useTableContext();
|
||||
|
||||
const {gridCellProps} = useTableCell({node}, state, domRef);
|
||||
|
||||
const tdStyles = clsx(classNames?.thead, className, node.props?.className);
|
||||
const {isFocusVisible, focusProps} = useFocusRing();
|
||||
const isRowSelected = state.selectionManager.isSelected(rowKey);
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={domRef}
|
||||
data-focus-visible={dataAttr(isFocusVisible)}
|
||||
data-selected={dataAttr(isRowSelected)}
|
||||
{...mergeProps(
|
||||
gridCellProps,
|
||||
focusProps,
|
||||
filterDOMProps(node.props, {labelable: true}),
|
||||
otherProps,
|
||||
)}
|
||||
className={slots.td?.({class: tdStyles})}
|
||||
>
|
||||
{node.rendered}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
TableCell.displayName = "NextUI.TableCell";
|
||||
|
||||
export default TableCell;
|
||||
75
packages/components/table/src/table-checkbox-cell.tsx
Normal file
75
packages/components/table/src/table-checkbox-cell.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import type {GridNode} from "@react-types/grid";
|
||||
import type {Key} from "react";
|
||||
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, dataAttr} from "@nextui-org/shared-utils";
|
||||
import {useTableCell, useTableSelectionCheckbox} from "@react-aria/table";
|
||||
import {filterDOMProps, mergeProps} from "@react-aria/utils";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {Checkbox} from "@nextui-org/checkbox";
|
||||
import {VisuallyHidden} from "@react-aria/visually-hidden";
|
||||
|
||||
import {useTableContext} from "./table-context";
|
||||
|
||||
export interface TableCheckboxCellProps<T = object> extends HTMLNextUIProps<"td"> {
|
||||
/**
|
||||
* The key of the table row.
|
||||
*/
|
||||
rowKey: Key;
|
||||
/**
|
||||
* The table cell.
|
||||
*/
|
||||
node: GridNode<T>;
|
||||
}
|
||||
|
||||
const TableCheckboxCell = forwardRef<TableCheckboxCellProps, "td">((props, ref) => {
|
||||
const {as, className, node, rowKey, ...otherProps} = props;
|
||||
|
||||
const Component = as || "td";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, state, color, disableAnimation, selectionMode, classNames} = useTableContext();
|
||||
|
||||
const {gridCellProps} = useTableCell({node}, state, domRef);
|
||||
const {isFocusVisible, focusProps} = useFocusRing();
|
||||
|
||||
const {checkboxProps} = useTableSelectionCheckbox({key: node?.parentKey || node.key}, state);
|
||||
|
||||
const tdStyles = clsx(classNames?.td, className, node.props?.className);
|
||||
|
||||
const isSingleSelectionMode = selectionMode === "single";
|
||||
|
||||
const {onChange, ...otherCheckboxProps} = checkboxProps;
|
||||
const isRowSelected = state.selectionManager.isSelected(rowKey);
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={domRef}
|
||||
data-focus-visible={dataAttr(isFocusVisible)}
|
||||
data-selected={dataAttr(isRowSelected)}
|
||||
{...mergeProps(
|
||||
gridCellProps,
|
||||
focusProps,
|
||||
filterDOMProps(node.props, {labelable: true}),
|
||||
otherProps,
|
||||
)}
|
||||
className={slots.td?.({class: tdStyles})}
|
||||
>
|
||||
{isSingleSelectionMode ? (
|
||||
<VisuallyHidden>{checkboxProps["aria-label"]}</VisuallyHidden>
|
||||
) : (
|
||||
<Checkbox
|
||||
color={color}
|
||||
disableAnimation={disableAnimation}
|
||||
onValueChange={onChange}
|
||||
{...otherCheckboxProps}
|
||||
/>
|
||||
)}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
TableCheckboxCell.displayName = "NextUI.TableCheckboxCell";
|
||||
|
||||
export default TableCheckboxCell;
|
||||
77
packages/components/table/src/table-column-header.tsx
Normal file
77
packages/components/table/src/table-column-header.tsx
Normal file
@ -0,0 +1,77 @@
|
||||
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 {useTableColumnHeader} from "@react-aria/table";
|
||||
import {filterDOMProps, mergeProps} from "@react-aria/utils";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {VisuallyHidden} from "@react-aria/visually-hidden";
|
||||
|
||||
import {useTableContext} from "./table-context";
|
||||
|
||||
export interface TableColumnHeaderProps<T = object> extends HTMLNextUIProps<"th"> {
|
||||
/**
|
||||
* The table node to render.
|
||||
*/
|
||||
node: GridNode<T>;
|
||||
}
|
||||
|
||||
const TableColumnHeader = forwardRef<TableColumnHeaderProps, "th">((props, ref) => {
|
||||
const {as, className, node, ...otherProps} = props;
|
||||
|
||||
const Component = as || "th";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, state, classNames} = useTableContext();
|
||||
|
||||
const {columnHeaderProps} = useTableColumnHeader({node}, state, domRef);
|
||||
|
||||
const thStyles = clsx(classNames?.th, className, node.props?.className);
|
||||
|
||||
const {isFocusVisible, focusProps} = useFocusRing();
|
||||
const {hideHeader, ...columnProps} = node.props;
|
||||
|
||||
let arrowIcon = state.sortDescriptor?.direction === "ascending" ? "▲" : "▼";
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={domRef}
|
||||
colSpan={node.colspan}
|
||||
data-focus-visible={isFocusVisible}
|
||||
{...mergeProps(
|
||||
columnHeaderProps,
|
||||
focusProps,
|
||||
filterDOMProps(columnProps, {labelable: true}),
|
||||
otherProps,
|
||||
)}
|
||||
className={slots.th?.({class: thStyles})}
|
||||
>
|
||||
{hideHeader ? <VisuallyHidden>{node.rendered}</VisuallyHidden> : node.rendered}
|
||||
{columnProps.allowsSorting && (
|
||||
<span
|
||||
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}
|
||||
// />
|
||||
)}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
TableColumnHeader.displayName = "NextUI.TableColumnHeader";
|
||||
|
||||
export default TableColumnHeader;
|
||||
43
packages/components/table/src/table-header-row.tsx
Normal file
43
packages/components/table/src/table-header-row.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
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 {useTableHeaderRow} from "@react-aria/table";
|
||||
import {filterDOMProps, mergeProps} from "@react-aria/utils";
|
||||
|
||||
import {useTableContext} from "./table-context";
|
||||
|
||||
export interface TableHeaderRowProps<T = object> extends HTMLNextUIProps<"tr"> {
|
||||
/**
|
||||
* The table node to render.
|
||||
*/
|
||||
node: GridNode<T>;
|
||||
}
|
||||
|
||||
const TableHeaderRow = forwardRef<TableHeaderRowProps, "tr">((props, ref) => {
|
||||
const {as, className, children, node, ...otherProps} = props;
|
||||
|
||||
const Component = as || "tr";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, state, classNames} = useTableContext();
|
||||
|
||||
const {rowProps} = useTableHeaderRow({node}, state, domRef);
|
||||
|
||||
const trStyles = clsx(classNames?.tr, className, node.props?.className);
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={domRef}
|
||||
{...mergeProps(rowProps, filterDOMProps(node.props, {labelable: true}), otherProps)}
|
||||
className={slots.tr?.({class: trStyles})}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
TableHeaderRow.displayName = "NextUI.TableHeaderRow";
|
||||
|
||||
export default TableHeaderRow;
|
||||
34
packages/components/table/src/table-row-group.tsx
Normal file
34
packages/components/table/src/table-row-group.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
import {useTableRowGroup} from "@react-aria/table";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
|
||||
import {useTableContext} from "./table-context";
|
||||
|
||||
const TableRowGroup = forwardRef<HTMLNextUIProps, "thead">((props, ref) => {
|
||||
const {as, className, children, ...otherProps} = props;
|
||||
|
||||
const Component = as || "thead";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, classNames} = useTableContext();
|
||||
|
||||
const {rowGroupProps} = useTableRowGroup();
|
||||
|
||||
const theadStyles = clsx(classNames?.thead, className);
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={domRef}
|
||||
className={slots.thead?.({class: theadStyles})}
|
||||
{...mergeProps(rowGroupProps, otherProps)}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
TableRowGroup.displayName = "NextUI.TableRowGroup";
|
||||
|
||||
export default TableRowGroup;
|
||||
80
packages/components/table/src/table-row.tsx
Normal file
80
packages/components/table/src/table-row.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import type {GridNode} from "@react-types/grid";
|
||||
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, dataAttr} from "@nextui-org/shared-utils";
|
||||
import {useTableRow} from "@react-aria/table";
|
||||
import {filterDOMProps, mergeProps} from "@react-aria/utils";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {useHover} from "@react-aria/interactions";
|
||||
import {useMemo} from "react";
|
||||
|
||||
import {useTableContext} from "./table-context";
|
||||
|
||||
export interface TableRowProps<T = object> extends HTMLNextUIProps<"tr"> {
|
||||
/**
|
||||
* The table row.
|
||||
*/
|
||||
node: GridNode<T>;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
const {rowProps} = useTableRow({node}, state, domRef);
|
||||
|
||||
const trStyles = clsx(classNames?.thead, className, node.props?.className);
|
||||
|
||||
const {isFocusVisible, focusProps} = useFocusRing();
|
||||
|
||||
const isDisabled = state.disabledKeys.has(node.key);
|
||||
const isSelected = state.selectionManager.isSelected(node.key);
|
||||
|
||||
const {isHovered, hoverProps} = useHover({isDisabled});
|
||||
|
||||
const {isFirst, isLast, isMiddle, isOdd} = useMemo(() => {
|
||||
const isFirst = node.key === state.collection.getFirstKey();
|
||||
const isLast = node.key === state.collection.getLastKey();
|
||||
const isMiddle = !isFirst && !isLast;
|
||||
const isOdd = node?.index ? (node.index + 1) % 2 === 0 : false;
|
||||
|
||||
return {
|
||||
isFirst,
|
||||
isLast,
|
||||
isMiddle,
|
||||
isOdd,
|
||||
};
|
||||
}, [node, state.collection]);
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={domRef}
|
||||
data-disabled={dataAttr(isDisabled)}
|
||||
data-first={dataAttr(isFirst)}
|
||||
data-focus-visible={dataAttr(isFocusVisible)}
|
||||
data-hover={dataAttr(isHovered)}
|
||||
data-last={dataAttr(isLast)}
|
||||
data-middle={dataAttr(isMiddle)}
|
||||
data-odd={dataAttr(isOdd)}
|
||||
data-selected={dataAttr(isSelected)}
|
||||
{...mergeProps(
|
||||
rowProps,
|
||||
isSelectable ? {...hoverProps, ...focusProps} : {},
|
||||
filterDOMProps(node.props, {labelable: true}),
|
||||
otherProps,
|
||||
)}
|
||||
className={slots.tr?.({class: trStyles})}
|
||||
>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
TableRow.displayName = "NextUI.TableRow";
|
||||
|
||||
export default TableRow;
|
||||
68
packages/components/table/src/table-select-all-checkbox.tsx
Normal file
68
packages/components/table/src/table-select-all-checkbox.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
import type {GridNode} from "@react-types/grid";
|
||||
|
||||
import {forwardRef, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, dataAttr} from "@nextui-org/shared-utils";
|
||||
import {useTableColumnHeader, useTableSelectAllCheckbox} from "@react-aria/table";
|
||||
import {filterDOMProps, mergeProps} from "@react-aria/utils";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {Checkbox} from "@nextui-org/checkbox";
|
||||
import {VisuallyHidden} from "@react-aria/visually-hidden";
|
||||
|
||||
import {useTableContext} from "./table-context";
|
||||
|
||||
export interface TableSelectAllCheckboxProps<T = object> extends HTMLNextUIProps<"th"> {
|
||||
/**
|
||||
* The table column.
|
||||
*/
|
||||
node: GridNode<T>;
|
||||
}
|
||||
|
||||
const TableSelectAllCheckbox = forwardRef<TableSelectAllCheckboxProps, "th">((props, ref) => {
|
||||
const {as, className, node, ...otherProps} = props;
|
||||
|
||||
const Component = as || "th";
|
||||
const domRef = useDOMRef(ref);
|
||||
|
||||
const {slots, state, selectionMode, color, disableAnimation, classNames} = useTableContext();
|
||||
|
||||
const {columnHeaderProps} = useTableColumnHeader({node}, state, domRef);
|
||||
const {isFocusVisible, focusProps} = useFocusRing();
|
||||
|
||||
const {checkboxProps} = useTableSelectAllCheckbox(state);
|
||||
|
||||
const thStyles = clsx(classNames?.th, className, node.props?.className);
|
||||
|
||||
const isSingleSelectionMode = selectionMode === "single";
|
||||
|
||||
const {onChange, ...otherCheckboxProps} = checkboxProps;
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={domRef}
|
||||
data-focus-visible={dataAttr(isFocusVisible)}
|
||||
{...mergeProps(
|
||||
columnHeaderProps,
|
||||
focusProps,
|
||||
filterDOMProps(node.props, {labelable: true}),
|
||||
otherProps,
|
||||
)}
|
||||
className={slots.th?.({class: thStyles})}
|
||||
>
|
||||
{isSingleSelectionMode ? (
|
||||
<VisuallyHidden>{checkboxProps["aria-label"]}</VisuallyHidden>
|
||||
) : (
|
||||
<Checkbox
|
||||
color={color}
|
||||
disableAnimation={disableAnimation}
|
||||
onValueChange={onChange}
|
||||
{...otherCheckboxProps}
|
||||
/>
|
||||
)}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
TableSelectAllCheckbox.displayName = "NextUI.TableSelectAllCheckbox";
|
||||
|
||||
export default TableSelectAllCheckbox;
|
||||
@ -1,18 +1,56 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {Spacer} from "@nextui-org/spacer";
|
||||
import {useCallback} from "react";
|
||||
|
||||
import {TableProvider} from "./table-context";
|
||||
import {UseTableProps, useTable} from "./use-table";
|
||||
import TableRowGroup from "./table-row-group";
|
||||
import TableHeaderRow from "./table-header-row";
|
||||
import TableColumnHeader from "./table-column-header";
|
||||
import TableSelectAllCheckbox from "./table-select-all-checkbox";
|
||||
import TableBody from "./table-body";
|
||||
|
||||
export interface TableProps extends Omit<UseTableProps, "ref"> {}
|
||||
export interface TableProps
|
||||
extends Omit<UseTableProps, "ref" | "isSelectable" | "isMultiSelectable"> {}
|
||||
|
||||
const Table = forwardRef<TableProps, "div">((props, ref) => {
|
||||
const {Component, children, context, getBaseProps, getTableProps} = useTable({ref, ...props});
|
||||
const Table = forwardRef<TableProps, "table">((props, ref) => {
|
||||
const {Component, collection, context, removeWrapper, getBaseProps, getTableProps} = useTable({
|
||||
ref,
|
||||
...props,
|
||||
});
|
||||
|
||||
const Wrapper = useCallback(
|
||||
({children}: {children: JSX.Element}) => {
|
||||
if (removeWrapper) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return <div {...getBaseProps()}>{children}</div>;
|
||||
},
|
||||
[removeWrapper, getBaseProps],
|
||||
);
|
||||
|
||||
return (
|
||||
<TableProvider value={context}>
|
||||
<div {...getBaseProps()}>
|
||||
<Component {...getTableProps()}>{children}</Component>
|
||||
</div>
|
||||
<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>
|
||||
</TableProvider>
|
||||
);
|
||||
});
|
||||
|
||||
@ -5,16 +5,16 @@ import type {
|
||||
TableSlots,
|
||||
} from "@nextui-org/theme";
|
||||
import type {SelectionBehavior, DisabledBehavior} from "@react-types/shared";
|
||||
import type {ReactNode, Key} from "react";
|
||||
import type {TableState, TableStateProps} from "@react-stately/table";
|
||||
import type {TableCollection} from "@react-types/table";
|
||||
|
||||
import {ReactNode, Key, useCallback} from "react";
|
||||
import {useTableState} from "@react-stately/table";
|
||||
import {useTable as useReactAriaTable} from "@react-aria/table";
|
||||
import {AriaTableProps, useTable as useReactAriaTable} from "@react-aria/table";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {table} from "@nextui-org/theme";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {filterDOMProps, mergeProps} from "@react-aria/utils";
|
||||
import {clsx, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useMemo} from "react";
|
||||
|
||||
@ -25,6 +25,11 @@ interface Props extends HTMLNextUIProps<"table"> {
|
||||
ref?: ReactRef<HTMLElement | null>;
|
||||
/** The elements that make up the table. Includes the TableHeader, TableBody, Columns, and Rows. */
|
||||
children?: ReactNode;
|
||||
/**
|
||||
* Whether the table 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.
|
||||
@ -46,33 +51,46 @@ interface Props extends HTMLNextUIProps<"table"> {
|
||||
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`.
|
||||
* otherwise, this will be `toggle` or `replace`.
|
||||
* @default "toggle"
|
||||
*/
|
||||
selectionBehavior?: SelectionBehavior;
|
||||
selectionBehavior?: SelectionBehavior | null;
|
||||
/**
|
||||
* Whether `disabledKeys` applies to all interactions, or only selection.
|
||||
* @default "selection"
|
||||
*/
|
||||
disabledBehavior?: DisabledBehavior;
|
||||
/**
|
||||
* Whether to disabled the related animations such as checkbox animation.
|
||||
* @default false
|
||||
*/
|
||||
disableAnimation?: boolean;
|
||||
/** Handler that is called when a user performs an action on the row. */
|
||||
onRowAction?: (key: Key) => void;
|
||||
/** Handler that is called when a user performs an action on the cell. */
|
||||
onCellAction?: (key: Key) => void;
|
||||
}
|
||||
|
||||
export type UseTableProps<T = object> = Props & TableStateProps<T> & TableVariantProps;
|
||||
export type UseTableProps<T = object> = Props &
|
||||
TableStateProps<T> &
|
||||
AriaTableProps<T> &
|
||||
TableVariantProps;
|
||||
|
||||
export type ContextType<T = object> = {
|
||||
state: TableState<T>;
|
||||
slots: TableReturnType;
|
||||
collection: TableCollection<T>;
|
||||
color: TableVariantProps["color"];
|
||||
isSelectable: boolean;
|
||||
selectionMode: UseTableProps<T>["selectionMode"];
|
||||
selectionBehavior: SelectionBehavior | null;
|
||||
disabledBehavior: UseTableProps<T>["disabledBehavior"];
|
||||
disableAnimation?: UseTableProps<T>["disableAnimation"];
|
||||
showSelectionCheckboxes: UseTableProps<T>["showSelectionCheckboxes"];
|
||||
classNames?: SlotsToClasses<TableSlots>;
|
||||
selectionMode: UseTableProps["selectionMode"];
|
||||
selectionBehavior: UseTableProps["selectionBehavior"];
|
||||
disabledBehavior: UseTableProps["disabledBehavior"];
|
||||
showSelectionCheckboxes: UseTableProps["showSelectionCheckboxes"];
|
||||
onRowAction?: UseTableProps["onRowAction"];
|
||||
onCellAction?: UseTableProps["onCellAction"];
|
||||
onRowAction?: UseTableProps<T>["onRowAction"];
|
||||
onCellAction?: UseTableProps<T>["onCellAction"];
|
||||
};
|
||||
|
||||
export function useTable<T extends object>(originalProps: UseTableProps<T>) {
|
||||
@ -83,13 +101,15 @@ export function useTable<T extends object>(originalProps: UseTableProps<T>) {
|
||||
as,
|
||||
children,
|
||||
className,
|
||||
classNames,
|
||||
removeWrapper = false,
|
||||
selectionMode = "none",
|
||||
selectionBehavior = "replace",
|
||||
selectionBehavior = selectionMode === "none" ? null : "toggle",
|
||||
disabledBehavior = "selection",
|
||||
showSelectionCheckboxes = selectionMode === "multiple" && selectionBehavior !== "replace",
|
||||
disableAnimation = false,
|
||||
onRowAction,
|
||||
onCellAction,
|
||||
classNames,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
@ -105,24 +125,32 @@ export function useTable<T extends object>(originalProps: UseTableProps<T>) {
|
||||
|
||||
const {collection} = state;
|
||||
|
||||
const {gridProps} = useReactAriaTable(originalProps, state, domRef);
|
||||
const {gridProps} = useReactAriaTable<T>(originalProps, state, domRef);
|
||||
|
||||
const isSelectable = selectionMode !== "none";
|
||||
const isMultiSelectable = selectionMode === "multiple";
|
||||
|
||||
const slots = useMemo(
|
||||
() =>
|
||||
table({
|
||||
...variantProps,
|
||||
isSelectable,
|
||||
isMultiSelectable,
|
||||
}),
|
||||
[...Object.values(variantProps)],
|
||||
[...Object.values(variantProps), isSelectable, isMultiSelectable],
|
||||
);
|
||||
|
||||
const tableStyles = clsx(classNames?.table, className);
|
||||
const baseStyles = clsx(classNames?.base, className);
|
||||
|
||||
const context = useMemo<ContextType<T>>(
|
||||
() => ({
|
||||
state,
|
||||
slots,
|
||||
isSelectable,
|
||||
color: originalProps?.color,
|
||||
collection,
|
||||
classNames,
|
||||
disableAnimation,
|
||||
selectionMode,
|
||||
selectionBehavior,
|
||||
disabledBehavior,
|
||||
@ -134,6 +162,9 @@ export function useTable<T extends object>(originalProps: UseTableProps<T>) {
|
||||
slots,
|
||||
state,
|
||||
collection,
|
||||
isSelectable,
|
||||
disableAnimation,
|
||||
originalProps?.color,
|
||||
classNames,
|
||||
selectionMode,
|
||||
selectionBehavior,
|
||||
@ -144,18 +175,31 @@ export function useTable<T extends object>(originalProps: UseTableProps<T>) {
|
||||
],
|
||||
);
|
||||
|
||||
const getBaseProps: PropGetter = (props) => ({
|
||||
...props,
|
||||
className: slots.base({class: props?.className}),
|
||||
});
|
||||
const getBaseProps: PropGetter = useCallback(
|
||||
(props) => ({
|
||||
...props,
|
||||
className: slots.base({class: clsx(baseStyles, props?.className)}),
|
||||
}),
|
||||
[baseStyles, slots],
|
||||
);
|
||||
|
||||
const getTableProps: PropGetter = (props) => ({
|
||||
...mergeProps(gridProps, otherProps, props),
|
||||
...mergeProps(gridProps, filterDOMProps(otherProps, {labelable: true}), props),
|
||||
ref: domRef,
|
||||
className: slots.base({class: clsx(tableStyles, props?.className)}),
|
||||
className: slots.table({class: clsx(classNames?.table, props?.className)}),
|
||||
});
|
||||
|
||||
return {Component, children, context, getBaseProps, getTableProps};
|
||||
return {
|
||||
Component,
|
||||
children,
|
||||
state,
|
||||
collection,
|
||||
context,
|
||||
removeWrapper,
|
||||
selectionMode,
|
||||
getBaseProps,
|
||||
getTableProps,
|
||||
};
|
||||
}
|
||||
|
||||
export type UseTableReturn = ReturnType<typeof useTable>;
|
||||
|
||||
@ -1,8 +1,12 @@
|
||||
import React from "react";
|
||||
import React, {Key} from "react";
|
||||
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 {Tooltip} from "@nextui-org/tooltip";
|
||||
import {EditIcon, DeleteIcon, EyeIcon} from "@nextui-org/shared-icons";
|
||||
|
||||
import {Table, TableProps} from "../src";
|
||||
import {Table, TableHeader, TableColumn, TableBody, TableCell, TableRow, TableProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "Components/Table",
|
||||
@ -14,33 +18,352 @@ export default {
|
||||
options: ["neutral", "primary", "secondary", "success", "warning", "danger"],
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["auto", "fixed"],
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["none", "base", "sm", "md", "lg", "xl", "full"],
|
||||
},
|
||||
},
|
||||
size: {
|
||||
shadow: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["xs", "sm", "md", "lg", "xl"],
|
||||
options: ["none", "sm", "md", "lg", "xl", "2xl", "inner"],
|
||||
},
|
||||
},
|
||||
isDisabled: {
|
||||
selectionMode: {
|
||||
control: {
|
||||
type: "select",
|
||||
options: ["none", "single", "multiple"],
|
||||
},
|
||||
},
|
||||
isStriped: {
|
||||
control: {
|
||||
type: "boolean",
|
||||
},
|
||||
},
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div className="flex items-center justify-center w-screen h-screen">
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
} as ComponentMeta<typeof Table>;
|
||||
|
||||
const defaultProps = {
|
||||
...table.defaultVariants,
|
||||
className: "max-w-lg",
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof Table> = (args: TableProps) => <Table {...args} />;
|
||||
const rows = [
|
||||
{
|
||||
key: "1",
|
||||
name: "Tony Reichert",
|
||||
role: "CEO",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
key: "2",
|
||||
name: "Zoey Lang",
|
||||
role: "Technical Lead",
|
||||
status: "Paused",
|
||||
},
|
||||
{
|
||||
key: "3",
|
||||
name: "Jane Fisher",
|
||||
role: "Senior Developer",
|
||||
status: "Active",
|
||||
},
|
||||
{
|
||||
key: "4",
|
||||
name: "William Howard",
|
||||
role: "Community Manager",
|
||||
status: "Vacation",
|
||||
},
|
||||
];
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
const columns = [
|
||||
{
|
||||
key: "name",
|
||||
label: "NAME",
|
||||
},
|
||||
{
|
||||
key: "role",
|
||||
label: "ROLE",
|
||||
},
|
||||
{
|
||||
key: "status",
|
||||
label: "STATUS",
|
||||
},
|
||||
];
|
||||
|
||||
const isObject = (target: unknown) => target && typeof target === "object";
|
||||
|
||||
const getKeyValue = (obj: any, key: Key) => {
|
||||
if (!isObject(obj)) return obj;
|
||||
if (obj instanceof Array) return [...obj];
|
||||
|
||||
return obj[key];
|
||||
};
|
||||
|
||||
const StaticTemplate: ComponentStory<typeof Table> = (args: TableProps) => (
|
||||
<Table aria-label="Example static collection table" {...args}>
|
||||
<TableHeader>
|
||||
<TableColumn>NAME</TableColumn>
|
||||
<TableColumn>ROLE</TableColumn>
|
||||
<TableColumn>STATUS</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<TableRow key="1">
|
||||
<TableCell>Tony Reichert</TableCell>
|
||||
<TableCell>CEO</TableCell>
|
||||
<TableCell>Active</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="2">
|
||||
<TableCell>Zoey Lang</TableCell>
|
||||
<TableCell>Technical Lead</TableCell>
|
||||
<TableCell>Paused</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="3">
|
||||
<TableCell>Jane Fisher</TableCell>
|
||||
<TableCell>Senior Developer</TableCell>
|
||||
<TableCell>Active</TableCell>
|
||||
</TableRow>
|
||||
<TableRow key="4">
|
||||
<TableCell>William Howard</TableCell>
|
||||
<TableCell>Community Manager</TableCell>
|
||||
<TableCell>Vacation</TableCell>
|
||||
</TableRow>
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
const DynamicTemplate: ComponentStory<typeof Table> = (args: TableProps) => (
|
||||
<Table aria-label="Example table with dynamic content" {...args}>
|
||||
<TableHeader columns={columns}>
|
||||
{(column) => <TableColumn key={column.key}>{column.label}</TableColumn>}
|
||||
</TableHeader>
|
||||
<TableBody items={rows}>
|
||||
{(item) => (
|
||||
<TableRow key={item.key}>
|
||||
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
|
||||
const CustomCellTemplate: ComponentStory<typeof Table> = (args: TableProps) => {
|
||||
const columns = [
|
||||
{name: "NAME", uid: "name"},
|
||||
{name: "ROLE", uid: "role"},
|
||||
{name: "STATUS", uid: "status"},
|
||||
{name: "ACTIONS", uid: "actions"},
|
||||
];
|
||||
const users = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tony Reichert",
|
||||
role: "CEO",
|
||||
team: "Management",
|
||||
status: "active",
|
||||
age: "29",
|
||||
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026024d",
|
||||
email: "tony.reichert@example.com",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Zoey Lang",
|
||||
role: "Technical Lead",
|
||||
team: "Development",
|
||||
status: "paused",
|
||||
age: "25",
|
||||
avatar: "https://i.pravatar.cc/150?u=a042581f4e29026704d",
|
||||
email: "zoey.lang@example.com",
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Jane Fisher",
|
||||
role: "Senior Developer",
|
||||
team: "Development",
|
||||
status: "active",
|
||||
age: "22",
|
||||
avatar: "https://i.pravatar.cc/150?u=a04258114e29026702d",
|
||||
email: "jane.fisher@example.com",
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "William Howard",
|
||||
role: "Community Manager",
|
||||
team: "Marketing",
|
||||
status: "vacation",
|
||||
age: "28",
|
||||
avatar: "https://i.pravatar.cc/150?u=a048581f4e29026701d",
|
||||
email: "william.howard@example.com",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Kristen Copper",
|
||||
role: "Sales Manager",
|
||||
team: "Sales",
|
||||
status: "active",
|
||||
age: "24",
|
||||
avatar: "https://i.pravatar.cc/150?u=a092581d4ef9026700d",
|
||||
email: "kristen.cooper@example.com",
|
||||
},
|
||||
];
|
||||
|
||||
const statusColorMap: Record<string, ChipProps["color"]> = {
|
||||
active: "success",
|
||||
paused: "danger",
|
||||
vacation: "warning",
|
||||
};
|
||||
|
||||
const renderCell = React.useCallback((user, columnKey) => {
|
||||
const cellValue = user[columnKey];
|
||||
|
||||
switch (columnKey) {
|
||||
case "name":
|
||||
return (
|
||||
<User
|
||||
avatarProps={{radius: "xl", src: user.avatar}}
|
||||
description={user.email}
|
||||
name={cellValue}
|
||||
>
|
||||
{user.email}
|
||||
</User>
|
||||
);
|
||||
case "role":
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<p className="text-bold text-sm capitalize">{cellValue}</p>
|
||||
<p className="text-bold text-sm capitalize text-neutral-400">{user.team}</p>
|
||||
</div>
|
||||
);
|
||||
case "status":
|
||||
return (
|
||||
<Chip className="capitalize" color={statusColorMap[user.status]} size="sm" variant="flat">
|
||||
{cellValue}
|
||||
</Chip>
|
||||
);
|
||||
|
||||
case "actions":
|
||||
return (
|
||||
<div className="relative flex items-center gap-2">
|
||||
<Tooltip content="Details">
|
||||
<span className="text-lg text-neutral-400 cursor-pointer active:opacity-50">
|
||||
<EyeIcon />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip content="Edit user">
|
||||
<span className="text-lg text-neutral-400 cursor-pointer active:opacity-50">
|
||||
<EditIcon />
|
||||
</span>
|
||||
</Tooltip>
|
||||
<Tooltip color="danger" content="Delete user">
|
||||
<span className="text-lg text-danger cursor-pointer active:opacity-50">
|
||||
<DeleteIcon />
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return cellValue;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Table aria-label="Example table with custom cells" {...args}>
|
||||
<TableHeader columns={columns}>
|
||||
{(column) => (
|
||||
<TableColumn key={column.uid} align={column.uid === "actions" ? "center" : "start"}>
|
||||
{column.name}
|
||||
</TableColumn>
|
||||
)}
|
||||
</TableHeader>
|
||||
<TableBody items={users}>
|
||||
{(item) => (
|
||||
<TableRow key={item.id}>
|
||||
{(columnKey) => <TableCell>{renderCell(item, columnKey)}</TableCell>}
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export const Static = StaticTemplate.bind({});
|
||||
Static.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const Dynamic = DynamicTemplate.bind({});
|
||||
Dynamic.args = {
|
||||
...defaultProps,
|
||||
};
|
||||
|
||||
export const CustomCells = CustomCellTemplate.bind({});
|
||||
CustomCells.args = {
|
||||
...defaultProps,
|
||||
className: "max-w-3xl",
|
||||
};
|
||||
|
||||
export const Striped = StaticTemplate.bind({});
|
||||
Striped.args = {
|
||||
...defaultProps,
|
||||
isStriped: true,
|
||||
};
|
||||
|
||||
export const RemoveWrapper = StaticTemplate.bind({});
|
||||
RemoveWrapper.args = {
|
||||
...defaultProps,
|
||||
classNames: {
|
||||
table: "max-w-lg",
|
||||
},
|
||||
removeWrapper: true,
|
||||
};
|
||||
|
||||
export const SingleSelection = StaticTemplate.bind({});
|
||||
SingleSelection.args = {
|
||||
...defaultProps,
|
||||
selectionMode: "single",
|
||||
};
|
||||
|
||||
export const MultipleSelection = StaticTemplate.bind({});
|
||||
MultipleSelection.args = {
|
||||
...defaultProps,
|
||||
selectionMode: "multiple",
|
||||
color: "secondary",
|
||||
};
|
||||
|
||||
export const DisabledKeys = StaticTemplate.bind({});
|
||||
DisabledKeys.args = {
|
||||
...defaultProps,
|
||||
selectionMode: "multiple",
|
||||
disabledKeys: ["2"],
|
||||
color: "warning",
|
||||
};
|
||||
|
||||
export const DisallowEmptySelection = StaticTemplate.bind({});
|
||||
DisallowEmptySelection.args = {
|
||||
...defaultProps,
|
||||
disallowEmptySelection: true,
|
||||
color: "primary",
|
||||
defaultSelectedKeys: ["2"],
|
||||
selectionMode: "multiple",
|
||||
};
|
||||
|
||||
export const DisableAnimation = StaticTemplate.bind({});
|
||||
DisableAnimation.args = {
|
||||
...defaultProps,
|
||||
selectionMode: "multiple",
|
||||
color: "secondary",
|
||||
disableAnimation: true,
|
||||
};
|
||||
|
||||
@ -78,22 +78,6 @@ export function useUser(props: UseUserProps) {
|
||||
|
||||
const baseStyles = clsx(classNames?.base, className);
|
||||
|
||||
const buttonStyles = useMemo(() => {
|
||||
if (as !== "button") return "";
|
||||
|
||||
// reset button classNames
|
||||
return [
|
||||
"p-0",
|
||||
"m-0",
|
||||
"bg-none",
|
||||
"radius-none",
|
||||
"appearance-none",
|
||||
"outline-none",
|
||||
"border-none",
|
||||
"cursor-pointer",
|
||||
];
|
||||
}, [as]);
|
||||
|
||||
const getUserProps = useCallback<PropGetter>(
|
||||
() => ({
|
||||
ref: domRef,
|
||||
@ -101,7 +85,7 @@ export function useUser(props: UseUserProps) {
|
||||
"data-focus-visible": dataAttr(isFocusVisible),
|
||||
"data-focused": dataAttr(isFocused),
|
||||
className: slots.base({
|
||||
class: clsx(baseStyles, buttonStyles),
|
||||
class: baseStyles,
|
||||
}),
|
||||
...mergeProps(otherProps, canBeFocused ? focusProps : {}),
|
||||
}),
|
||||
|
||||
@ -64,7 +64,9 @@
|
||||
"@nextui-org/dropdown": "workspace:*",
|
||||
"@nextui-org/image": "workspace:*",
|
||||
"@nextui-org/modal": "workspace:*",
|
||||
"@nextui-org/navbar": "workspace:*"
|
||||
"@nextui-org/navbar": "workspace:*",
|
||||
"@nextui-org/table": "workspace:*",
|
||||
"@nextui-org/spacer": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
|
||||
@ -24,3 +24,5 @@ export * from "@nextui-org/dropdown";
|
||||
export * from "@nextui-org/image";
|
||||
export * from "@nextui-org/modal";
|
||||
export * from "@nextui-org/navbar";
|
||||
export * from "@nextui-org/table";
|
||||
export * from "@nextui-org/spacer";
|
||||
|
||||
@ -53,7 +53,7 @@
|
||||
"lodash.foreach": "^4.5.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.isempty": "^4.4.0",
|
||||
"tailwind-variants": "^0.1.2",
|
||||
"tailwind-variants": "^0.1.3",
|
||||
"tailwindcss": "^3.2.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -31,3 +31,4 @@ export * from "./image";
|
||||
export * from "./modal";
|
||||
export * from "./navbar";
|
||||
export * from "./table";
|
||||
export * from "./spacer";
|
||||
|
||||
29
packages/core/theme/src/components/spacer.ts
Normal file
29
packages/core/theme/src/components/spacer.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type {VariantProps} from "tailwind-variants";
|
||||
|
||||
import {tv} from "tailwind-variants";
|
||||
|
||||
/**
|
||||
* Spacer wrapper **Tailwind Variants** component
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* const styles = spacer()
|
||||
*
|
||||
* <span className={styles} />
|
||||
*/
|
||||
const spacer = tv({
|
||||
base: "w-px h-px inline-block",
|
||||
variants: {
|
||||
isInline: {
|
||||
true: "inline-block",
|
||||
false: "block",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
isInline: false,
|
||||
},
|
||||
});
|
||||
|
||||
export type SpacerVariantProps = VariantProps<typeof spacer>;
|
||||
|
||||
export {spacer};
|
||||
@ -2,6 +2,15 @@ import type {VariantProps} from "tailwind-variants";
|
||||
|
||||
import {tv} from "tailwind-variants";
|
||||
|
||||
const focusRing = [
|
||||
"data-[focus-visible=true]:outline-none",
|
||||
"data-[focus-visible=true]:ring-2",
|
||||
"data-[focus-visible=true]:ring-primary",
|
||||
"data-[focus-visible=true]:ring-offset-2",
|
||||
"data-[focus-visible=true]:ring-offset-background",
|
||||
"data-[focus-visible=true]:dark:ring-offset-background-dark",
|
||||
];
|
||||
|
||||
/**
|
||||
* Table **Tailwind Variants** component
|
||||
*
|
||||
@ -39,17 +48,169 @@ import {tv} from "tailwind-variants";
|
||||
*/
|
||||
const table = tv({
|
||||
slots: {
|
||||
base: "",
|
||||
table: "group",
|
||||
base: "p-4 border border-neutral-100",
|
||||
table: "",
|
||||
thead: "",
|
||||
tbody: "",
|
||||
tr: "",
|
||||
th: "",
|
||||
td: "",
|
||||
tr: ["group", "outline-none", ...focusRing],
|
||||
th: [
|
||||
"px-3",
|
||||
"h-10",
|
||||
"text-left",
|
||||
"align-middle",
|
||||
"bg-neutral-100",
|
||||
"text-neutral-500",
|
||||
"text-xs",
|
||||
"font-semibold",
|
||||
"first:rounded-l-lg",
|
||||
"last:rounded-r-lg",
|
||||
"outline-none",
|
||||
...focusRing,
|
||||
],
|
||||
td: [
|
||||
"py-2",
|
||||
"px-3",
|
||||
"relative",
|
||||
"align-middle",
|
||||
"whitespace-normal",
|
||||
"text-base",
|
||||
"font-normal",
|
||||
"outline-none",
|
||||
...focusRing,
|
||||
// before content for selection
|
||||
"before:content-['']",
|
||||
"before:absolute",
|
||||
"before:-z-[1]",
|
||||
"before:inset-0",
|
||||
"before:opacity-0",
|
||||
"data-[selected=true]:before:opacity-100",
|
||||
// disabled
|
||||
"group-data-[disabled=true]:text-neutral-300",
|
||||
],
|
||||
tfoot: "",
|
||||
},
|
||||
variants: {},
|
||||
defaultVariants: {},
|
||||
variants: {
|
||||
color: {
|
||||
neutral: {
|
||||
td:
|
||||
"before:bg-neutral-200 dark:before:bg-neutral-100 data-[selected=true]:text-neutral-foreground",
|
||||
},
|
||||
primary: {
|
||||
td: "before:bg-primary-50 data-[selected=true]:text-primary",
|
||||
},
|
||||
secondary: {
|
||||
td: "before:bg-secondary-100 data-[selected=true]:text-secondary",
|
||||
},
|
||||
success: {
|
||||
td: "before:bg-success-50 data-[selected=true]:text-success",
|
||||
},
|
||||
warning: {
|
||||
td: "before:bg-warning-50 data-[selected=true]:text-warning",
|
||||
},
|
||||
danger: {
|
||||
td: "before:bg-danger-50 data-[selected=true]:text-danger",
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
auto: {
|
||||
table: "table-auto",
|
||||
},
|
||||
fixed: {
|
||||
table: "table-fixed",
|
||||
},
|
||||
},
|
||||
radius: {
|
||||
none: {
|
||||
base: "rounded-none",
|
||||
},
|
||||
base: {
|
||||
base: "rounded",
|
||||
},
|
||||
sm: {
|
||||
base: "rounded-sm",
|
||||
},
|
||||
md: {
|
||||
base: "rounded-md",
|
||||
},
|
||||
lg: {
|
||||
base: "rounded-lg",
|
||||
},
|
||||
xl: {
|
||||
base: "rounded-xl",
|
||||
},
|
||||
"2xl": {
|
||||
base: "rounded-2xl",
|
||||
},
|
||||
},
|
||||
shadow: {
|
||||
none: {
|
||||
base: "shadow-none",
|
||||
},
|
||||
sm: {
|
||||
base: "shadow-sm",
|
||||
},
|
||||
md: {
|
||||
base: "shadow-md",
|
||||
},
|
||||
lg: {
|
||||
base: "shadow-lg",
|
||||
},
|
||||
xl: {
|
||||
base: "shadow-xl",
|
||||
},
|
||||
"2xl": {
|
||||
base: "shadow-2xl",
|
||||
},
|
||||
inner: {
|
||||
base: "shadow-inner",
|
||||
},
|
||||
},
|
||||
isStriped: {
|
||||
true: {
|
||||
td: [
|
||||
"group-data-[odd=true]:before:bg-neutral-100",
|
||||
"group-data-[odd=true]:before:opacity-100",
|
||||
],
|
||||
},
|
||||
},
|
||||
isSelectable: {
|
||||
true: {
|
||||
tr: "cursor-default",
|
||||
td: ["group-data-[hover=true]:before:opacity-70"],
|
||||
},
|
||||
},
|
||||
isMultiSelectable: {
|
||||
true: {
|
||||
td: [
|
||||
// first
|
||||
"group-data-[first=true]:first:before:rounded-tl-lg",
|
||||
"group-data-[first=true]:last:before:rounded-tr-lg",
|
||||
// middle
|
||||
"group-data-[middle=true]:before:rounded-none",
|
||||
// last
|
||||
"group-data-[last=true]:first:before:rounded-bl-lg",
|
||||
"group-data-[last=true]:last:before:rounded-br-lg",
|
||||
],
|
||||
},
|
||||
false: {
|
||||
td: ["first:before:rounded-l-lg", "last:before:rounded-r-lg"],
|
||||
},
|
||||
},
|
||||
fullWidth: {
|
||||
true: {
|
||||
base: "w-full",
|
||||
table: "w-full",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
layout: "auto",
|
||||
shadow: "lg",
|
||||
radius: "xl",
|
||||
color: "neutral",
|
||||
isStriped: false,
|
||||
fullWidth: true,
|
||||
},
|
||||
});
|
||||
|
||||
export type TableVariantProps = VariantProps<typeof table>;
|
||||
|
||||
@ -28,7 +28,7 @@ const user = tv({
|
||||
],
|
||||
wrapper: "inline-flex flex-col items-start",
|
||||
name: "text-sm text-foreground dark:text-foreground-dark",
|
||||
description: "text-xs text-neutral-500",
|
||||
description: "text-xs text-neutral-400",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@ -55,19 +55,19 @@ h3 {
|
||||
color: #bcbcbc;
|
||||
}
|
||||
|
||||
.dark tbody td {
|
||||
.dark #docs-root tbody td {
|
||||
background: #161616 !important;
|
||||
color: #bcbcbc !important;
|
||||
}
|
||||
|
||||
.dark tbody tr:first-child td:first-child {
|
||||
.dark #docs-root tbody tr:first-child td:first-child {
|
||||
border-top-left-radius: 0 !important;
|
||||
}
|
||||
|
||||
.dark tbody tr:first-child td:last-child {
|
||||
.dark #docs-root tbody tr:first-child td:last-child {
|
||||
border-top-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
.dark tbody tr:not(:first-child) {
|
||||
.dark #docs-root tbody tr:not(:first-child) {
|
||||
border-top: 1px solid #292929 !important;
|
||||
}
|
||||
50
packages/utilities/shared-icons/src/delete.tsx
Normal file
50
packages/utilities/shared-icons/src/delete.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import {IconSvgProps} from "./types";
|
||||
|
||||
export const DeleteIcon = (props: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 20 20"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M17.5 4.98332C14.725 4.70832 11.9333 4.56665 9.15 4.56665C7.5 4.56665 5.85 4.64998 4.2 4.81665L2.5 4.98332"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<path
|
||||
d="M7.08331 4.14169L7.26665 3.05002C7.39998 2.25835 7.49998 1.66669 8.90831 1.66669H11.0916C12.5 1.66669 12.6083 2.29169 12.7333 3.05835L12.9166 4.14169"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<path
|
||||
d="M15.7084 7.61664L15.1667 16.0083C15.075 17.3166 15 18.3333 12.675 18.3333H7.32502C5.00002 18.3333 4.92502 17.3166 4.83335 16.0083L4.29169 7.61664"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<path
|
||||
d="M8.60834 13.75H11.3833"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<path
|
||||
d="M7.91669 10.4167H12.0834"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
39
packages/utilities/shared-icons/src/edit.tsx
Normal file
39
packages/utilities/shared-icons/src/edit.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import {IconSvgProps} from "./types";
|
||||
|
||||
export const EditIcon = (props: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 20 20"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M11.05 3.00002L4.20835 10.2417C3.95002 10.5167 3.70002 11.0584 3.65002 11.4334L3.34169 14.1334C3.23335 15.1084 3.93335 15.775 4.90002 15.6084L7.58335 15.15C7.95835 15.0834 8.48335 14.8084 8.74168 14.525L15.5834 7.28335C16.7667 6.03335 17.3 4.60835 15.4583 2.86668C13.625 1.14168 12.2334 1.75002 11.05 3.00002Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit={10}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<path
|
||||
d="M9.90833 4.20831C10.2667 6.50831 12.1333 8.26665 14.45 8.49998"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit={10}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<path
|
||||
d="M2.5 18.3333H17.5"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeMiterlimit={10}
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
29
packages/utilities/shared-icons/src/eye.tsx
Normal file
29
packages/utilities/shared-icons/src/eye.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import {IconSvgProps} from "./types";
|
||||
|
||||
export const EyeIcon = (props: IconSvgProps) => (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
focusable="false"
|
||||
height="1em"
|
||||
role="presentation"
|
||||
viewBox="0 0 20 20"
|
||||
width="1em"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
d="M12.9833 10C12.9833 11.65 11.65 12.9833 10 12.9833C8.35 12.9833 7.01666 11.65 7.01666 10C7.01666 8.35 8.35 7.01666 10 7.01666C11.65 7.01666 12.9833 8.35 12.9833 10Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
<path
|
||||
d="M9.99999 16.8916C12.9417 16.8916 15.6833 15.1583 17.5917 12.1583C18.3417 10.9833 18.3417 9.00831 17.5917 7.83331C15.6833 4.83331 12.9417 3.09998 9.99999 3.09998C7.05833 3.09998 4.31666 4.83331 2.40833 7.83331C1.65833 9.00831 1.65833 10.9833 2.40833 12.1583C4.31666 15.1583 7.05833 16.8916 9.99999 16.8916Z"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={1.5}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
@ -24,3 +24,6 @@ export * from "./eye-slash-filled";
|
||||
export * from "./search";
|
||||
export * from "./bulk";
|
||||
export * from "./lock-filled";
|
||||
export * from "./edit";
|
||||
export * from "./delete";
|
||||
export * from "./eye";
|
||||
|
||||
509
pnpm-lock.yaml
generated
509
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user