@kiwicom/orbit-components
Version:
Orbit-components is a React component library which provides developers with the easiest possible way of building Kiwi.com's products.
199 lines (198 loc) • 9.29 kB
JavaScript
"use client";
import * as React from "react";
import cx from "clsx";
import Heading from "../Heading";
import { FakeCheckbox } from "../Checkbox";
import Text from "../Text";
import handleKeyDown from "../utils/handleKeyDown";
/**
* @orbit-doc-start
* README
* ----------
* # ListChoice
*
* To implement ListChoice component into your project you'll need to add the import:
*
* ```jsx
* import ListChoice from "@kiwicom/orbit-components/lib/ListChoice";
* ```
*
* After adding import into your project you can use it simply like:
*
* ```jsx
* <ListChoice title="My Choice" />
* ```
*
* ## Props
*
* Table below contains all types of the props available in the ListChoice component.
*
* | Name | Type | Default | Description |
* | :---------- | :------------------------- | :------ | :---------------------------------------------------------------------------------------------------------------------------------------------- |
* | dataTest | `string` | | Optional prop for testing purposes. |
* | id | `string` | | Set `id` for `ListChoice`. |
* | description | `Translation` | | The additional info about the ListChoice. |
* | disabled | `boolean` | | If `true`, the ListChoice won't perform any `onClick` action and if `selectable` is set to `true`, the check box glyph will use disabled state. |
* | icon | `React.Node` | | The icon on the left of the ListChoice. |
* | onClick | `event => void \| Promise` | | Function for handling onClick event. |
* | selectable | `boolean` | | If `true`, the check box glyph appears on the right size and it will be possible to select the ListChoice. |
* | selected | `boolean` | `false` | If `true`, the check box glyph will be checked. |
* | **title** | `Translation` | | The title of the ListChoice. |
* | action | `React.Node` | | Area for action elements, like Button. |
* | role | `string` | | ARIA role. Defaults to "checkbox" when selectable, "button" when no action is provided or undefined otherwise. |
* | tabIndex | `number` | `0` | Specifies the tab order of an element. |
*
*
* Accessibility
* -------------
* ## Accessibility
*
* The ListChoice component has been designed with accessibility in mind, providing interactive list options that can function as buttons or selectable items.
*
* ### Accessibility Props
*
* The following props are available to improve the accessibility of your ListChoice component:
*
* | Name | Type | Description |
* | :------- | :------- | :------------------------------------------------------------------------------------------------- |
* | role | `string` | Specifies the ARIA role of the component. If not provided, the role is set automatically. |
* | tabIndex | `number` | Specifies the tab order of the component. Default is `0`, automatically set to `-1` when disabled. |
*
* ### Automatic Accessibility Features
*
* The ListChoice component handles several important ARIA attributes automatically:
*
* - `aria-disabled` is automatically set to match the `disabled` prop value
* - `aria-checked` is automatically set to the value of the `selected` prop when `selectable` is true
* - Role is automatically determined in this order:
* - If a `role` prop is explicitly provided, it will use that value
* - Otherwise, if `selectable` is true, it uses "checkbox"
* - Otherwise, if `action` is not provided, it uses "button"
* - If none of these conditions are met, the role will be undefined
*
* ### Keyboard Navigation
*
* - **Enter** or **Space** keys trigger the `onClick` action when the component is focused
* - **Tab** key moves focus to and from the component following the document's tab order
*
* ### Best practices
*
* - Provide a descriptive `title` that clearly explains the purpose of the list item
* - Use `description` to provide additional context when the action isn't self-explanatory
* - When using `icon`, ensure it visually reinforces the action and is marked as decorative
* - When using a custom `role`, ensure it matches the component's behavior (e.g., use "link" only if it navigates to another page)
* - When using the `selectable` prop, group related options together and ensure your implementation correctly updates the selected state
*
* ### Examples
*
* #### Navigation Option
*
* In this example, the ListChoice functions like a navigation link:
*
* ```jsx
* <ListChoice
* title="Flight details"
* description="View your booking information"
* icon={<Airplane ariaHidden />}
* role="link"
* onClick={() => navigate("/booking-details")}
* />
* ```
*
* Screen reader announces: "Flight details, View your booking information, link"
*
* #### Selectable Option in a Group
*
* ```jsx
* <Stack>
* <ListChoice
* title="Window seat"
* selectable
* selected={selectedSeat === "window"}
* onClick={() => setSelectedSeat("window")}
* />
* <ListChoice
* title="Aisle seat"
* selectable
* selected={selectedSeat === "aisle"}
* onClick={() => setSelectedSeat("aisle")}
* />
* </Stack>
* ```
*
* Screen reader announces: "Window seat, checkbox, checked" (when selected)
*
* #### Disabled Option
*
* ```jsx
* <ListChoice
* title="Unavailable flight"
* description="This flight is fully booked"
* disabled
* icon={<CloseCircle ariaHidden />}
* />
* ```
*
* Screen reader announces: "Unavailable flight, This flight is fully booked, button, disabled"
*
* #### Option with Action Component
*
* ```jsx
* <ListChoice
* title="Priority boarding"
* description="Board the plane first"
* action={
* <Button size="small" type="secondary">
* Add €10
* </Button>
* }
* />
* ```
*
* Screen reader announces: "Priority boarding, Board the plane first" for the ListChoice content and then "Add €10, button" when navigating to the action button.
*
*
* @orbit-doc-end
*/
const ListChoice = ({
dataTest,
id,
icon,
action,
title,
description,
selectable,
role,
onClick,
tabIndex = 0,
selected = false,
disabled,
ref
}) => {
return /*#__PURE__*/React.createElement("div", {
className: cx("orbit-list-choice", "py-300 px-400 box-border flex w-full items-center", "border-b-cloud-normal bg-white-normal border-b border-solid", "duration-fast transition-[background-color] ease-in-out", disabled ? "cursor-not-allowed" : "hover:bg-cloud-light cursor-pointer", "hover:outline-none [&_button]:hover:bg-transparent", "[&_.orbit-checkbox-label]:w-auto"),
onClick: !disabled ? onClick : undefined,
"data-test": dataTest,
id: id,
ref: ref,
onKeyDown: !disabled ? handleKeyDown(onClick) : undefined,
tabIndex: tabIndex || disabled ? -1 : 0,
"data-title": title,
"aria-disabled": disabled,
"aria-checked": selectable ? selected : undefined,
role: role || selectable && "checkbox" || !action && "button" || undefined
}, icon && /*#__PURE__*/React.createElement("div", {
className: cx("me-200 h-icon-medium flex flex-none items-center self-start", "[&_svg]:size-icon-medium [&_svg]:text-icon-primary-foreground [&_svg]:self-center", "[&_svg]:duration-fast [&_svg]:transition-[color] [&_svg]:ease-in-out")
}, icon), /*#__PURE__*/React.createElement("div", {
className: "pe-300 flex w-full flex-col justify-center"
}, /*#__PURE__*/React.createElement(Heading, {
type: "title4"
}, title), description && /*#__PURE__*/React.createElement(Text, {
type: "secondary",
size: "small"
}, description)), selectable && /*#__PURE__*/React.createElement(FakeCheckbox, {
checked: selected,
disabled: disabled
}), !selectable && action);
};
export default ListChoice;