Some checks failed
No response / noResponse (push) Has been cancelled
CI / Continuous releases (push) Has been cancelled
CI / test-dev (macos-latest) (push) Has been cancelled
CI / test-dev (ubuntu-latest) (push) Has been cancelled
CI / test-dev (windows-latest) (push) Has been cancelled
Maintenance / main (push) Has been cancelled
Scorecards supply-chain security / Scorecards analysis (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
265 lines
8.6 KiB
TypeScript
265 lines
8.6 KiB
TypeScript
'use client';
|
|
import * as React from 'react';
|
|
import clsx from 'clsx';
|
|
import PropTypes from 'prop-types';
|
|
import { unstable_composeClasses as composeClasses } from '@mui/base';
|
|
import { OverridableComponent } from '@mui/types';
|
|
import capitalize from '@mui/utils/capitalize';
|
|
import isMuiElement from '@mui/utils/isMuiElement';
|
|
import { useThemeProps } from '../styles';
|
|
import { applySolidInversion, applySoftInversion } from '../colorInversion';
|
|
import styled from '../styles/styled';
|
|
import { getCardUtilityClass } from './cardClasses';
|
|
import { CardProps, CardOwnerState, CardTypeMap } from './CardProps';
|
|
import { resolveSxValue } from '../styles/styleUtils';
|
|
import useSlot from '../utils/useSlot';
|
|
import { DividerProps } from '../Divider';
|
|
|
|
const useUtilityClasses = (ownerState: CardOwnerState) => {
|
|
const { size, variant, color, orientation } = ownerState;
|
|
|
|
const slots = {
|
|
root: [
|
|
'root',
|
|
orientation,
|
|
variant && `variant${capitalize(variant)}`,
|
|
color && `color${capitalize(color)}`,
|
|
size && `size${capitalize(size)}`,
|
|
],
|
|
};
|
|
|
|
return composeClasses(slots, getCardUtilityClass, {});
|
|
};
|
|
|
|
export const StyledCardRoot = styled('div')<{ ownerState: CardOwnerState }>(({
|
|
theme,
|
|
ownerState,
|
|
}) => {
|
|
const { p, padding, borderRadius } = resolveSxValue({ theme, ownerState }, [
|
|
'p',
|
|
'padding',
|
|
'borderRadius',
|
|
]);
|
|
return [
|
|
{
|
|
'--Icon-color':
|
|
ownerState.color !== 'neutral' || ownerState.variant === 'solid'
|
|
? 'currentColor'
|
|
: theme.vars.palette.text.icon,
|
|
// a context variable for any child component
|
|
'--Card-childRadius':
|
|
'max((var(--Card-radius) - var(--variant-borderWidth, 0px)) - var(--Card-padding), min(var(--Card-padding) / 2, (var(--Card-radius) - var(--variant-borderWidth, 0px)) / 2))',
|
|
// AspectRatio integration
|
|
'--AspectRatio-radius': 'var(--Card-childRadius)',
|
|
// Link integration
|
|
'--unstable_actionMargin': 'calc(-1 * var(--variant-borderWidth, 0px))',
|
|
// Link, Radio, Checkbox integration
|
|
'--unstable_actionRadius': 'var(--Card-radius)',
|
|
// CardCover integration
|
|
'--CardCover-radius': 'calc(var(--Card-radius) - var(--variant-borderWidth, 0px))',
|
|
// CardOverflow integration
|
|
'--CardOverflow-offset': `calc(-1 * var(--Card-padding))`,
|
|
'--CardOverflow-radius': 'calc(var(--Card-radius) - var(--variant-borderWidth, 0px))',
|
|
// Divider integration
|
|
'--Divider-inset': 'calc(-1 * var(--Card-padding))',
|
|
...(ownerState.size === 'sm' && {
|
|
'--Card-radius': theme.vars.radius.sm,
|
|
'--Card-padding': '0.625rem',
|
|
gap: '0.5rem',
|
|
}),
|
|
...(ownerState.size === 'md' && {
|
|
'--Card-radius': theme.vars.radius.md,
|
|
'--Card-padding': '1rem',
|
|
gap: '0.75rem 1rem',
|
|
}),
|
|
...(ownerState.size === 'lg' && {
|
|
'--Card-radius': theme.vars.radius.lg,
|
|
'--Card-padding': '1.5rem',
|
|
gap: '1rem 1.5rem',
|
|
}),
|
|
padding: 'var(--Card-padding)',
|
|
borderRadius: 'var(--Card-radius)',
|
|
backgroundColor: theme.vars.palette.background.surface,
|
|
position: 'relative',
|
|
display: 'flex',
|
|
flexDirection: ownerState.orientation === 'horizontal' ? 'row' : 'column',
|
|
...theme.typography[`body-${ownerState.size!}`],
|
|
...(ownerState.variant === 'solid' &&
|
|
ownerState.color &&
|
|
ownerState.invertedColors &&
|
|
applySolidInversion(ownerState.color)(theme)),
|
|
...(ownerState.variant === 'soft' &&
|
|
ownerState.color &&
|
|
ownerState.invertedColors &&
|
|
applySoftInversion(ownerState.color)(theme)),
|
|
...theme.variants[ownerState.variant!]?.[ownerState.color!],
|
|
} as const,
|
|
p !== undefined && { '--Card-padding': p },
|
|
padding !== undefined && { '--Card-padding': padding },
|
|
borderRadius !== undefined && { '--Card-radius': borderRadius },
|
|
];
|
|
});
|
|
|
|
const CardRoot = styled(StyledCardRoot, {
|
|
name: 'JoyCard',
|
|
slot: 'Root',
|
|
overridesResolver: (props, styles) => styles.root,
|
|
})<{ ownerState: CardOwnerState }>({});
|
|
|
|
/**
|
|
*
|
|
* Demos:
|
|
*
|
|
* - [Card](https://mui.com/joy-ui/react-card/)
|
|
*
|
|
* API:
|
|
*
|
|
* - [Card API](https://mui.com/joy-ui/api/card/)
|
|
*/
|
|
const Card = React.forwardRef(function Card(inProps, ref) {
|
|
const props = useThemeProps<typeof inProps & CardProps>({
|
|
props: inProps,
|
|
name: 'JoyCard',
|
|
});
|
|
|
|
const {
|
|
className,
|
|
color = 'neutral',
|
|
component = 'div',
|
|
invertedColors = false,
|
|
size = 'md',
|
|
variant = 'outlined',
|
|
children,
|
|
orientation = 'vertical',
|
|
slots = {},
|
|
slotProps = {},
|
|
...other
|
|
} = props;
|
|
|
|
const ownerState = {
|
|
...props,
|
|
color,
|
|
component,
|
|
orientation,
|
|
size,
|
|
variant,
|
|
invertedColors,
|
|
};
|
|
|
|
const classes = useUtilityClasses(ownerState);
|
|
const externalForwardedProps = { ...other, component, slots, slotProps };
|
|
|
|
const [SlotRoot, rootProps] = useSlot('root', {
|
|
ref,
|
|
className: clsx(classes.root, className),
|
|
elementType: CardRoot,
|
|
externalForwardedProps,
|
|
ownerState,
|
|
});
|
|
|
|
return (
|
|
<SlotRoot {...rootProps}>
|
|
{React.Children.map(children, (child, index) => {
|
|
if (!React.isValidElement(child)) {
|
|
return child;
|
|
}
|
|
const extraProps: Record<string, any> = {};
|
|
if (isMuiElement(child, ['Divider'])) {
|
|
const childProps = child.props as DividerProps;
|
|
extraProps.inset = childProps?.inset ?? 'context';
|
|
|
|
const dividerOrientation = orientation === 'vertical' ? 'horizontal' : 'vertical';
|
|
extraProps.orientation = childProps?.orientation ?? dividerOrientation;
|
|
}
|
|
if (index === 0) {
|
|
extraProps['data-first-child'] = '';
|
|
}
|
|
if (index === React.Children.count(children) - 1) {
|
|
extraProps['data-last-child'] = '';
|
|
}
|
|
return React.cloneElement(child, extraProps);
|
|
})}
|
|
</SlotRoot>
|
|
);
|
|
}) as OverridableComponent<CardTypeMap>;
|
|
|
|
Card.propTypes /* remove-proptypes */ = {
|
|
// ┌────────────────────────────── Warning ──────────────────────────────┐
|
|
// │ These PropTypes are generated from the TypeScript type definitions. │
|
|
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
|
|
// └─────────────────────────────────────────────────────────────────────┘
|
|
/**
|
|
* Used to render icon or text elements inside the Card if `src` is not set.
|
|
* This can be an element, or just a string.
|
|
*/
|
|
children: PropTypes.node,
|
|
/**
|
|
* @ignore
|
|
*/
|
|
className: PropTypes.string,
|
|
/**
|
|
* The color of the component. It supports those theme colors that make sense for this component.
|
|
* @default 'neutral'
|
|
*/
|
|
color: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
|
|
PropTypes.oneOf(['danger', 'neutral', 'primary', 'success', 'warning']),
|
|
PropTypes.string,
|
|
]),
|
|
/**
|
|
* The component used for the root node.
|
|
* Either a string to use a HTML element or a component.
|
|
*/
|
|
component: PropTypes.elementType,
|
|
/**
|
|
* If `true`, the children with an implicit color prop invert their colors to match the component's variant and color.
|
|
* @default false
|
|
*/
|
|
invertedColors: PropTypes.bool,
|
|
/**
|
|
* The component orientation.
|
|
* @default 'vertical'
|
|
*/
|
|
orientation: PropTypes.oneOf(['horizontal', 'vertical']),
|
|
/**
|
|
* The size of the component.
|
|
* It accepts theme values between 'sm' and 'lg'.
|
|
* @default 'md'
|
|
*/
|
|
size: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
|
|
PropTypes.oneOf(['lg', 'md', 'sm']),
|
|
PropTypes.string,
|
|
]),
|
|
/**
|
|
* The props used for each slot inside.
|
|
* @default {}
|
|
*/
|
|
slotProps: PropTypes.shape({
|
|
root: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
|
|
}),
|
|
/**
|
|
* The components used for each slot inside.
|
|
* @default {}
|
|
*/
|
|
slots: PropTypes.shape({
|
|
root: PropTypes.elementType,
|
|
}),
|
|
/**
|
|
* The system prop that allows defining system overrides as well as additional CSS styles.
|
|
*/
|
|
sx: PropTypes.oneOfType([
|
|
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])),
|
|
PropTypes.func,
|
|
PropTypes.object,
|
|
]),
|
|
/**
|
|
* The [global variant](https://mui.com/joy-ui/main-features/global-variants/) to use.
|
|
* @default 'outlined'
|
|
*/
|
|
variant: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([
|
|
PropTypes.oneOf(['outlined', 'plain', 'soft', 'solid']),
|
|
PropTypes.string,
|
|
]),
|
|
} as any;
|
|
|
|
export default Card;
|