UNPKG

@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.

194 lines (192 loc) 10.4 kB
"use strict"; "use client"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default; exports.__esModule = true; exports.default = void 0; var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")); var React = _interopRequireWildcard(require("react")); var _clsx = _interopRequireDefault(require("clsx")); var _CloseCircle = _interopRequireDefault(require("../icons/CloseCircle")); var _consts = require("./consts"); const buttonClickEmulation = (ev, callback) => { if (ev && ev.code === "Space") { ev.preventDefault(); if (callback) callback(); } else if (ev && ev.code === "Enter") { if (callback) callback(); } }; /** * @orbit-doc-start * README * ---------- * # Tag * * To implement Tag component into your project you'll need to add the import: * * ```jsx * import Tag from "@kiwicom/orbit-components/lib/Tag"; * ``` * * After adding import into your project you can use it simply like: * * ```jsx * <Tag>Hello!</Tag> * ``` * * ## Props * * Table below contains all types of the props available in the Tag component. * * | Name | Type | Default | Description | * | :----------- | :---------------------- | :-------- | :--------------------------------------------------------------------------------------------------- | * | **children** | `React.Node` | | The content of the Tag. | * | dataTest | `string` | | Optional prop for testing purposes. | * | iconLeft | `React.Node` | | The displayed icon on the left. | * | id | `string` | | Set `id` for `Tag`. | * | dateTag | `boolean` | | Optional prop, if it's true, selected color has a different background. | * | type | [`enum`](#enum) | `neutral` | The color type of the Tag. | * | onClick | `() => void \| Promise` | | Function for handling the onClick event. | * | onRemove | `() => void \| Promise` | | Function for handling the onClick event of the close icon. [See Functional specs](#functional-specs) | * | selected | `boolean` | `false` | If `true`, the Tag will have selected styles. | * | size | [`enum`](#enum) | `normal` | Size of the Tag. | * | ref | `func` | | Prop for forwarded ref of the Tag. | * | labelDismiss | `string` | | Optional prop for `aria-label` attribute of dismiss button (available when `onRemove` is not null). | * * ### enum * * | size | type | * | :--------- | :---------- | * | `"small"` | `"neutral"` | * | `"normal"` | `"colored"` | * * ## Functional specs * * - By passing the `onRemove` the close icon will appear on the right side of the Tag. * * * Accessibility * ------------- * ## Accessibility * * The Tag component has been designed with accessibility in mind, providing proper semantic structure and keyboard navigation for interactive labels that can be selected, clicked, or removed. * * ### Accessibility Props * * **Tag props:** * * | Name | Type | Description | * | :----------- | :----- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | * | labelDismiss | string | Specifies the accessible name for the dismiss button when `onRemove` is provided. Required for screen reader users to understand the purpose of the remove action. | * * ### Automatic Accessibility Features * * - The component automatically manages ARIA attributes: * - When `onClick` is provided, the tag receives `role="button"` and `tabIndex="0"` to make it keyboard accessible * - When `onRemove` is provided, the dismiss button receives `role="button"`, `tabIndex="0"`, and `aria-label` from the `labelDismiss` prop * - The dismiss button is properly isolated from the main tag interaction to prevent nested interactive elements * - Focus management is handled automatically: * - Interactive tags are included in the tab order with proper focus indicators * - The dismiss button can be focused independently from the main tag when both are present * - Keyboard navigation is fully supported: * - **Space** and **Enter** keys activate both the main tag and dismiss button * - Event propagation is properly managed to prevent conflicts between tag and dismiss actions * * ### Best Practices * * - Always provide a `labelDismiss` prop when using `onRemove` to ensure screen reader users understand the purpose of the dismiss button. * - Use clear, descriptive text content for tags to help users understand their meaning and purpose. * - Use the `selected` prop to indicate which tags are currently active or applied, providing clear visual feedback through enhanced contrast and styling. * * ### Icons * * When using icons in tags: * * - Provide an `ariaLabel` if the icon has a meaning by itself that is not obvious to screen readers, regardless of the existence of text * - If the icon is decorative and the badge contains text, the icon should be marked as `aria-hidden="true"` * * ### Examples * * #### Basic Interactive Tag * * ```jsx * <Tag onClick={() => console.log("Tag clicked")}>Transport</Tag> * ``` * * Screen reader announces: "Transport, button" * * #### Tag with Both Click and Remove Actions * * ```jsx * <Tag * onClick={() => console.log("Tag clicked")} * onRemove={() => console.log("Tag removed")} * labelDismiss="Remove transport filter" * > * Transport * </Tag> * ``` * * Screen reader announces: "Transport, button" for the main tag, then "Remove transport filter, button" for the dismiss button * * #### Tag with Icon * * ```jsx * <Tag iconLeft={<Icons.PlusMinus ariaHidden />} onClick={() => console.log("Tag clicked")}> * Add Filter * </Tag> * ``` * * Screen reader announces: "Add Filter, button" * * * @orbit-doc-end */ const Tag = ({ selected, children, iconLeft, size = _consts.SIZES.NORMAL, onClick, onRemove, id, dataTest, type = _consts.TYPES.NEUTRAL, dateTag, labelDismiss, ref }) => { return /*#__PURE__*/React.createElement("div", { className: (0, _clsx.default)("orbit-tag", "font-base rounded-150 box-border inline-flex items-stretch justify-center font-medium", "duration-fast transition-[color,_background-color,_box-shadow] ease-in-out", size === _consts.SIZES.SMALL && "text-small leading-small", size === _consts.SIZES.NORMAL && "text-normal leading-normal", !!onRemove && !onClick && "pointer-events-none", selected && ["text-white-normal", dateTag ? ["bg-ink-light-hover", !!(onClick || onRemove) && "hover:bg-ink-light-active focus:bg-ink-light-active active:bg-ink-light-hover"] : ["bg-blue-normal", !!(onClick || onRemove) && "hover:bg-blue-normal-hover focus:bg-blue-normal-hover active:bg-blue-normal-active"]], type === _consts.TYPES.NEUTRAL && ["text-ink-dark", !selected && ["bg-cloud-normal", !!(onClick || onRemove) && "hover:bg-cloud-normal-hover focus:bg-cloud-normal-hover active:bg-cloud-normal-active"]], type === _consts.TYPES.COLORED && ["text-blue-darker", !selected && ["bg-blue-light", !!(onClick || onRemove) && "hover:bg-blue-light-hover focus:bg-blue-light-hover active:bg-blue-light-active"]]), "data-test": dataTest }, /*#__PURE__*/React.createElement("div", (0, _extends2.default)({ className: (0, _clsx.default)("flex items-center", "focus:rounded-150 focus:z-default", onRemove ? "ps-200 py-200 pe-100 rounded-l-150" : "p-200 rounded-150", selected && [dateTag ? "focus-visible:bg-ink-light-active" : "focus-visible:bg-blue-normal-hover"], type === _consts.TYPES.NEUTRAL && !selected && "focus-visible:bg-cloud-normal-hover", type === _consts.TYPES.COLORED && !selected && "focus-visible:bg-blue-light-hover"), id: id, ref: ref }, onClick && { role: "button", tabIndex: 0, onClick, onKeyDown: ev => buttonClickEmulation(ev, onClick) }), iconLeft && /*#__PURE__*/React.createElement("div", { className: "pe-200 [&_svg]:size-icon-small flex flex-row items-center justify-center" }, iconLeft), children), onRemove && /*#__PURE__*/React.createElement("div", { className: (0, _clsx.default)("orbit-tag-close-container", "pe-200 ps-100 rounded-r-150 flex items-center justify-center", "duration-fast transition-[color,_opacity] ease-in-out", "focus:rounded-150 focus:z-default focus:opacity-100 active:opacity-100", !onClick && "pointer-events-auto", !selected && (type === _consts.TYPES.NEUTRAL ? "text-ink-normal active:text-ink-dark focus-visible:bg-cloud-normal-hover" : "text-blue-darker"), !selected && _consts.TYPES.COLORED && "focus-visible:bg-blue-light-hover", selected ? "text-white-normal opacity-100" : "opacity-50", selected && [dateTag ? "focus-visible:bg-ink-light-active" : "focus-visible:bg-blue-normal-hover"]), tabIndex: 0, "aria-label": labelDismiss, role: "button", onKeyDown: ev => { ev.stopPropagation(); buttonClickEmulation(ev, onRemove); }, onClick: ev => { ev.stopPropagation(); onRemove(); } }, /*#__PURE__*/React.createElement(_CloseCircle.default, { size: "small", ariaHidden: true }))); }; var _default = exports.default = Tag;