SelectCard

A SelectCard is a controlled component that allows users to select one or more options from a set of options. It is useful for presenting a list of options in a card-like format.

Updated in eds-core: 1.15.0

Quick Start

Installation
npm install @adaptavant/eds-core
Import
import { SelectCard } from '@adaptavant/eds-core';

ControlType

The controlType prop specifies the input type rendered within each card of the SelectCard group. It supports two values checkbox and radio. Default value is "radio".

const themeOptionsForRadio = [
	{
		value: 'light',
		name: 'theme',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeLightIcon size="20" />
				</div>
				<Text className="text-body-12">Light</Text>
			</>
		),
	},
	{
		value: 'dark',
		name: 'theme',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeDarkIcon size="20" />
				</div>
				<Text className="text-body-12">Dark</Text>
			</>
		),
	},
	{
		value: 'system',
		name: 'theme',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<DeviceDesktopIcon size="20" />
				</div>
				<Text className="text-body-12">System</Text>
			</>
		),
	},
];

const themeOptions = [
	{
		value: 'light',
		name: 'sun',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeLightIcon size="20" />
				</div>
				<Text className="text-body-12">Light</Text>
			</>
		),
	},
	{
		value: 'dark',
		name: 'moon',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeDarkIcon size="20" />
				</div>
				<Text className="text-body-12">Dark</Text>
			</>
		),
	},
	{
		value: 'system',
		name: 'system',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<DeviceDesktopIcon size="20" />
				</div>
				<Text className="text-body-12">System</Text>
			</>
		),
	},
];

const [selectedThemeOption, setSelectedThemeOption] = React.useState(['']);
const [selectedThemeOptionRadio, setSelectedThemeOptionRadio] = React.useState('');

return (
	<Box className="flex flex-col gap-4">
		<SelectCard
			controlType="radio"
			onChange={setSelectedThemeOptionRadio}
			options={themeOptionsForRadio}
			value={selectedThemeOptionRadio}
		/>
		<SelectCard
			controlType="checkbox"
			onChange={setSelectedThemeOption}
			options={themeOptions}
			value={selectedThemeOption}
		/>
	</Box>
);

Orientation

The orientation prop determines the layout of the cards within the SelectCard component. It accepts the following values:

  • horizontal: Arranges the cards in a horizontal layout. (Default)
  • vertical: Arranges the cards in a vertical layout.
const addonList = [
	{
		value: 'online',
		name: 'online',
		directSlot: (
			<>
				<Box>
					<Text className="text-body-12 font-strong">Online appointment</Text>
					<Text className="text-body-12 text-secondary">Book appointments online</Text>
				</Box>
				<Text className="text-body-12 text-secondary">Free</Text>
			</>
		),
	},
	{
		value: 'ivr',
		name: 'ivr',
		directSlot: (
			<>
				<Box>
					<Text className="text-body-12 font-strong">IVR</Text>
					<Text className="text-body-12 text-secondary">Set Interactive Voice Response for customers</Text>
				</Box>
				<Text className="text-body-12 text-secondary">Free</Text>
			</>
		),
	},
	{
		value: 'business',
		name: 'business-line',
		directSlot: (
			<>
				<Box>
					<Text className="text-body-12 font-strong">Business line</Text>
					<Text className="text-body-12 text-secondary">Dedicate local business line</Text>
				</Box>
				<Text className="text-body-12 text-secondary">$5 per number</Text>
			</>
		),
	},
];

const [selectedAddonList, setSelectedAddonList] = React.useState([]);

return (
	<SelectCard
		className="w-full"
		controlType="checkbox"
		onChange={(selectedItem) => {
			setSelectedAddonList(selectedItem);
			// You can perform additional actions here
		}}
		options={addonList}
		orientation="vertical"
		value={selectedAddonList}
	/>
);

Value & OnChange

The value prop represents the currently selected value(s) in the SelectCard component. Since SelectCard is a controlled component, this prop is required to manage the component's state externally.

  • For radio controlType: Pass a single string to represent the selected card.
  • For checkbox controlType: Pass an array of strings to represent the selected cards.

When initializing the SelectCard component in consumer applications, you can set default values by providing them through the value prop.

The onChange callback is triggered whenever the selection changes within the SelectCard component. It provides the updated value(s) as an argument:

  • For radio controlType: Returns the newly selected string value.
  • For checkbox controlType: Returns an updated array of selected values.
const themeOptions = [
	{
		value: 'light',
		name: 'sun',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeLightIcon size="20" />
				</div>
				<Text className="text-body-12">Light</Text>
			</>
		),
	},
	{
		value: 'dark',
		name: 'moon',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeDarkIcon size="20" />
				</div>
				<Text className="text-body-12">Dark</Text>
			</>
		),
	},
	{
		value: 'system',
		name: 'system',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<DeviceDesktopIcon size="20" />
				</div>
				<Text className="text-body-12">System</Text>
			</>
		),
	},
];

const [selectedThemeOption, setSelectedThemeOption] = React.useState(['light', 'dark']); // Default selected values

return (
	<SelectCard
		controlType="checkbox"
		onChange={(selectedItem) => {
			setSelectedThemeOption(selectedItem);
			// You can perform additional actions here
		}}
		options={themeOptions}
		value={selectedThemeOption}
	/>
);

Dynamic content

SelectCard has slots for dynamic contents to be rendered in view only when a particular card is selected(checked), pass dynamicSlot in your options array to achieve this behaviour.

Select Payment Type
function NameInput() {
	const [errorMsg, setErrorMsg] = React.useState(null);

	const handleInputOnChange = (e) => {
		setErrorMsg(
			e.target.value.length > 25
				? 'Name should not be more than 25 characters'
				: null
		);
	};

	return (
		<Field errorMessage={errorMsg} label="Your name" labelVisibility="hidden">
			<TextInput onChange={handleInputOnChange} placeholder="John Doe" />
		</Field>
	);
};

function CreditCardInput() {
	const [errorMsg, setErrorMsg] = React.useState(null);

	const handleInputOnChange = (e) => {
		setErrorMsg(
			e.target.value.length !== 16
				? 'Credit card number should be 16 digits'
				: null
		);
	};

	return (
		<Field
			errorMessage={errorMsg}
			label="Credit Card Number"
			labelVisibility="hidden"
		>
			<TextInput
				onChange={handleInputOnChange}
				placeholder="1234 5678 9012 3456"
			/>
		</Field>
	);
}

const dynamicOptions = [
	{
		value: 'pay-via-card',
		name: 'paymentType',
		directSlot: (
			<>
				<Text className="text-body-16 sm:text-body-12">Pay via card</Text>
				<Box className="flex items-center">
					<AmexColorIcon size="20" />
					<VisaColorIcon size="20" />
					<MastercardColorIcon size="20" />
				</Box>
			</>
		),
		dynamicSlot: (
			<Box className="flex space-x-4">
				<NameInput />
				<CreditCardInput />
			</Box>
		),
		transitionHeightClass: 'h-14', // When not passed card will grow without any css transition
	},
	{
		value: 'pay-via-UPI',
		name: 'paymentType',
		directSlot: 'Pay via upi',
		dynamicSlot: (
			<Box className="flex space-x-4">
				<NameInput />
				<CreditCardInput />
			</Box>
		),
		transitionHeightClass: 'h-14', // give maximum dynamicContent can grow - classNames will be used for transition card height
	},
	{
		value: 'pay-via-cash',
		name: 'paymentType',
		directSlot: 'Pay with cash',
		isDisabled: true, // allows you to disable particular card alone
	},
];

const [selectPaymentType, setSelectedPaymentType] = React.useState('');

return (
	<SelectCard
		className="w-full"
		controlType="radio"
		onChange={(newSelectPaymentType) => {
			setSelectedPaymentType(newSelectPaymentType);
		}}
		legend= "Select Payment Type"
		options={dynamicOptions}
		orientation="vertical"
		value={selectPaymentType}
	/>
);

Disabled

Use the isDisabled prop and set it to "true" to disable all cards in the SelectCard group. To disable an individual card, add the isdisabled property to the respective option object in the "options" array.

const themeOptions = [
	{
		value: 'light',
		name: 'sun',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeLightIcon size="20" />
				</div>
				<Text className="text-body-12">Light</Text>
			</>
		),
	},
	{
		value: 'dark',
		name: 'moon',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeDarkIcon size="20" />
				</div>
				<Text className="text-body-12">Dark</Text>
			</>
		),
	},
	{
		value: 'system',
		name: 'system',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<DeviceDesktopIcon size="20" />
				</div>
				<Text className="text-body-12">System</Text>
			</>
		),
	},
];

const themeOptionsTwo = [
	{
		value: 'light2',
		name: 'sun',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeLightIcon size="20" />
				</div>
				<Text className="text-body-12">Light</Text>
			</>
		),
	},
	{
		value: 'dark2',
		name: 'moon',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<ModeDarkIcon size="20" />
				</div>
				<Text className="text-body-12">Dark</Text>
			</>
		),
	},
	{
		value: 'system2',
		name: 'system',
		directSlot: (
			<>
				<div className="w-5 h-5 flex items-center justify-center">
					<DeviceDesktopIcon size="20" />
				</div>
				<Text className="text-body-12">System</Text>
			</>
		),
		isDisabled: true, // allows you to disable particular card alone
	},
];

const [selectedThemeOption, setSelectedThemeOption] = React.useState('');

return (
	<Box className="flex flex-col gap-4">
		<SelectCard
			controlType="checkbox"
			isDisabled={true} // Disabled all cards
			onChange={(selectedItem) => {
				setSelectedThemeOption(selectedItem);
			}}
			options={themeOptions}
			value={selectedThemeOption}
		/>
		<SelectCard 
			controlType="checkbox"
			onChange={(selectedItem) => {
				setSelectedThemeOption(selectedItem);
			}}
			options={themeOptionsTwo} // Disabled single card in list
			value={selectedThemeOption}
		/>
	</Box>
);

Style API

Our design system components include style props that allow you to easily customize different parts of each component to match your design needs.

Please refer to the Style API documentation for more insights.

SelectCard parts

Select Payment Type
function NameInput() {
	return (
		<Field label="Your name" labelVisibility="hidden">
			<TextInput placeholder="John Doe" />
		</Field>
	);
};
const dynamicOptions = [
	{
		value: 'pay-via-card',
		name: 'paymentType',
		directSlot: (
			<>
				<Text className="text-body-16 sm:text-body-12">Pay via card</Text>
				<Box className="flex items-center">
					<AmexColorIcon size="20" />
					<VisaColorIcon size="20" />
					<MastercardColorIcon size="20" />
				</Box>
			</>
		),
		dynamicSlot: <NameInput />,
		// @ts-expect-error
		classNames: { // classNames to target primitive parts. Note: This is not a valid prop for SelectCardProps thats why we have "ts-error" comment
			icon: 'fill-caution',
			control: 'bg-critical hover:bg-critical-hover border-[orange] border-2',
		},
	},
];

const [selectPaymentType, setSelectedPaymentType] = React.useState('');

return (
	<SelectCard
		className="w-96 m-auto"
        classNames={{
            legend: 'text-positive sm:font-stronger',
            controlWrapper: 'bg-positive px-0.5 rounded-4px',
            card: 'px-2 bg-positive-hover',
            cardRow: 'bg-caution-secondary',
            focusIndicator: 'border-4 border-input-critical',
            directContentWrapper: 'text-link bg-positive-secondary-pressed ps-2',
            dynamicContentWrapper: 'bg-caution-hover',
        }}
		controlType="radio"
		onChange={(newSelectPaymentType) => {
			setSelectedPaymentType(newSelectPaymentType);
		}}
		legend= "Select Payment Type"
		options={dynamicOptions}
		orientation="vertical"
		value={selectPaymentType}
	/>
);

Stylable Parts

Description

root

The root container of all cards, fieldset that is wrapping all inner element.

legend

Legend element in fieldset. First child of fieldset.

controlWrapper

Targets root part(AlignChildToText) of primitives.

control

The container for the control element, which visually represents the checked/unchecked state. Pass in "options" object.

icon

The icon displayed within the checkbox/radio when it is checked. Pass in "options" object.

card

Wrapper element of each card, consist of control, directSlot and dynamicSlot

focusIndicator

Targets focus ring of the card.

directContentWrapper

Wrapper element for directSlot.

dynamicContentWrapper

Wrapper element for dynamicSlot.