import * as stylex from '@stylexjs/stylex'; import React from 'react'; import { X } from '../../icons/glyphs/x'; import { useFlowStepActive } from '../../primitives/flow'; import { useTransition } from '../../primitives/hooks'; import { useRender } from '../../primitives/utils'; import type { MosaicComponentProps } from '../../props'; import { mergeStyleProps, themeProps } from '../../props'; import { reset } from '../../styles/reset.styles'; import { hasMessage, useHeldMessage } from '../../utils/feedback'; import type { BannerRootProps } from '../banner'; import { Banner } from '../banner'; import { Branding } from '../branding'; import { Button } from '../button'; import { ButtonContext } from '../button/button.context'; import type { DialogContextValue } from '../dialog'; import { Dialog, DialogContext, isInDialog } from '../dialog'; import { Icon } from '../icon'; import { cardContentMarker, cardFooterMarker } from './card.markers.stylex'; import * as slots from './card.styles'; type CardElevation = 'card' | 'flush' | 'overlay'; export type CardSize = keyof typeof slots.sizes; export type CardAlign = keyof typeof slots.headerAlign; const DEFAULT_ELEVATION: CardElevation = 'card'; // The enclosing card and the dialog it was rendered in. Context crosses portals, so a card in a // dialog opened from inside another card is still the dialog's outermost one. const CardContext = React.createContext<{ elevation: CardElevation; align: CardAlign; dialog: DialogContextValue | null; } | null>(null); function CardBranding() { return (
); } export interface CardProps extends MosaicComponentProps<'div'> { /** Surface treatment applied to the card. @default 'card' */ elevation?: CardElevation; /** The card's width. `lg` is for forms that need the room, like a tag input. @default 'md' */ size?: CardSize; /** Aligns the content of every `Card.Header` in the card. @default 'start' */ align?: CardAlign; /** * Signs the foot of the card with "Secured by Clerk". An instance that has paid the branding off * carries none of it, so a connected surface passes `displayConfig.branded` here. * * @default true */ renderBranding?: boolean; } const Root = React.forwardRef(function CardRoot( { elevation = DEFAULT_ELEVATION, size = 'md', align = 'start', renderBranding = true, render, xstyle, children, ...rest }, ref, ) { const dialog = React.useContext(DialogContext); const enclosing = React.useContext(CardContext); const isSurface = (enclosing === null || enclosing.dialog !== dialog) && elevation !== 'flush'; const context = React.useMemo(() => ({ elevation, align, dialog }), [elevation, align, dialog]); const element = useRender({ defaultTagName: 'div', render, ref, props: { ...mergeStyleProps( themeProps('card-root', { elevation, size, align }), stylex.props( reset.base, slots.root.base, slots.root[elevation], slots.sizes[size], isSurface && slots.surface.root, xstyle, ), rest, ), children: ( <> {children} {renderBranding ? : null} ), }, }); return {element}; }); /** * Dismisses the dialog from inside the header, in flow, so the header reserves the width it takes * and a long title cannot run under it. `Dialog.CloseButton` stays the corner affordance, for the * `Profile`, which anchors its own. */ function HeaderCloseButton() { return ( (