mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
chore(card): initial sctructure changed
This commit is contained in:
parent
91cb12b8bd
commit
e9bbf2e673
@ -3,11 +3,11 @@ import type {AriaButtonProps} from "@react-types/button";
|
||||
import type {HTMLNextUIProps, PropGetter} from "@nextui-org/system";
|
||||
import type {ReactNode} from "react";
|
||||
|
||||
import {dataAttr, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {callAllHandlers, dataAttr, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {MouseEventHandler, useCallback} from "react";
|
||||
import {useButton as useAriaButton} from "@react-aria/button";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {chain, mergeProps} from "@react-aria/utils";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {useDrip} from "@nextui-org/drip";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {clsx} from "@nextui-org/shared-utils";
|
||||
@ -117,7 +117,7 @@ export function useButton(props: UseButtonProps) {
|
||||
elementType: as,
|
||||
isDisabled,
|
||||
onPress,
|
||||
onClick: chain(onClick, handleDrip),
|
||||
onClick: callAllHandlers(onClick, handleDrip),
|
||||
...otherProps,
|
||||
} as AriaButtonProps,
|
||||
domRef,
|
||||
|
||||
@ -45,7 +45,8 @@
|
||||
"@nextui-org/drip": "workspace:*",
|
||||
"@react-aria/focus": "^3.11.0",
|
||||
"@react-aria/utils": "^3.15.0",
|
||||
"@react-aria/interactions": "^3.14.0"
|
||||
"@react-aria/interactions": "^3.14.0",
|
||||
"@react-aria/button": "^3.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-types/shared": "^3.17.0",
|
||||
|
||||
@ -1,69 +1,25 @@
|
||||
import {forwardRef} from "@nextui-org/system";
|
||||
import {__DEV__} from "@nextui-org/shared-utils";
|
||||
import {Image} from "@nextui-org/image";
|
||||
import {Drip} from "@nextui-org/drip";
|
||||
|
||||
import CardHeader from "./card-header";
|
||||
import {useCard, UseCardProps} from "./use-card";
|
||||
import CardBody from "./card-body";
|
||||
import CardFooter from "./card-footer";
|
||||
|
||||
export interface CardProps extends Omit<UseCardProps, "ref"> {}
|
||||
|
||||
type CompoundCard = {
|
||||
Header: typeof CardHeader;
|
||||
Body: typeof CardBody;
|
||||
Footer: typeof CardFooter;
|
||||
Image: typeof Image;
|
||||
};
|
||||
|
||||
const Card = forwardRef<CardProps, "div", CompoundCard>((props, ref) => {
|
||||
const {
|
||||
cardRef,
|
||||
children,
|
||||
className,
|
||||
Component,
|
||||
styles,
|
||||
isPressable,
|
||||
disableAnimation,
|
||||
disableRipple,
|
||||
dripBindings,
|
||||
getCardProps,
|
||||
} = useCard({ref, ...props});
|
||||
|
||||
const {base} = styles;
|
||||
const Card = forwardRef<CardProps, "div">((props, ref) => {
|
||||
const {children, Component, drips, isPressable, disableAnimation, disableRipple, getCardProps} =
|
||||
useCard({
|
||||
ref,
|
||||
...props,
|
||||
});
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={cardRef}
|
||||
className={base({class: className})}
|
||||
role={isPressable ? "button" : "section"}
|
||||
tabIndex={isPressable ? 0 : -1}
|
||||
{...getCardProps()}
|
||||
>
|
||||
{isPressable && !disableAnimation && !disableRipple && (
|
||||
<Drip
|
||||
{...dripBindings}
|
||||
styles={{
|
||||
base: "opacity-30 z-50",
|
||||
svg: "text-inherit",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Component {...getCardProps()}>
|
||||
{isPressable && !disableAnimation && !disableRipple && <Drip drips={drips} />}
|
||||
{children}
|
||||
</Component>
|
||||
);
|
||||
});
|
||||
|
||||
Card.Header = CardHeader;
|
||||
Card.Body = CardBody;
|
||||
Card.Footer = CardFooter;
|
||||
Card.Image = Image;
|
||||
|
||||
if (__DEV__) {
|
||||
Card.displayName = "NextUI.Card";
|
||||
}
|
||||
|
||||
Card.toString = () => ".nextui-card";
|
||||
Card.displayName = "NextUI.Card";
|
||||
|
||||
export default Card;
|
||||
|
||||
@ -4,5 +4,5 @@ export type {CardProps} from "./card";
|
||||
// export hooks
|
||||
export {useCard} from "./use-card";
|
||||
|
||||
// export component
|
||||
// export components
|
||||
export {default as Card} from "./card";
|
||||
|
||||
@ -1,111 +1,97 @@
|
||||
import type {FocusableProps, PressEvent, PressEvents} from "@react-types/shared";
|
||||
import type {FocusableProps, PressEvents} from "@react-types/shared";
|
||||
import type {SlotsToClasses, CardSlots, CardVariantProps} from "@nextui-org/theme";
|
||||
|
||||
import {card} from "@nextui-org/theme";
|
||||
import {MouseEvent, useCallback, useMemo} from "react";
|
||||
import {mergeProps} from "@react-aria/utils";
|
||||
import {useFocusRing} from "@react-aria/focus";
|
||||
import {useHover, usePress} from "@react-aria/interactions";
|
||||
import {HTMLNextUIProps} from "@nextui-org/system";
|
||||
import {NormalWeights, ReactRef, warn} from "@nextui-org/shared-utils";
|
||||
import {useHover} from "@react-aria/interactions";
|
||||
import {useButton as useAriaButton} from "@react-aria/button";
|
||||
import {HTMLNextUIProps, mapPropsVariants, PropGetter} from "@nextui-org/system";
|
||||
import {callAllHandlers, clsx, ReactRef} from "@nextui-org/shared-utils";
|
||||
import {useDOMRef} from "@nextui-org/dom-utils";
|
||||
import {useDrip} from "@nextui-org/drip";
|
||||
import {AriaButtonProps} from "@react-aria/button";
|
||||
|
||||
export interface UseCardProps extends HTMLNextUIProps<"div", PressEvents & FocusableProps> {
|
||||
export interface Props extends HTMLNextUIProps<"div"> {
|
||||
/**
|
||||
* The card ref.
|
||||
* Ref to the DOM node.
|
||||
*/
|
||||
ref: ReactRef<HTMLDivElement | null>;
|
||||
/**
|
||||
* The card variant style.
|
||||
* @default "shadow"
|
||||
*/
|
||||
variant?: "shadow" | "flat" | "bordered";
|
||||
/**
|
||||
* The border weight of the `bordered` variant card.
|
||||
* @default "normal"
|
||||
*/
|
||||
borderWeight?: NormalWeights;
|
||||
/**
|
||||
* Whether the card should allow users to interact with the card.
|
||||
* @default false
|
||||
*/
|
||||
isPressable?: boolean;
|
||||
/**
|
||||
* Whether the card can be hovered by the user.
|
||||
* @default false
|
||||
*/
|
||||
isHoverable?: boolean;
|
||||
/**
|
||||
* Whether the card should show a ripple animation on press
|
||||
* @default false
|
||||
*/
|
||||
disableRipple?: boolean;
|
||||
// @deprecated - use `onPress` instead
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
/**
|
||||
* Whether the card is animated.
|
||||
* @default false
|
||||
*/
|
||||
disableAnimation?: boolean;
|
||||
|
||||
/**
|
||||
* Whether the card should allow text selection on press. (only for pressable cards)
|
||||
* @default true
|
||||
*/
|
||||
allowTextSelectionOnPress?: boolean;
|
||||
/**
|
||||
* Classname or List of classes to change the styles of the element.
|
||||
* if `className` is passed, it will be added to the base slot.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* <Card styles={{
|
||||
* base:"base-classes",
|
||||
* header: "dot-classes",
|
||||
* body: "content-classes",
|
||||
* footer: "avatar-classes",
|
||||
* }} />
|
||||
* ```
|
||||
*/
|
||||
styles?: SlotsToClasses<CardSlots>;
|
||||
}
|
||||
|
||||
export function useCard(props: UseCardProps) {
|
||||
export type UseCardProps = Props & PressEvents & FocusableProps & CardVariantProps;
|
||||
|
||||
export function useCard(originalProps: UseCardProps) {
|
||||
const [props, variantProps] = mapPropsVariants(originalProps, card.variantKeys);
|
||||
|
||||
const {
|
||||
ref,
|
||||
as,
|
||||
children,
|
||||
disableAnimation = false,
|
||||
disableRipple = false,
|
||||
variant = "shadow",
|
||||
isHoverable = false,
|
||||
borderWeight = "light",
|
||||
isPressable = false,
|
||||
onClick: deprecatedOnClick,
|
||||
onClick,
|
||||
onPress,
|
||||
autoFocus,
|
||||
className,
|
||||
styles,
|
||||
allowTextSelectionOnPress = true,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const cardRef = useDOMRef<HTMLDivElement>(ref);
|
||||
const Component = as || (isPressable ? "button" : "div");
|
||||
const domRef = useDOMRef<HTMLDivElement>(ref);
|
||||
const Component = as || (originalProps.isPressable ? "button" : "div");
|
||||
|
||||
const {onClick: onDripClickHandler, ...dripBindings} = useDrip(false, cardRef);
|
||||
const baseStyles = clsx(styles?.base, className);
|
||||
|
||||
const handleDrip = (e: MouseEvent<HTMLDivElement> | PressEvent | Event) => {
|
||||
if (!disableAnimation && !disableRipple && cardRef.current) {
|
||||
const {onClick: onDripClickHandler, drips} = useDrip();
|
||||
|
||||
const handleDrip = (e: MouseEvent<HTMLDivElement>) => {
|
||||
if (!originalProps.disableAnimation && !disableRipple && domRef.current) {
|
||||
onDripClickHandler(e);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePress = (e: PressEvent) => {
|
||||
if (e.pointerType === "keyboard" || e.pointerType === "virtual") {
|
||||
handleDrip(e);
|
||||
} else if (typeof window !== "undefined" && window.event) {
|
||||
handleDrip(window.event);
|
||||
}
|
||||
if (deprecatedOnClick) {
|
||||
deprecatedOnClick(e as any);
|
||||
warn("onClick is deprecated, please use onPress", "Card");
|
||||
}
|
||||
onPress?.(e);
|
||||
};
|
||||
|
||||
const {isPressed, pressProps} = usePress({
|
||||
isDisabled: !isPressable,
|
||||
onPress: handlePress,
|
||||
allowTextSelectionOnPress,
|
||||
...otherProps,
|
||||
});
|
||||
const {buttonProps, isPressed} = useAriaButton(
|
||||
{
|
||||
elementType: as,
|
||||
isDisabled: !originalProps.isPressable,
|
||||
onPress,
|
||||
onClick: callAllHandlers(onClick, handleDrip),
|
||||
allowTextSelectionOnPress,
|
||||
...otherProps,
|
||||
} as AriaButtonProps,
|
||||
domRef,
|
||||
);
|
||||
|
||||
const {hoverProps, isHovered} = useHover({
|
||||
isDisabled: !isHoverable,
|
||||
isDisabled: !originalProps.isHoverable,
|
||||
...otherProps,
|
||||
});
|
||||
|
||||
@ -113,49 +99,57 @@ export function useCard(props: UseCardProps) {
|
||||
autoFocus,
|
||||
});
|
||||
|
||||
const styles = useMemo(
|
||||
const slots = useMemo(
|
||||
() =>
|
||||
card({
|
||||
borderWeight,
|
||||
className,
|
||||
disableAnimation,
|
||||
isPressable,
|
||||
isHoverable,
|
||||
variant,
|
||||
...variantProps,
|
||||
isFocusVisible,
|
||||
}),
|
||||
[disableAnimation, variant, borderWeight, isPressable, isHoverable],
|
||||
[...Object.values(variantProps), isFocusVisible],
|
||||
);
|
||||
|
||||
const getCardProps = useCallback(
|
||||
const getCardProps = useCallback<PropGetter>(
|
||||
(props = {}) => {
|
||||
return {
|
||||
ref: domRef,
|
||||
className: slots.base({class: baseStyles}),
|
||||
role: originalProps.isPressable ? "button" : "section",
|
||||
tabIndex: originalProps.isPressable ? 0 : -1,
|
||||
...mergeProps(
|
||||
isPressable ? {...pressProps, ...focusProps} : {},
|
||||
isHoverable ? hoverProps : {},
|
||||
originalProps.isPressable ? {...buttonProps, ...focusProps} : {},
|
||||
originalProps.isHoverable ? hoverProps : {},
|
||||
otherProps,
|
||||
props,
|
||||
),
|
||||
};
|
||||
},
|
||||
[isPressable, isHoverable, pressProps, focusProps, hoverProps, otherProps],
|
||||
[
|
||||
domRef,
|
||||
slots,
|
||||
baseStyles,
|
||||
originalProps.isPressable,
|
||||
originalProps.isHoverable,
|
||||
buttonProps,
|
||||
focusProps,
|
||||
hoverProps,
|
||||
otherProps,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
cardRef,
|
||||
domRef,
|
||||
Component,
|
||||
styles,
|
||||
variant,
|
||||
children,
|
||||
borderWeight,
|
||||
isPressable,
|
||||
drips,
|
||||
isHovered,
|
||||
isPressed,
|
||||
disableAnimation,
|
||||
isPressable: originalProps.isPressable,
|
||||
isHoverable: originalProps.isHoverable,
|
||||
disableAnimation: originalProps.disableAnimation,
|
||||
disableRipple,
|
||||
dripBindings,
|
||||
onDripClickHandler,
|
||||
isFocusVisible,
|
||||
className,
|
||||
getCardProps,
|
||||
};
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ const button = tv({
|
||||
"outline-none",
|
||||
"select-none",
|
||||
"font-medium",
|
||||
"antialiased",
|
||||
"subpixel-antialiased",
|
||||
"active:scale-95",
|
||||
"overflow-hidden",
|
||||
"gap-3",
|
||||
|
||||
@ -1,22 +1,42 @@
|
||||
import {tv, type VariantProps} from "tailwind-variants";
|
||||
|
||||
import {focusVisibleClasses} from "../../utils";
|
||||
import {ringClasses} from "../utils";
|
||||
|
||||
/**
|
||||
* Card **Tailwind Variants** component
|
||||
*
|
||||
* @example
|
||||
* <div className={card()}>A basic card</div>
|
||||
* ```js
|
||||
* const {base, header, body, footer} = card({...})
|
||||
*
|
||||
* <div className={card()}>
|
||||
* <div className={header()}>Header</div>
|
||||
* <div className={body()}>Body</div>
|
||||
* <div className={footer()}>Footer</div>
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
const card = tv({
|
||||
slots: {
|
||||
base: [
|
||||
...focusVisibleClasses,
|
||||
"flex flex-col m-0 p-0 relative overflow-hidden w-full height-auto bg-white text-foreground rounded-xl box-border dark:bg-neutral-900 dark:text-foreground-dark",
|
||||
"flex",
|
||||
"flex-col",
|
||||
"m-0",
|
||||
"p-0",
|
||||
"relative",
|
||||
"overflow-hidden",
|
||||
"w-full",
|
||||
"height-auto",
|
||||
"bg-white",
|
||||
"text-foreground",
|
||||
"rounded-xl",
|
||||
"box-border",
|
||||
"dark:bg-neutral-900",
|
||||
"dark:text-foreground-dark",
|
||||
],
|
||||
header: "",
|
||||
body: "",
|
||||
footer: "",
|
||||
header: "",
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
@ -37,6 +57,11 @@ const card = tv({
|
||||
isPressable: {
|
||||
true: "cursor-pointer",
|
||||
},
|
||||
isFocusVisible: {
|
||||
true: {
|
||||
base: [...ringClasses],
|
||||
},
|
||||
},
|
||||
disableAnimation: {
|
||||
true: "",
|
||||
false: "!transition motion-reduce:transition-none",
|
||||
@ -82,6 +107,7 @@ const card = tv({
|
||||
defaultVariants: {
|
||||
variant: "shadow",
|
||||
isHoverable: false,
|
||||
isPressable: false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -5,9 +5,10 @@ import {ringClasses, colorVariants} from "../utils";
|
||||
/**
|
||||
* Chip wrapper **Tailwind Variants** component
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const {base, content, dot, avatar, closeButton} = chip({...})
|
||||
*
|
||||
* @example
|
||||
* <div className={base())}>
|
||||
* // left content
|
||||
* <span className={avatar()}/>
|
||||
@ -16,6 +17,7 @@ import {ringClasses, colorVariants} from "../utils";
|
||||
* <svg className={closeButton()}>close button</svg>
|
||||
* // right content
|
||||
* </div>
|
||||
* ```
|
||||
*/
|
||||
const chip = tv({
|
||||
slots: {
|
||||
|
||||
2
pnpm-lock.yaml
generated
2
pnpm-lock.yaml
generated
@ -437,6 +437,7 @@ importers:
|
||||
'@nextui-org/shared-utils': workspace:*
|
||||
'@nextui-org/system': workspace:*
|
||||
'@nextui-org/theme': workspace:*
|
||||
'@react-aria/button': ^3.7.0
|
||||
'@react-aria/focus': ^3.11.0
|
||||
'@react-aria/interactions': ^3.14.0
|
||||
'@react-aria/utils': ^3.15.0
|
||||
@ -450,6 +451,7 @@ importers:
|
||||
'@nextui-org/shared-utils': link:../../utilities/shared-utils
|
||||
'@nextui-org/system': link:../../core/system
|
||||
'@nextui-org/theme': link:../../core/theme
|
||||
'@react-aria/button': 3.7.0_react@18.2.0
|
||||
'@react-aria/focus': 3.11.0_react@18.2.0
|
||||
'@react-aria/interactions': 3.14.0_react@18.2.0
|
||||
'@react-aria/utils': 3.15.0_react@18.2.0
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user