@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.
248 lines (246 loc) • 11.8 kB
JavaScript
"use strict";
"use client";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
exports.__esModule = true;
exports.default = void 0;
var React = _interopRequireWildcard(require("react"));
var _clsx = _interopRequireDefault(require("clsx"));
var _consts = _interopRequireDefault(require("./consts"));
var _useTheme = _interopRequireDefault(require("../hooks/useTheme"));
const CircleLoader = ({
animationDelay
}) => {
return /*#__PURE__*/React.createElement("div", {
className: "animate-loader bg-cloud-dark size-200 me-150 rounded-full [&:nth-child(3)]:m-0",
style: {
animationDelay
}
});
};
const Loader = ({
type,
customSize,
title,
ariaHidden
}) => {
const theme = (0, _useTheme.default)();
const isCircledIcon = type === _consts.default.BOX_LOADER || type === _consts.default.SEARCH_LOADER || type === _consts.default.INLINE_LOADER;
if (customSize) {
return /*#__PURE__*/React.createElement("svg", {
viewBox: `0 0 ${customSize} ${customSize}`,
className: "orbit-loading-spinner animate-spinner",
style: {
height: `${customSize}px`,
width: `${customSize}px`
},
role: "img",
"aria-hidden": ariaHidden
}, /*#__PURE__*/React.createElement("title", null, title), /*#__PURE__*/React.createElement("circle", {
cx: "50%",
cy: "50%",
fill: "transparent",
strokeWidth: "3px",
stroke: type === _consts.default.BUTTON_LOADER ? "current" : theme.orbit.paletteCloudDark,
strokeLinecap: "round",
strokeDasharray: `${customSize * 3 + 8}px`,
strokeDashoffset: `${Number(customSize) + 24}px`,
r: customSize / 2 - 2
}));
}
if (isCircledIcon) {
return /*#__PURE__*/React.createElement("div", {
className: "flex items-center justify-center",
"aria-hidden": ariaHidden,
role: "img",
"aria-label": title
}, /*#__PURE__*/React.createElement(CircleLoader, null), /*#__PURE__*/React.createElement(CircleLoader, {
animationDelay: "0.1s"
}), /*#__PURE__*/React.createElement(CircleLoader, {
animationDelay: "0.2s"
}));
}
return /*#__PURE__*/React.createElement("svg", {
viewBox: "0 0 40 40",
className: "orbit-loading-spinner animate-spinner size-1000",
stroke: type === _consts.default.BUTTON_LOADER ? "currentColor" : theme.orbit.paletteCloudDark,
role: "img",
"aria-hidden": ariaHidden
}, /*#__PURE__*/React.createElement("title", null, title), /*#__PURE__*/React.createElement("circle", {
cx: "50%",
cy: "50%",
fill: "transparent",
strokeWidth: "3px",
stroke: type === _consts.default.BUTTON_LOADER ? "current" : theme.orbit.paletteCloudDark,
strokeLinecap: "round",
r: "18",
strokeDasharray: "128px",
strokeDashoffset: "64px"
}));
};
/**
* @orbit-doc-start
* README
* ----------
* # Loading
*
* To implement Loading component into your project you'll need to add the import:
*
* ```jsx
* import Loading from "@kiwicom/orbit-components/lib/Loading";
* ```
*
* After adding import into your project you can use it simply like:
*
* ```jsx
* <Loading title="Loading" />
* ```
*
* ## Props
*
* Table below contains all types of the props available in the Loading component.
*
* | Name | Type | Default | Description |
* | :---------- | :------------------------ | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------ |
* | children | `React.Node` | | The content that is shown when `loading` is **not** `true`. |
* | dataTest | `string` | | Optional prop for testing purposes. |
* | id | `string` | | Set `id` for `Loading`. |
* | loading | `boolean` | `false` | If `true`, the Loading will be displayed. Loading which doesn't have children is always shown, even if `loading` prop is set to `false`. |
* | type | [`enum`](#enum) | `"pageLoader"` | The type of the Loading. |
* | customSize | `number` | | Allows you to define custom size for circle loader. |
* | asComponent | `string \| React.Element` | `"div"` | The component used for the root node. |
* | text | `Translation` | | Text to be displayed below the loader image. Cannot be used with `title` or `ariaHidden`. See Accessibility tab. |
* | title | `string` | | Provides an accessible name for the loading indicator that is announced by screen readers. Cannot be used with `text` or `ariaHidden`. See Accessibility tab. |
* | ariaHidden | `boolean` | | If `true`, hides the loading indicator from screen readers. Cannot be used with `text` or `title`. See Accessibility tab. |
*
* ### enum
*
* | type |
* | :--------------- |
* | `"buttonLoader"` |
* | `"searchLoader"` |
* | `"boxLoader"` |
* | `"pageLoader"` |
* | `"inlineLoader"` |
*
*
* Accessibility
* -------------
* ## Accessibility
*
* The Loading component has been designed with accessibility in mind, providing features that enhance the experience for users of assistive technologies.
*
* ### Accessibility Props
*
* The following props are available to improve the accessibility of your Loading component:
*
* | Name | Type | Description |
* | :---------- | :------------------------ | :---------------------------------------------------------------------------------------------------------------------- |
* | title | `string` | Provides an accessible name for the loading indicator that is announced by screen readers but not visually displayed. |
* | ariaHidden | `boolean` | When `true`, hides the entire component from screen readers. Useful when loading state is conveyed through other means. |
* | text | `Translation` | Text displayed below the loader image. When provided, this text is announced by screen readers instead of the `title`. |
* | asComponent | `string \| React.Element` | Allows rendering the loading indicator with a different HTML element to maintain semantic structure. |
*
* One of these props must be provided: `text`, `title`, or `ariaHidden={true}`. They are mutually exclusive and cannot be used together.
*
* ### Automatic Accessibility Features
*
* The Loading component automatically implements the following accessibility features:
*
* - Uses semantic SVG elements with proper ARIA attributes for the loading indicator
* - Automatically adjusts announcements based on the presence of `text` or `title` props:
* - When `text` is provided, the visual loading indicator is hidden from screen readers and only the text is announced
* - When `title` is provided, it serves as an accessible name for the loading indicator
* - When `ariaHidden` is `true`, the entire component is hidden from screen readers
* - Always renders as a `div` when `text` is provided to ensure proper structure
*
* ### Best Practices
*
* - Always provide either `text`, `title`, or `ariaHidden={true}` to ensure proper accessibility:
*
* - Use `text` when you want both visible and announced text
* - Use `title` for loading indicators without visible text
* - Use `ariaHidden={true}` when:
* - Loading state is conveyed through other visible text on the page
* - Multiple loading indicators are present and only one should be announced
* - Always ensure both `title` and `text` are properly translated
*
* - When using `asComponent`:
*
* - By default, Loading renders as a `div` element
* - Use `asComponent` when the Loading component is wrapped by another component that requires a [phrasing element](https://developer.mozilla.org/en-US/docs/Web/HTML/Content_categories) as children
* - Ensure the chosen element maintains proper semantic structure:
*
* ```jsx
* <Button>
* <Loading title="Title of the button is loading" asComponent="span" />
* </Button>
* ```
*
* ### Examples
*
* #### Basic loading with accessible name
*
* ```jsx
* <Loading title="Loading page content" type="pageLoader" />
* ```
*
* Screen reader announces: "Loading page content, image"
*
* #### Loading with visible text
*
* ```jsx
* <Loading text="Please wait while we process your request" type="boxLoader" />
* ```
*
* Screen reader announces: "Please wait while we process your request"
*
* #### Multiple loaders with controlled announcements
*
* ```jsx
* <Stack>
* <Loading type="searchLoader" title="Loading search results" />
* <Loading
* type="inlineLoader"
* ariaHidden={true} // This loader won't be announced
* />
* </Stack>
* ```
*
* Screen reader only announces: "Loading search results, image"
*
*
* @orbit-doc-end
*/
const Loading = ({
loading = false,
asComponent = "div",
type = _consts.default.PAGE_LOADER,
text,
children,
dataTest,
customSize,
title,
id,
ariaHidden
}) => {
const Element = text ? "div" : asComponent;
return /*#__PURE__*/React.createElement(React.Fragment, null, Boolean(children) && !loading ? children : /*#__PURE__*/React.createElement(Element, {
className: (0, _clsx.default)(["items-center", "overflow-hidden", "box-border", type === _consts.default.BUTTON_LOADER && "[&_.orbit-loading-spinner]:size-icon-medium absolute start-0 top-0 size-full justify-center", type === _consts.default.SEARCH_LOADER && "h-1000 justify-start", type === _consts.default.INLINE_LOADER && "inline-flex min-h-[19px] justify-center", type !== _consts.default.INLINE_LOADER && "flex", type === _consts.default.BOX_LOADER && "h-[80px] justify-center", type === _consts.default.PAGE_LOADER && "h-[120px] flex-col justify-center"]),
style: {
height: customSize
},
"data-test": dataTest,
id: id,
"aria-hidden": ariaHidden ? "true" : undefined
}, /*#__PURE__*/React.createElement(Loader, {
title: title,
type: type,
customSize: customSize,
ariaHidden: text ? "true" : undefined
}), type !== _consts.default.BUTTON_LOADER && Boolean(text) && /*#__PURE__*/React.createElement("div", {
className: (0, _clsx.default)(["font-base text-normal text-cloud-dark leading-normal", type === _consts.default.PAGE_LOADER ? "mt-400" : "ms-300"])
}, text)));
};
Loading.displayName = "Loading";
var _default = exports.default = Loading;