mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
feat(components): prop getter type implemented to reduced the size of d.ts files
This commit is contained in:
parent
dc0f6a4d55
commit
f502080e69
@ -10,20 +10,18 @@ export interface AvatarGroupProps extends Omit<UseAvatarGroupProps, "ref"> {}
|
||||
const AvatarGroup = forwardRef<AvatarGroupProps, "div">((props, ref) => {
|
||||
const {
|
||||
Component,
|
||||
domRef,
|
||||
clones,
|
||||
context,
|
||||
styles,
|
||||
remainingCount,
|
||||
renderCount = (count) => <Avatar className="hover:-translate-x-0" name={`+${count}`} />,
|
||||
...otherProps
|
||||
getAvatarGroupProps,
|
||||
} = useAvatarGroup({
|
||||
ref,
|
||||
...props,
|
||||
});
|
||||
|
||||
return (
|
||||
<Component ref={domRef} className={styles} role="group" {...otherProps}>
|
||||
<Component {...getAvatarGroupProps()}>
|
||||
<AvatarGroupProvider value={context}>
|
||||
{clones}
|
||||
{remainingCount > 0 && renderCount(remainingCount)}
|
||||
|
||||
@ -1,38 +1,18 @@
|
||||
import type {ReactNode} from "react";
|
||||
|
||||
import {avatarGroup} from "@nextui-org/theme";
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {ReactRef, clsx, getValidChildren, compact} from "@nextui-org/shared-utils";
|
||||
import {cloneElement, useMemo} from "react";
|
||||
|
||||
import {AvatarProps} from "./index";
|
||||
|
||||
export interface UseAvatarGroupProps extends HTMLNextUIProps<"div"> {
|
||||
interface Props extends HTMLNextUIProps<"div"> {
|
||||
/**
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref?: ReactRef<HTMLDivElement | null>;
|
||||
/**
|
||||
* The size of the avatars
|
||||
*/
|
||||
size?: AvatarProps["size"];
|
||||
/**
|
||||
* The color of the avatars
|
||||
*/
|
||||
color?: AvatarProps["color"];
|
||||
/**
|
||||
* The radius of the avatars
|
||||
*/
|
||||
radius?: AvatarProps["radius"];
|
||||
/**
|
||||
* Whether the avatars are bordered
|
||||
*/
|
||||
isBordered?: AvatarProps["isBordered"];
|
||||
/**
|
||||
* Whether the avatars are disabled
|
||||
*/
|
||||
isDisabled?: AvatarProps["isDisabled"];
|
||||
/**
|
||||
* Whether the avatars should be displayed in a grid
|
||||
*/
|
||||
@ -52,6 +32,9 @@ export interface UseAvatarGroupProps extends HTMLNextUIProps<"div"> {
|
||||
renderCount?: (count: number) => ReactNode;
|
||||
}
|
||||
|
||||
export type UseAvatarGroupProps = Props &
|
||||
Pick<AvatarProps, "size" | "color" | "radius" | "isDisabled" | "isBordered">;
|
||||
|
||||
export type ContextType = {
|
||||
size?: AvatarProps["size"];
|
||||
color?: AvatarProps["color"];
|
||||
@ -74,6 +57,7 @@ export function useAvatarGroup(props: UseAvatarGroupProps) {
|
||||
isBordered,
|
||||
isDisabled,
|
||||
isGrid,
|
||||
renderCount,
|
||||
className,
|
||||
...otherProps
|
||||
} = props;
|
||||
@ -82,7 +66,7 @@ export function useAvatarGroup(props: UseAvatarGroupProps) {
|
||||
|
||||
const Component = as || "div";
|
||||
|
||||
const context = useMemo(
|
||||
const context = useMemo<ContextType>(
|
||||
() => ({
|
||||
size,
|
||||
color,
|
||||
@ -114,14 +98,22 @@ export function useAvatarGroup(props: UseAvatarGroupProps) {
|
||||
return cloneElement(child, compact(childProps));
|
||||
});
|
||||
|
||||
const getAvatarGroupProps: PropGetter = () => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: styles,
|
||||
role: "group",
|
||||
...otherProps,
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
Component,
|
||||
context,
|
||||
domRef,
|
||||
styles,
|
||||
remainingCount,
|
||||
clones,
|
||||
...otherProps,
|
||||
renderCount,
|
||||
getAvatarGroupProps,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import type {BadgeSlots, BadgeVariantProps, SlotsToClasses} from "@nextui-org/th
|
||||
import type {ReactNode} from "react";
|
||||
|
||||
import {badge} from "@nextui-org/theme";
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useMemo} from "react";
|
||||
@ -59,7 +59,7 @@ export function useBadge(originalProps: UseBadgeProps) {
|
||||
[...Object.values(variantProps), isOneChar, isDot],
|
||||
);
|
||||
|
||||
const getBadgeProps = () => {
|
||||
const getBadgeProps: PropGetter = () => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: slots.badge({class: baseStyles}),
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import type {ButtonProps} from "./index";
|
||||
import type {ReactRef} from "@nextui-org/shared-utils";
|
||||
import type {ButtonGroupVariantProps} from "@nextui-org/theme";
|
||||
import type {AriaButtonProps} from "@react-types/button";
|
||||
|
||||
import {buttonGroup} from "@nextui-org/theme";
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, PropGetter, mapPropsVariants} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {useMemo, useCallback} from "react";
|
||||
interface Props extends HTMLNextUIProps<"div">, ButtonGroupVariantProps {
|
||||
@ -31,7 +30,7 @@ export type ContextType = {
|
||||
};
|
||||
|
||||
export type UseButtonGroupProps = Props &
|
||||
Omit<ButtonProps, "ref" | "fullWidth" | keyof AriaButtonProps>;
|
||||
Pick<ButtonProps, "size" | "color" | "radius" | "variant" | "disableAnimation" | "disableRipple">;
|
||||
|
||||
export function useButtonGroup(originalProps: UseButtonGroupProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, buttonGroup.variantKeys);
|
||||
@ -87,7 +86,7 @@ export function useButtonGroup(originalProps: UseButtonGroupProps) {
|
||||
],
|
||||
);
|
||||
|
||||
const getButtonGroupProps = useCallback(
|
||||
const getButtonGroupProps: PropGetter = useCallback(
|
||||
() => ({
|
||||
role: "group",
|
||||
...otherProps,
|
||||
|
||||
@ -2,7 +2,7 @@ import type {ButtonVariantProps} from "@nextui-org/theme";
|
||||
import type {AriaButtonProps} from "@react-types/button";
|
||||
import type {PressEvent} from "@react-types/shared";
|
||||
import type {ReactRef} from "@nextui-org/shared-utils";
|
||||
import type {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
|
||||
import type {ReactNode} from "react";
|
||||
|
||||
import {MouseEventHandler, useCallback} from "react";
|
||||
@ -145,7 +145,7 @@ export function useButton(props: UseButtonProps) {
|
||||
domRef,
|
||||
);
|
||||
|
||||
const getButtonProps = useCallback(
|
||||
const getButtonProps: PropGetter = useCallback(
|
||||
() => mergeProps(buttonAriaProps, focusProps, otherProps),
|
||||
[buttonAriaProps, focusProps, otherProps],
|
||||
);
|
||||
|
||||
@ -5,7 +5,7 @@ import {button, buttonGroup} from "@nextui-org/theme";
|
||||
import {Button, ButtonGroup, ButtonGroupProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "General/ButtonGroup",
|
||||
title: "Actions/ButtonGroup",
|
||||
component: ButtonGroup,
|
||||
argTypes: {
|
||||
variant: {
|
||||
|
||||
@ -7,7 +7,7 @@ import {Spinner} from "@nextui-org/spinner";
|
||||
import {Button, ButtonProps} from "../src";
|
||||
|
||||
export default {
|
||||
title: "General/Button",
|
||||
title: "Actions/Button",
|
||||
component: Button,
|
||||
argTypes: {
|
||||
variant: {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {CheckboxGroupSlots, SlotsToClasses} from "@nextui-org/theme";
|
||||
import type {AriaCheckboxGroupProps, AriaCheckboxProps} from "@react-types/checkbox";
|
||||
import type {AriaCheckboxGroupProps} from "@react-types/checkbox";
|
||||
import type {Orientation} from "@react-types/shared";
|
||||
import type {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
|
||||
import type {ReactRef} from "@nextui-org/shared-utils";
|
||||
|
||||
import {useMemo} from "react";
|
||||
@ -42,7 +42,11 @@ interface Props extends HTMLNextUIProps<"div", AriaCheckboxGroupProps> {
|
||||
styles?: SlotsToClasses<CheckboxGroupSlots>;
|
||||
}
|
||||
|
||||
export type UseCheckboxGroupProps = Props & Omit<CheckboxProps, "ref" | keyof AriaCheckboxProps>;
|
||||
export type UseCheckboxGroupProps = Props &
|
||||
Pick<
|
||||
CheckboxProps,
|
||||
"color" | "size" | "radius" | "lineThrough" | "isDisabled" | "disableAnimation"
|
||||
>;
|
||||
|
||||
export type ContextType = {
|
||||
groupState: CheckboxGroupState;
|
||||
@ -86,21 +90,24 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps) {
|
||||
groupState,
|
||||
);
|
||||
|
||||
const context: ContextType = {
|
||||
size,
|
||||
color,
|
||||
radius,
|
||||
lineThrough,
|
||||
isDisabled,
|
||||
disableAnimation,
|
||||
groupState,
|
||||
};
|
||||
const context = useMemo<ContextType>(
|
||||
() => ({
|
||||
size,
|
||||
color,
|
||||
radius,
|
||||
lineThrough,
|
||||
isDisabled,
|
||||
disableAnimation,
|
||||
groupState,
|
||||
}),
|
||||
[size, color, radius, lineThrough, isDisabled, disableAnimation, groupState],
|
||||
);
|
||||
|
||||
const slots = useMemo(() => checkboxGroup(), []);
|
||||
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const getGroupProps = () => {
|
||||
const getGroupProps: PropGetter = () => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: slots.base({class: baseStyles}),
|
||||
@ -108,14 +115,14 @@ export function useCheckboxGroup(props: UseCheckboxGroupProps) {
|
||||
};
|
||||
};
|
||||
|
||||
const getLabelProps = () => {
|
||||
const getLabelProps: PropGetter = () => {
|
||||
return {
|
||||
className: slots.label({class: styles?.label}),
|
||||
...labelProps,
|
||||
};
|
||||
};
|
||||
|
||||
const getWrapperProps = () => {
|
||||
const getWrapperProps: PropGetter = () => {
|
||||
return {
|
||||
className: slots.wrapper({class: styles?.wrapper}),
|
||||
role: "presentation",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type {CheckboxVariantProps, CheckboxSlots, SlotsToClasses} from "@nextui-org/theme";
|
||||
import type {AriaCheckboxProps} from "@react-types/checkbox";
|
||||
import type {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
|
||||
|
||||
import {ReactNode, Ref, useCallback} from "react";
|
||||
import {useMemo, useRef} from "react";
|
||||
@ -161,7 +161,7 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const getBaseProps = () => {
|
||||
const getBaseProps: PropGetter = () => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: slots.base({class: baseStyles}),
|
||||
@ -172,7 +172,7 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
};
|
||||
};
|
||||
|
||||
const getWrapperProps = () => {
|
||||
const getWrapperProps: PropGetter = () => {
|
||||
return {
|
||||
"data-hover": dataAttr(isHovered),
|
||||
"data-checked": dataAttr(inputProps.checked),
|
||||
@ -187,14 +187,14 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
};
|
||||
};
|
||||
|
||||
const getInputProps = () => {
|
||||
const getInputProps: PropGetter = () => {
|
||||
return {
|
||||
ref: inputRef,
|
||||
...mergeProps(inputProps, focusProps),
|
||||
};
|
||||
};
|
||||
|
||||
const getLabelProps = useCallback(
|
||||
const getLabelProps: PropGetter = useCallback(
|
||||
() => ({
|
||||
"data-disabled": dataAttr(isDisabled),
|
||||
"data-checked": dataAttr(inputProps.checked),
|
||||
@ -204,7 +204,7 @@ export function useCheckbox(props: UseCheckboxProps) {
|
||||
[slots, isDisabled, inputProps.checked, otherProps.validationState],
|
||||
);
|
||||
|
||||
const getIconProps = useCallback(
|
||||
const getIconProps: PropGetter = useCallback(
|
||||
() => ({
|
||||
"data-checked": dataAttr(inputProps.checked),
|
||||
isSelected: inputProps.checked,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
import {render} from "@testing-library/react";
|
||||
import {Avatar} from "@nextui-org/avatar";
|
||||
import {act} from "@testing-library/react-hooks";
|
||||
|
||||
import {Chip} from "../src";
|
||||
|
||||
@ -52,7 +53,9 @@ describe("Chip", () => {
|
||||
const onClose = jest.fn();
|
||||
const {getByRole} = render(<Chip onClose={onClose} />);
|
||||
|
||||
getByRole("button").click();
|
||||
act(() => {
|
||||
getByRole("button").click();
|
||||
});
|
||||
|
||||
expect(onClose).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@ -5,7 +5,8 @@ import {useMemo} from "react";
|
||||
|
||||
import {UseChipProps, useChip} from "./use-chip";
|
||||
|
||||
export interface ChipProps extends Omit<UseChipProps, "ref"> {}
|
||||
export interface ChipProps
|
||||
extends Omit<UseChipProps, "ref" | "isOneChar" | "isCloseButtonFocusVisible"> {}
|
||||
|
||||
const Chip = forwardRef<ChipProps, "div">((props, ref) => {
|
||||
const {
|
||||
@ -13,7 +14,7 @@ const Chip = forwardRef<ChipProps, "div">((props, ref) => {
|
||||
children,
|
||||
slots,
|
||||
styles,
|
||||
variant,
|
||||
isDot,
|
||||
isCloseable,
|
||||
leftContent,
|
||||
rightContent,
|
||||
@ -25,12 +26,12 @@ const Chip = forwardRef<ChipProps, "div">((props, ref) => {
|
||||
});
|
||||
|
||||
const left = useMemo(() => {
|
||||
if (variant === "dot" && !leftContent) {
|
||||
if (isDot && !leftContent) {
|
||||
return <span className={slots.dot({class: styles?.dot})} />;
|
||||
}
|
||||
|
||||
return leftContent;
|
||||
}, [slots, leftContent, variant]);
|
||||
}, [slots, leftContent, isDot]);
|
||||
|
||||
const right = useMemo(() => {
|
||||
if (isCloseable) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {ChipVariantProps, ChipSlots, SlotsToClasses} from "@nextui-org/theme";
|
||||
import type {ReactNode} from "react";
|
||||
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {usePress} from "@react-aria/interactions";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
@ -74,6 +74,7 @@ export function useChip(originalProps: UseChipProps) {
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const isCloseable = !!onClose;
|
||||
const isDotVariant = originalProps.variant === "dot";
|
||||
|
||||
const {focusProps: closeFocusProps, isFocusVisible: isCloseButtonFocusVisible} = useFocusRing();
|
||||
|
||||
@ -97,7 +98,7 @@ export function useChip(originalProps: UseChipProps) {
|
||||
onPress: onClose,
|
||||
});
|
||||
|
||||
const getChipProps = () => {
|
||||
const getChipProps: PropGetter = () => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: slots.base({class: baseStyles}),
|
||||
@ -105,7 +106,7 @@ export function useChip(originalProps: UseChipProps) {
|
||||
};
|
||||
};
|
||||
|
||||
const getCloseButtonProps = () => {
|
||||
const getCloseButtonProps: PropGetter = () => {
|
||||
return {
|
||||
role: "button",
|
||||
tabIndex: 0,
|
||||
@ -136,10 +137,10 @@ export function useChip(originalProps: UseChipProps) {
|
||||
children,
|
||||
slots,
|
||||
styles,
|
||||
variant: originalProps.variant,
|
||||
isDot: isDotVariant,
|
||||
isCloseable,
|
||||
leftContent: getAvatarClone(avatar) || getContentClone(leftContent),
|
||||
rightContent: getContentClone(rightContent),
|
||||
isCloseable,
|
||||
getCloseButtonProps,
|
||||
getChipProps,
|
||||
};
|
||||
|
||||
@ -6,13 +6,9 @@ import {useCode, UseCodeProps} from "./use-code";
|
||||
export interface CodeProps extends Omit<UseCodeProps, "ref"> {}
|
||||
|
||||
const Code = forwardRef<CodeProps, "code">((props, ref) => {
|
||||
const {Component, domRef, children, styles, ...otherProps} = useCode({ref, ...props});
|
||||
const {Component, children, getCodeProps} = useCode({ref, ...props});
|
||||
|
||||
return (
|
||||
<Component ref={domRef} className={styles} {...otherProps}>
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
return <Component {...getCodeProps()}>{children}</Component>;
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {CodeVariantProps} from "@nextui-org/theme";
|
||||
|
||||
import {code} from "@nextui-org/theme";
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, PropGetter, mapPropsVariants} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useMemo} from "react";
|
||||
@ -16,7 +16,7 @@ export interface UseCodeProps extends HTMLNextUIProps<"code">, CodeVariantProps
|
||||
export function useCode(originalProps: UseCodeProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, code.variantKeys);
|
||||
|
||||
const {ref, as, className, ...otherProps} = props;
|
||||
const {ref, as, children, className, ...otherProps} = props;
|
||||
|
||||
const Component = as || "code";
|
||||
|
||||
@ -31,7 +31,15 @@ export function useCode(originalProps: UseCodeProps) {
|
||||
[...Object.values(variantProps), className],
|
||||
);
|
||||
|
||||
return {Component, as, styles, domRef, ...otherProps};
|
||||
const getCodeProps: PropGetter = () => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: styles,
|
||||
...otherProps,
|
||||
};
|
||||
};
|
||||
|
||||
return {Component, children, getCodeProps};
|
||||
}
|
||||
|
||||
export type UseCodeReturn = ReturnType<typeof useCode>;
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {__DEV__} from "@nextui-org/shared-utils";
|
||||
|
||||
@ -10,17 +9,14 @@ export interface LinkProps extends Omit<UseLinkProps, "ref"> {}
|
||||
const Link = forwardRef<LinkProps, "a">((props, ref) => {
|
||||
const {
|
||||
Component,
|
||||
domRef,
|
||||
styles,
|
||||
children,
|
||||
showAnchorIcon,
|
||||
anchorIcon = <LinkIcon />,
|
||||
linkProps,
|
||||
...otherProps
|
||||
getLinkProps,
|
||||
} = useLink({...props, ref});
|
||||
|
||||
return (
|
||||
<Component ref={domRef} className={styles} {...mergeProps(linkProps, otherProps)}>
|
||||
<Component {...getLinkProps()}>
|
||||
<>
|
||||
{children}
|
||||
{showAnchorIcon && anchorIcon}
|
||||
|
||||
@ -3,7 +3,7 @@ import type {LinkVariantProps} from "@nextui-org/theme";
|
||||
|
||||
import {link} from "@nextui-org/theme";
|
||||
import {useLink as useAriaLink} from "@react-aria/link";
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useMemo} from "react";
|
||||
@ -35,7 +35,16 @@ export type UseLinkProps = Props & AriaLinkProps;
|
||||
export function useLink(originalProps: UseLinkProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, link.variantKeys);
|
||||
|
||||
const {ref, as, isExternal = false, showAnchorIcon = false, className, ...otherProps} = props;
|
||||
const {
|
||||
ref,
|
||||
as,
|
||||
children,
|
||||
anchorIcon,
|
||||
isExternal = false,
|
||||
showAnchorIcon = false,
|
||||
className,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const Component = as || "a";
|
||||
|
||||
@ -61,7 +70,16 @@ export function useLink(originalProps: UseLinkProps) {
|
||||
[...Object.values(variantProps), className],
|
||||
);
|
||||
|
||||
return {Component, as, styles, domRef, linkProps, showAnchorIcon, ...otherProps};
|
||||
const getLinkProps: PropGetter = () => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: styles,
|
||||
...linkProps,
|
||||
...otherProps,
|
||||
};
|
||||
};
|
||||
|
||||
return {Component, children, anchorIcon, linkProps, showAnchorIcon, getLinkProps};
|
||||
}
|
||||
|
||||
export type UseLinkReturn = ReturnType<typeof useLink>;
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import * as React from "react";
|
||||
import {render, act} from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import {Snippet} from "../src";
|
||||
|
||||
@ -70,10 +69,10 @@ describe("Snippet - Clipboard", () => {
|
||||
|
||||
let code = "npm install @nextui-org/react";
|
||||
|
||||
act(() => {
|
||||
const wrapper = render(<Snippet data-testid="code-test">{code}</Snippet>);
|
||||
const wrapper = render(<Snippet data-testid="code-test">{code}</Snippet>);
|
||||
|
||||
userEvent.click(wrapper.getByRole("button"));
|
||||
act(() => {
|
||||
wrapper.getByRole("button").click();
|
||||
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(code);
|
||||
});
|
||||
});
|
||||
|
||||
@ -40,7 +40,6 @@
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/shared-css": "workspace:*",
|
||||
"@nextui-org/dom-utils": "workspace:*",
|
||||
"@nextui-org/use-clipboard": "workspace:*",
|
||||
"@nextui-org/tooltip": "workspace:*",
|
||||
|
||||
@ -1,137 +0,0 @@
|
||||
import {styled} from "@nextui-org/system";
|
||||
import {cssFocusVisible} from "@nextui-org/shared-css";
|
||||
|
||||
export const StyledSnippet = styled("div", {
|
||||
position: "relative",
|
||||
width: "initial",
|
||||
maxWidth: "100%",
|
||||
padding: "calc($space$lg * 0.75) $space$lg",
|
||||
br: "$lg",
|
||||
bg: "$background",
|
||||
variants: {
|
||||
color: {
|
||||
default: {
|
||||
$$snippetBorderColor: "$border",
|
||||
$$snippetBgColor: "$background",
|
||||
color: "$text",
|
||||
},
|
||||
primary: {
|
||||
$$snippetBorderColor: "$border",
|
||||
$$snippetBgColor: "$primary",
|
||||
color: "$text",
|
||||
},
|
||||
success: {
|
||||
$$snippetBorderColor: "$success",
|
||||
$$snippetBgColor: "$background",
|
||||
color: "$success",
|
||||
},
|
||||
warning: {
|
||||
$$snippetBorderColor: "$warning",
|
||||
$$snippetBgColor: "$background",
|
||||
color: "$warning",
|
||||
},
|
||||
error: {
|
||||
$$snippetBorderColor: "$error",
|
||||
$$snippetBgColor: "$background",
|
||||
color: "$error",
|
||||
},
|
||||
secondary: {
|
||||
$$snippetBorderColor: "$secondary",
|
||||
$$snippetBgColor: "$background",
|
||||
color: "$secondary",
|
||||
},
|
||||
invert: {
|
||||
$$snippetBorderColor: "$foreground",
|
||||
$$snippetBgColor: "$foreground",
|
||||
color: "$background",
|
||||
},
|
||||
},
|
||||
borderWeight: {
|
||||
light: {
|
||||
$$borderWeight: "$light",
|
||||
},
|
||||
normal: {
|
||||
$$borderWeight: "$normal",
|
||||
},
|
||||
bold: {
|
||||
$$borderWeight: "$bold",
|
||||
},
|
||||
extrabold: {
|
||||
$$borderWeight: "$extrabold",
|
||||
},
|
||||
black: {
|
||||
$$borderWeight: "$black",
|
||||
},
|
||||
},
|
||||
bordered: {
|
||||
true: {
|
||||
border: "$$borderWeight solid $$snippetBorderColor",
|
||||
},
|
||||
},
|
||||
filled: {
|
||||
true: {
|
||||
backgroundColor: "$$snippetBgColor",
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: "default",
|
||||
borderWeight: "normal",
|
||||
filled: false,
|
||||
},
|
||||
});
|
||||
|
||||
export const StyledSnippetPre = styled("pre", {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
border: "none",
|
||||
br: 0,
|
||||
bgColor: "transparent",
|
||||
color: "inherit",
|
||||
fontSize: "$sm",
|
||||
"*": {
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
fontSize: "inherit",
|
||||
color: "inherit",
|
||||
},
|
||||
variants: {
|
||||
withCopyButton: {
|
||||
true: {
|
||||
width: "calc(100% - 2 * $lg)",
|
||||
},
|
||||
false: {
|
||||
width: "100%",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const StyledSnippetCopyButton = styled(
|
||||
"button",
|
||||
{
|
||||
display: "inline-flex",
|
||||
jc: "center",
|
||||
border: "none",
|
||||
ai: "flex-start",
|
||||
bg: "transparent",
|
||||
width: "calc(2 * $space$lg)",
|
||||
br: "$xs",
|
||||
color: "inherit",
|
||||
transition: "opacity 0.2s ease 0s",
|
||||
cursor: "pointer",
|
||||
us: "none",
|
||||
"@motion": {
|
||||
transition: "none",
|
||||
},
|
||||
"&:hover": {
|
||||
opacity: "0.7",
|
||||
},
|
||||
svg: {
|
||||
path: {
|
||||
fill: "$accents6",
|
||||
},
|
||||
},
|
||||
},
|
||||
cssFocusVisible,
|
||||
);
|
||||
@ -26,9 +26,9 @@ const Snippet = forwardRef<SnippetProps, "div">((props, ref) => {
|
||||
hideCopyButton,
|
||||
tooltipProps,
|
||||
isMultiLine,
|
||||
getSnippetProps,
|
||||
focusProps,
|
||||
onCopy,
|
||||
getSnippetProps,
|
||||
} = useSnippet({ref, ...props});
|
||||
|
||||
const TooltipContent = useCallback(
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type {SnippetVariantProps, SnippetSlots, SlotsToClasses} from "@nextui-org/theme";
|
||||
|
||||
import {snippet} from "@nextui-org/theme";
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useClipboard} from "@nextui-org/use-clipboard";
|
||||
@ -106,7 +106,7 @@ export function useSnippet(originalProps: UseSnippetProps) {
|
||||
hideSymbol = false,
|
||||
onCopy: onCopyProp,
|
||||
tooltipProps = {
|
||||
offset: 12,
|
||||
offset: 15,
|
||||
content: "Copy to clipboard",
|
||||
size: originalProps?.size as TooltipProps["size"],
|
||||
variant: originalProps?.variant as TooltipProps["variant"],
|
||||
@ -147,7 +147,7 @@ export function useSnippet(originalProps: UseSnippetProps) {
|
||||
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const getSnippetProps = useCallback(
|
||||
const getSnippetProps = useCallback<PropGetter>(
|
||||
() => ({
|
||||
className: slots.base({
|
||||
class: baseStyles,
|
||||
@ -168,6 +168,7 @@ export function useSnippet(originalProps: UseSnippetProps) {
|
||||
} else if (Array.isArray(children)) {
|
||||
value = children.join("\n");
|
||||
}
|
||||
|
||||
copy(value);
|
||||
onCopyProp?.(value);
|
||||
}, [copy, disableCopy, onCopyProp, children]);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type {SpinnerVariantProps, SpinnerSlots, SlotsToClasses} from "@nextui-org/theme";
|
||||
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {spinner} from "@nextui-org/theme";
|
||||
import {clsx, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
@ -53,7 +53,7 @@ export function useSpinner(originalProps: UseSpinnerProps) {
|
||||
return !otherProps["aria-label"] ? "Loading" : "";
|
||||
}, [children, label, otherProps["aria-label"]]);
|
||||
|
||||
const getSpinnerProps = useCallback(
|
||||
const getSpinnerProps = useCallback<PropGetter>(
|
||||
() => ({
|
||||
"aria-label": ariaLabel,
|
||||
className: slots.base({
|
||||
|
||||
@ -8,7 +8,7 @@ import {useTooltipTriggerState} from "@react-stately/tooltip";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {useTooltip as useReactAriaTooltip, useTooltipTrigger} from "@react-aria/tooltip";
|
||||
import {useOverlayPosition, useOverlay, AriaOverlayProps} from "@react-aria/overlays";
|
||||
import {HTMLNextUIProps, mapPropsVariants} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {tooltip} from "@nextui-org/theme";
|
||||
import {ReactRef, mergeRefs} from "@nextui-org/shared-utils";
|
||||
import {useMemo, useRef, useState, useCallback} from "react";
|
||||
@ -178,7 +178,7 @@ export function useTooltip(originalProps: UseTooltipProps) {
|
||||
[...Object.values(variantProps), className],
|
||||
);
|
||||
|
||||
const getTriggerProps = useCallback(
|
||||
const getTriggerProps = useCallback<PropGetter>(
|
||||
(props = {}, _ref: Ref<any> | null | undefined = null) => ({
|
||||
...mergeProps(triggerProps, props),
|
||||
ref: mergeRefs(triggerRef, _ref),
|
||||
@ -188,7 +188,7 @@ export function useTooltip(originalProps: UseTooltipProps) {
|
||||
[isDismissable, triggerRef, triggerProps],
|
||||
);
|
||||
|
||||
const getTooltipProps = useCallback(
|
||||
const getTooltipProps = useCallback<PropGetter>(
|
||||
() => ({
|
||||
ref: domRef,
|
||||
className: styles,
|
||||
|
||||
@ -3,7 +3,7 @@ import type {AvatarProps} from "@nextui-org/avatar";
|
||||
|
||||
import {ReactNode, useMemo, useCallback} from "react";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
|
||||
import {user} from "@nextui-org/theme";
|
||||
import {ReactRef, clsx} from "@nextui-org/shared-utils";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
@ -53,7 +53,6 @@ export function useUser(props: UseUserProps) {
|
||||
const {
|
||||
as,
|
||||
ref,
|
||||
css,
|
||||
name,
|
||||
description,
|
||||
className,
|
||||
@ -96,8 +95,9 @@ export function useUser(props: UseUserProps) {
|
||||
];
|
||||
}, [as]);
|
||||
|
||||
const getUserProps = useCallback(
|
||||
const getUserProps = useCallback<PropGetter>(
|
||||
() => ({
|
||||
ref: domRef,
|
||||
tabIndex: canBeFocused ? 0 : -1,
|
||||
className: slots.base({
|
||||
class: clsx(baseStyles, buttonStyles),
|
||||
@ -109,9 +109,7 @@ export function useUser(props: UseUserProps) {
|
||||
|
||||
return {
|
||||
Component,
|
||||
domRef,
|
||||
className,
|
||||
css,
|
||||
slots,
|
||||
name,
|
||||
description,
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
import {styled} from "@nextui-org/system";
|
||||
import {cssFocusVisible} from "@nextui-org/shared-css";
|
||||
|
||||
export const StyledUser = styled(
|
||||
"div",
|
||||
{
|
||||
d: "inline-flex",
|
||||
p: "0 $sm",
|
||||
jc: "center",
|
||||
ai: "center",
|
||||
w: "max-content",
|
||||
maxWidth: "100%",
|
||||
transition: "transform 250ms ease 0ms, box-shadow 0.25s ease 0s",
|
||||
"@motion": {
|
||||
transition: "none",
|
||||
},
|
||||
},
|
||||
cssFocusVisible,
|
||||
);
|
||||
|
||||
export const StyledUserInfo = styled("div", {
|
||||
ml: "$sm",
|
||||
d: "inline-flex",
|
||||
fd: "column",
|
||||
alignItems: "flex-start",
|
||||
whiteSpace: "nowrap",
|
||||
});
|
||||
|
||||
export const StyledUserName = styled("span", {
|
||||
fontSize: "$sm",
|
||||
color: "$text",
|
||||
lh: "$sm",
|
||||
fontWeight: "$medium",
|
||||
maxW: "$60",
|
||||
to: "ellipsis", // text overflow
|
||||
ov: "hidden", // overflow
|
||||
});
|
||||
|
||||
export const StyledUserDesc = styled("span", {
|
||||
fontSize: "$xs",
|
||||
color: "$accents7",
|
||||
"*:first-child": {
|
||||
mt: 0,
|
||||
},
|
||||
"*:last-child": {
|
||||
mb: 0,
|
||||
},
|
||||
});
|
||||
@ -7,13 +7,13 @@ import {UseUserProps, useUser} from "./use-user";
|
||||
export interface UserProps extends Omit<UseUserProps, "ref"> {}
|
||||
|
||||
const User = forwardRef<UserProps, "div">((props, ref) => {
|
||||
const {Component, domRef, name, slots, description, avatarProps, getUserProps} = useUser({
|
||||
const {Component, name, slots, description, avatarProps, getUserProps} = useUser({
|
||||
ref,
|
||||
...props,
|
||||
});
|
||||
|
||||
return (
|
||||
<Component ref={domRef} {...getUserProps()}>
|
||||
<Component {...getUserProps()}>
|
||||
<Avatar {...avatarProps} />
|
||||
<div className={slots.wrapper()}>
|
||||
<span className={slots.name()}>{name}</span>
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
export * from "./system";
|
||||
export * from "./theme-provider";
|
||||
export * from "./types";
|
||||
export * from "./utils";
|
||||
|
||||
@ -2,8 +2,6 @@
|
||||
* Part of this code is taken from @chakra-ui/system ❤️
|
||||
*/
|
||||
|
||||
import {forwardRef as baseForwardRef} from "react";
|
||||
|
||||
export type As<Props = any> = React.ElementType<Props>;
|
||||
export type DOMElements = keyof JSX.IntrinsicElements;
|
||||
export type CapitalizedDOMElements = Capitalize<DOMElements>;
|
||||
@ -13,13 +11,23 @@ export interface NextUIProps {
|
||||
* The HTML element to render.
|
||||
*/
|
||||
as?: As;
|
||||
/**
|
||||
* The stiches's css style object
|
||||
* TODO: remove this prop after migrating all components to TailwindCSS
|
||||
*/
|
||||
css?: any;
|
||||
}
|
||||
|
||||
export interface DOMElement extends Element, HTMLOrSVGElement {}
|
||||
|
||||
type DataAttributes = {
|
||||
[dataAttr: string]: any;
|
||||
};
|
||||
|
||||
export type DOMAttributes<T = DOMElement> = React.AriaAttributes &
|
||||
React.DOMAttributes<T> &
|
||||
DataAttributes & {
|
||||
id?: string;
|
||||
role?: React.AriaRole;
|
||||
tabIndex?: number;
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
|
||||
export type OmitCommonProps<Target, OmitAdditionalProps extends keyof any = never> = Omit<
|
||||
Target,
|
||||
"transition" | "as" | "color" | OmitAdditionalProps
|
||||
@ -64,72 +72,18 @@ export type PropsOf<T extends As> = React.ComponentPropsWithoutRef<T> & {
|
||||
as?: As;
|
||||
};
|
||||
|
||||
export type Merge<M, N> = N extends Record<string, unknown> ? M : Omit<M, keyof N> & N;
|
||||
|
||||
export type HTMLNextUIProps<T extends As = "div", K extends object = {}> = Omit<
|
||||
Omit<PropsOf<T>, "ref" | "color" | "slot"> & NextUIProps,
|
||||
keyof K
|
||||
> &
|
||||
K;
|
||||
|
||||
export function forwardRef<
|
||||
Props extends object,
|
||||
Component extends As,
|
||||
CompoundComponents extends object = {},
|
||||
>(
|
||||
component: React.ForwardRefRenderFunction<
|
||||
any,
|
||||
RightJoinProps<PropsOf<Component>, Props> & {
|
||||
as?: As;
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return baseForwardRef(component) as unknown as ComponentWithAs<Component, Props> &
|
||||
CompoundComponents;
|
||||
}
|
||||
|
||||
export interface NextUIComponent<C extends As, P = {}>
|
||||
extends ComponentWithAs<C, NextUIProps & P> {}
|
||||
|
||||
export type HTMLNextUIComponents = {
|
||||
[Tag in CapitalizedDOMElements]: NextUIComponent<Uncapitalize<Tag>, {}>;
|
||||
};
|
||||
|
||||
export const toIterator = (obj: any) => {
|
||||
return {
|
||||
...obj,
|
||||
[Symbol.iterator]: function () {
|
||||
const keys = Object.keys(this);
|
||||
let index = 0;
|
||||
|
||||
return {
|
||||
next: () => {
|
||||
if (index >= keys.length) {
|
||||
return {done: true};
|
||||
}
|
||||
const key = keys[index];
|
||||
const value = this[key];
|
||||
|
||||
index++;
|
||||
|
||||
return {value: {key, value}, done: false};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const mapPropsVariants = <T extends Record<string, any>, K extends keyof T>(
|
||||
props: T,
|
||||
variantKeys?: K[],
|
||||
): readonly [Omit<T, K>, Pick<T, K> | {}] => {
|
||||
if (!variantKeys) {
|
||||
return [props, {}];
|
||||
}
|
||||
|
||||
const omitted = Object.keys(props)
|
||||
.filter((key) => !variantKeys.includes(key as K))
|
||||
.reduce((acc, key) => ({...acc, [key]: props[key as keyof T]}), {});
|
||||
|
||||
const picked = variantKeys.reduce((acc, key) => ({...acc, [key]: props[key]}), {});
|
||||
|
||||
return [omitted, picked] as [Omit<T, K>, Pick<T, K>];
|
||||
};
|
||||
export type PropGetter<P = Record<string, unknown>, R = DOMAttributes> = (
|
||||
props?: Merge<DOMAttributes, P>,
|
||||
ref?: React.Ref<any>,
|
||||
) => R & React.RefAttributes<any>;
|
||||
60
packages/core/system/src/utils.ts
Normal file
60
packages/core/system/src/utils.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import type {As, RightJoinProps, PropsOf, ComponentWithAs} from "./types";
|
||||
|
||||
import {forwardRef as baseForwardRef} from "react";
|
||||
|
||||
export function forwardRef<
|
||||
Props extends object,
|
||||
Component extends As,
|
||||
CompoundComponents extends object = {},
|
||||
>(
|
||||
component: React.ForwardRefRenderFunction<
|
||||
any,
|
||||
RightJoinProps<PropsOf<Component>, Props> & {
|
||||
as?: As;
|
||||
}
|
||||
>,
|
||||
) {
|
||||
return baseForwardRef(component) as unknown as ComponentWithAs<Component, Props> &
|
||||
CompoundComponents;
|
||||
}
|
||||
|
||||
export const toIterator = (obj: any) => {
|
||||
return {
|
||||
...obj,
|
||||
[Symbol.iterator]: function () {
|
||||
const keys = Object.keys(this);
|
||||
let index = 0;
|
||||
|
||||
return {
|
||||
next: () => {
|
||||
if (index >= keys.length) {
|
||||
return {done: true};
|
||||
}
|
||||
const key = keys[index];
|
||||
const value = this[key];
|
||||
|
||||
index++;
|
||||
|
||||
return {value: {key, value}, done: false};
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const mapPropsVariants = <T extends Record<string, any>, K extends keyof T>(
|
||||
props: T,
|
||||
variantKeys?: K[],
|
||||
): readonly [Omit<T, K>, Pick<T, K> | {}] => {
|
||||
if (!variantKeys) {
|
||||
return [props, {}];
|
||||
}
|
||||
|
||||
const omitted = Object.keys(props)
|
||||
.filter((key) => !variantKeys.includes(key as K))
|
||||
.reduce((acc, key) => ({...acc, [key]: props[key as keyof T]}), {});
|
||||
|
||||
const picked = variantKeys.reduce((acc, key) => ({...acc, [key]: props[key]}), {});
|
||||
|
||||
return [omitted, picked] as [Omit<T, K>, Pick<T, K>];
|
||||
};
|
||||
@ -23,7 +23,7 @@ const snippet = tv({
|
||||
slots: {
|
||||
base: "inline-flex items-center justify-between space-x-3 rounded-md",
|
||||
pre: "bg-transparent text-inherit font-mono whitespace-pre-wrap",
|
||||
copy: "z-10 apparance-none outline-none select-none",
|
||||
copy: "z-10 apparance-none outline-none",
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
@ -26,10 +26,8 @@ export function useClipboard({timeout = 2000}: UseClipboardProps = {}) {
|
||||
|
||||
const handleCopyResult = (value: boolean) => {
|
||||
onClearTimeout();
|
||||
if (timeout > 0) {
|
||||
setCopyTimeout(setTimeout(() => setCopied(false), timeout));
|
||||
setCopied(value);
|
||||
}
|
||||
setCopyTimeout(setTimeout(() => setCopied(false), timeout));
|
||||
setCopied(value);
|
||||
};
|
||||
|
||||
const copy = (valueToCopy: any) => {
|
||||
|
||||
@ -35,7 +35,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/use-real-shape": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
@ -86,6 +86,7 @@ const CSSTransition: FC<CSSTransitionProps> = React.memo(
|
||||
|
||||
return React.cloneElement(children, {
|
||||
...otherProps,
|
||||
// @ts-ignore
|
||||
"data-transition": statusClassName,
|
||||
className: clsx(children.props.className, className, !childrenRef?.current && classes),
|
||||
});
|
||||
|
||||
@ -1,96 +0,0 @@
|
||||
import React, {useEffect, useRef, useState} from "react";
|
||||
import {styled, HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {useRealShape} from "@nextui-org/use-real-shape";
|
||||
|
||||
export interface ExpandProps extends HTMLNextUIProps<"div"> {
|
||||
isExpanded?: boolean;
|
||||
animated?: boolean;
|
||||
delay?: number;
|
||||
}
|
||||
|
||||
const StyledExpand = styled("div", {
|
||||
p: 0,
|
||||
m: 0,
|
||||
h: 0,
|
||||
opacity: 0,
|
||||
overflow: "hidden",
|
||||
variants: {
|
||||
isExpanded: {
|
||||
true: {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const Expand: React.FC<ExpandProps> = ({
|
||||
css,
|
||||
children,
|
||||
delay = 200,
|
||||
animated = true,
|
||||
isExpanded = false,
|
||||
...otherProps
|
||||
}) => {
|
||||
const [height, setHeight] = useState<string>(isExpanded ? "auto" : "0");
|
||||
const [selfExpanded, setSelfExpanded] = useState<boolean>(isExpanded);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
const entryTimer = useRef<number>();
|
||||
const leaveTimer = useRef<number>();
|
||||
const resetTimer = useRef<number>();
|
||||
const [state, updateShape] = useRealShape<HTMLDivElement>(contentRef);
|
||||
|
||||
useEffect(() => setHeight(`${state.height}px`), [state.height]);
|
||||
useEffect(() => {
|
||||
if (isExpanded === selfExpanded) return;
|
||||
// show element or reset height.
|
||||
// force an update once manually, even if the element does not change.
|
||||
// (the height of the element might be "auto")
|
||||
if (!isExpanded) {
|
||||
updateShape();
|
||||
setHeight(`${state.height}px`);
|
||||
}
|
||||
|
||||
// show expand animation
|
||||
entryTimer.current = window.setTimeout(() => {
|
||||
setSelfExpanded(isExpanded);
|
||||
clearTimeout(entryTimer.current);
|
||||
}, 30);
|
||||
|
||||
// Reset height after animation
|
||||
if (isExpanded) {
|
||||
resetTimer.current = window.setTimeout(() => {
|
||||
setHeight("auto");
|
||||
clearTimeout(resetTimer.current);
|
||||
}, delay);
|
||||
} else {
|
||||
leaveTimer.current = window.setTimeout(() => {
|
||||
clearTimeout(leaveTimer.current);
|
||||
}, delay / 2);
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearTimeout(entryTimer.current);
|
||||
clearTimeout(leaveTimer.current);
|
||||
clearTimeout(resetTimer.current);
|
||||
};
|
||||
}, [isExpanded]);
|
||||
|
||||
return (
|
||||
<StyledExpand
|
||||
css={{
|
||||
height: selfExpanded ? height : "0",
|
||||
transition: animated
|
||||
? `height ${delay}ms ease 0ms,
|
||||
opacity ${delay * 1.5}ms ease 0ms;`
|
||||
: "none",
|
||||
...(css as any),
|
||||
}}
|
||||
isExpanded={selfExpanded}
|
||||
{...otherProps}
|
||||
>
|
||||
<div ref={contentRef} className="nextui-expand-content">
|
||||
{children}
|
||||
</div>
|
||||
</StyledExpand>
|
||||
);
|
||||
};
|
||||
@ -1,2 +1 @@
|
||||
export * from "./css-transition";
|
||||
export * from "./expand";
|
||||
|
||||
4
pnpm-lock.yaml
generated
4
pnpm-lock.yaml
generated
@ -655,7 +655,6 @@ importers:
|
||||
packages/components/snippet:
|
||||
specifiers:
|
||||
'@nextui-org/dom-utils': workspace:*
|
||||
'@nextui-org/shared-css': workspace:*
|
||||
'@nextui-org/shared-utils': workspace:*
|
||||
'@nextui-org/system': workspace:*
|
||||
'@nextui-org/theme': workspace:*
|
||||
@ -666,7 +665,6 @@ importers:
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
'@nextui-org/dom-utils': link:../../utilities/dom-utils
|
||||
'@nextui-org/shared-css': link:../../utilities/shared-css
|
||||
'@nextui-org/shared-utils': link:../../utilities/shared-utils
|
||||
'@nextui-org/system': link:../../core/system
|
||||
'@nextui-org/theme': link:../../core/theme
|
||||
@ -1020,13 +1018,11 @@ importers:
|
||||
specifiers:
|
||||
'@nextui-org/shared-utils': workspace:*
|
||||
'@nextui-org/system': workspace:*
|
||||
'@nextui-org/use-real-shape': workspace:*
|
||||
clean-package: 2.2.0
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
'@nextui-org/shared-utils': link:../shared-utils
|
||||
'@nextui-org/system': link:../../core/system
|
||||
'@nextui-org/use-real-shape': link:../../hooks/use-real-shape
|
||||
devDependencies:
|
||||
clean-package: 2.2.0
|
||||
react: 18.2.0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user