/** * External dependencies */ import classnames from 'classnames'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { isValidElement } from '@wordpress/element'; import { DropdownMenu, __experimentalHStack as HStack } from '@wordpress/components'; // eslint-disable-line @wordpress/no-unsafe-wp-apis import { moreVertical } from '@wordpress/icons'; /** * Internal dependencies */ import Badge from '../badge'; import Button from '../button'; import Card from '../card'; import './style.scss'; type BadgeLevel = 'default' | 'info' | 'success' | 'warning' | 'error'; type CardFeatureIcon = { /** The icon node to render (e.g. a WordPress component). */ node: React.ReactNode; /** SVG fill colour, applied via currentColor. */ fill?: string; /** Background colour for the icon container. */ backgroundColor?: string; /** * Border-radius of the icon container. * 'small' uses $radius-small (2px), 'full' uses $radius-round (50%). * Only relevant when backgroundColor is set. */ radius?: 'small' | 'full'; }; type MoreControl = { title: string; onClick: () => void; icon?: JSX.Element; }; type CardFeatureProps = { title: string; description?: string; /** Icon shown beside the title: a descriptor (coloured badge) or a ready element rendered as-is. */ icon?: CardFeatureIcon | React.ReactElement; /** Whether the feature is currently enabled. */ enabled?: boolean; /** * When set, the card enters the "unmet requirements" state: an error * badge displays this string and the title/description are muted. By * default the primary button is disabled — set `requirementsActionable` * if the primary button is the remediation for the unmet requirement. */ requirements?: string; /** * When `requirements` is set, keep the primary button clickable so the * user can remediate the unmet requirement from this card, and keep the * "More" dropdown available — the feature is degraded but still operable * (e.g. can be disabled), unlike a hard-locked requirement. */ requirementsActionable?: boolean; /** Primary button label when not enabled. Default: "Enable". */ enableLabel?: string; /** Show the primary button as busy (spinner) and disabled while an action is in flight. */ busy?: boolean; /** Primary button label when enabled. Default: "Configure". */ configureLabel?: string; /** Called when the primary button is clicked and the feature is not enabled. */ onEnable?: () => void; /** Called when the primary button is clicked and the feature is enabled. */ onConfigure?: () => void; /** Controls rendered inside the "More" dropdown, shown when enabled — including the unmet-requirements state when `requirementsActionable`. */ moreControls?: MoreControl[]; /** Badge text shown when enabled. Default: "Enabled". */ badgeText?: string; /** Badge level shown when enabled. Default: "success". */ badgeLevel?: BadgeLevel; className?: string; }; /** * CardFeature component. * * A card for presenting a named feature or setting with a predictable * action model: a primary button, an optional "More" dropdown when enabled, * and an automatic badge reflecting the current state. */ const CardFeature = ( { title, description, icon, enabled = false, requirements, requirementsActionable = false, enableLabel, busy = false, configureLabel, onEnable, onConfigure, moreControls, badgeText, badgeLevel = 'success', className, }: CardFeatureProps ) => { const isMuted = !! requirements; const classes = classnames( 'newspack-card-feature', className, { 'newspack-card-feature--muted': isMuted, } ); let badge: { text: string; level: BadgeLevel } | undefined; if ( requirements ) { badge = { text: requirements, level: 'error' }; } else if ( enabled ) { badge = { text: badgeText ?? __( 'Enabled', 'newspack-plugin' ), level: badgeLevel }; } const isConfigureState = enabled && ! requirements; const buttonLabel = isConfigureState ? configureLabel ?? __( 'Configure', 'newspack-plugin' ) : enableLabel ?? __( 'Enable', 'newspack-plugin' ); const showMoreControls = enabled && !! moreControls?.length && ( ! requirements || requirementsActionable ); const handleButtonClick = () => { if ( isConfigureState ) { onConfigure?.(); } else { onEnable?.(); } }; const iconDescriptor = icon && ! isValidElement( icon ) ? ( icon as CardFeatureIcon ) : null; const iconClasses = iconDescriptor ? classnames( 'newspack-card-feature__icon', { 'newspack-card-feature__icon--radius-small': !! iconDescriptor.backgroundColor && iconDescriptor.radius !== 'full', 'newspack-card-feature__icon--radius-full': iconDescriptor.radius === 'full', } ) : undefined; let renderedIcon = null; if ( isValidElement( icon ) ) { renderedIcon = icon; } else if ( iconDescriptor ) { renderedIcon = (
{ iconDescriptor.node }
); } return (

{ title }

{ description &&

{ description }

}
{ renderedIcon }
{ showMoreControls && ( ) } { badge && } ), } } /> ); }; export default CardFeature;