feat(components): prop getter type implemented to reduced the size of d.ts files

This commit is contained in:
Junior Garcia 2023-03-05 00:24:58 -03:00
parent dc0f6a4d55
commit f502080e69
36 changed files with 213 additions and 470 deletions

View File

@ -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)}

View File

@ -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,
};
}

View File

@ -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}),

View File

@ -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,

View File

@ -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],
);

View File

@ -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: {

View File

@ -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: {

View File

@ -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",

View File

@ -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,

View File

@ -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();
});

View File

@ -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) {

View File

@ -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,
};

View File

@ -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__) {

View File

@ -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>;

View File

@ -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}

View File

@ -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>;

View File

@ -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);
});
});

View File

@ -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:*",

View File

@ -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,
);

View File

@ -26,9 +26,9 @@ const Snippet = forwardRef<SnippetProps, "div">((props, ref) => {
hideCopyButton,
tooltipProps,
isMultiLine,
getSnippetProps,
focusProps,
onCopy,
getSnippetProps,
} = useSnippet({ref, ...props});
const TooltipContent = useCallback(

View File

@ -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]);

View File

@ -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({

View File

@ -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,

View File

@ -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,

View File

@ -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,
},
});

View File

@ -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>

View File

@ -1,2 +1,3 @@
export * from "./system";
export * from "./theme-provider";
export * from "./types";
export * from "./utils";

View File

@ -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>;

View 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>];
};

View File

@ -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: {

View File

@ -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) => {

View File

@ -35,7 +35,6 @@
},
"dependencies": {
"@nextui-org/system": "workspace:*",
"@nextui-org/use-real-shape": "workspace:*",
"@nextui-org/shared-utils": "workspace:*"
},
"peerDependencies": {

View File

@ -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),
});

View File

@ -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>
);
};

View File

@ -1,2 +1 @@
export * from "./css-transition";
export * from "./expand";

4
pnpm-lock.yaml generated
View File

@ -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