colibri-ui 0.8.97 → 0.8.99

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. package/Accordion/index.js +4 -2
  2. package/Accordion/index.props.d.ts +2 -1
  3. package/Avatar/index.js +2 -5
  4. package/Avatar/index.props.d.ts +2 -1
  5. package/Badge/Control/index.props.d.ts +2 -1
  6. package/Badge/index.js +1 -2
  7. package/Button/index.js +0 -1
  8. package/Checkbox/index.js +4 -2
  9. package/Checkbox/index.props.d.ts +2 -1
  10. package/Chip/index.js +2 -4
  11. package/Chip/index.props.d.ts +2 -2
  12. package/ColorPicker/ColorPicker.d.ts +1 -1
  13. package/ColorPicker/ColorPicker.js +4 -2
  14. package/ColorPicker/index.props.d.ts +2 -1
  15. package/Counter/index.js +4 -2
  16. package/Counter/index.props.d.ts +2 -1
  17. package/Datepicker/index.d.ts +1 -1
  18. package/Datepicker/index.js +5 -3
  19. package/Datepicker/index.props.d.ts +2 -1
  20. package/DotsLoader/index.js +5 -2
  21. package/DotsLoader/index.props.d.ts +2 -1
  22. package/Dropdown/index.js +2 -2
  23. package/Dropdown/index.props.d.ts +2 -1
  24. package/Grid/index.js +3 -1
  25. package/Grid/index.props.d.ts +2 -1
  26. package/InputRadio/index.js +3 -3
  27. package/InputRadio/index.props.d.ts +2 -2
  28. package/Menu/components/MenuItem/index.js +0 -1
  29. package/Menu/components/MenuItem/index.props.d.ts +2 -1
  30. package/Menu/index.d.ts +1 -1
  31. package/Menu/index.js +4 -2
  32. package/Menu/index.props.d.ts +2 -1
  33. package/Modal/index.js +3 -1
  34. package/Modal/index.props.d.ts +2 -1
  35. package/MultiSelect/index.js +3 -1
  36. package/NavigationMenu/NavigationMenu.d.ts +3 -0
  37. package/NavigationMenu/NavigationMenu.js +170 -0
  38. package/NavigationMenu/NavigationMenu.module.css +67 -0
  39. package/NavigationMenu/components/MoreMenu/MoreMenu.module.css +19 -0
  40. package/NavigationMenu/components/MoreMenu/index.d.ts +16 -0
  41. package/NavigationMenu/components/MoreMenu/index.js +155 -0
  42. package/NavigationMenu/components/NavigationLink/NavigationLink.module.css +59 -0
  43. package/NavigationMenu/components/NavigationLink/index.d.ts +16 -0
  44. package/NavigationMenu/components/NavigationLink/index.js +51 -0
  45. package/NavigationMenu/helpers.d.ts +9 -0
  46. package/NavigationMenu/helpers.js +20 -0
  47. package/NavigationMenu/index.d.ts +2 -0
  48. package/NavigationMenu/index.js +7 -0
  49. package/NavigationMenu/index.props.d.ts +40 -0
  50. package/NavigationMenu/package.json +9 -0
  51. package/NavigationMenu/splitItems.d.ts +30 -0
  52. package/NavigationMenu/splitItems.js +54 -0
  53. package/NavigationMenu/useOverflowSplit.d.ts +25 -0
  54. package/NavigationMenu/useOverflowSplit.js +63 -0
  55. package/RadioGroup/Radio.js +2 -2
  56. package/RadioGroup/index.js +1 -1
  57. package/RadioGroup/index.props.d.ts +1 -1
  58. package/Select/index.d.ts +1 -1
  59. package/Select/index.js +4 -3
  60. package/Separator/index.js +6 -4
  61. package/Separator/index.props.d.ts +2 -1
  62. package/Skeleton/index.js +6 -2
  63. package/Skeleton/index.props.d.ts +3 -2
  64. package/Status/index.d.ts +1 -1
  65. package/Status/index.js +3 -1
  66. package/Status/index.props.d.ts +2 -1
  67. package/TextArea/index.js +0 -1
  68. package/Theme/index.d.ts +8 -0
  69. package/Theme/themes/buenos_aires.d.ts +4 -0
  70. package/Theme/themes/buenos_aires.js +4 -0
  71. package/Theme/themes/jaipur.d.ts +4 -0
  72. package/Theme/themes/jaipur.js +4 -0
  73. package/TimeSelect/index.js +3 -1
  74. package/Tooltip/index.js +3 -1
  75. package/Tooltip/index.props.d.ts +2 -1
  76. package/base/FormField/index.d.ts +1 -1
  77. package/base/FormField/index.js +4 -2
  78. package/base/FormField/index.props.d.ts +2 -1
  79. package/esm/Accordion/index.js +4 -2
  80. package/esm/Accordion/index.props.d.ts +2 -1
  81. package/esm/Avatar/index.js +2 -5
  82. package/esm/Avatar/index.props.d.ts +2 -1
  83. package/esm/Badge/Control/index.props.d.ts +2 -1
  84. package/esm/Badge/index.js +1 -2
  85. package/esm/Button/index.js +0 -1
  86. package/esm/Checkbox/index.js +4 -2
  87. package/esm/Checkbox/index.props.d.ts +2 -1
  88. package/esm/Chip/index.js +2 -4
  89. package/esm/Chip/index.props.d.ts +2 -2
  90. package/esm/ColorPicker/ColorPicker.d.ts +1 -1
  91. package/esm/ColorPicker/ColorPicker.js +4 -2
  92. package/esm/ColorPicker/index.props.d.ts +2 -1
  93. package/esm/Counter/index.js +4 -2
  94. package/esm/Counter/index.props.d.ts +2 -1
  95. package/esm/Datepicker/index.d.ts +1 -1
  96. package/esm/Datepicker/index.js +5 -3
  97. package/esm/Datepicker/index.props.d.ts +2 -1
  98. package/esm/DotsLoader/index.js +5 -2
  99. package/esm/DotsLoader/index.props.d.ts +2 -1
  100. package/esm/Dropdown/index.js +2 -2
  101. package/esm/Dropdown/index.props.d.ts +2 -1
  102. package/esm/Grid/index.js +3 -1
  103. package/esm/Grid/index.props.d.ts +2 -1
  104. package/esm/InputRadio/index.js +3 -3
  105. package/esm/InputRadio/index.props.d.ts +2 -2
  106. package/esm/Menu/components/MenuItem/index.js +0 -1
  107. package/esm/Menu/components/MenuItem/index.props.d.ts +2 -1
  108. package/esm/Menu/index.d.ts +1 -1
  109. package/esm/Menu/index.js +4 -2
  110. package/esm/Menu/index.props.d.ts +2 -1
  111. package/esm/Modal/index.js +3 -1
  112. package/esm/Modal/index.props.d.ts +2 -1
  113. package/esm/MultiSelect/index.js +3 -1
  114. package/esm/NavigationMenu/NavigationMenu.d.ts +3 -0
  115. package/esm/NavigationMenu/NavigationMenu.js +162 -0
  116. package/esm/NavigationMenu/NavigationMenu.module.css +67 -0
  117. package/esm/NavigationMenu/components/MoreMenu/MoreMenu.module.css +19 -0
  118. package/esm/NavigationMenu/components/MoreMenu/index.d.ts +16 -0
  119. package/esm/NavigationMenu/components/MoreMenu/index.js +147 -0
  120. package/esm/NavigationMenu/components/NavigationLink/NavigationLink.module.css +59 -0
  121. package/esm/NavigationMenu/components/NavigationLink/index.d.ts +16 -0
  122. package/esm/NavigationMenu/components/NavigationLink/index.js +41 -0
  123. package/esm/NavigationMenu/helpers.d.ts +9 -0
  124. package/esm/NavigationMenu/helpers.js +15 -0
  125. package/esm/NavigationMenu/index.d.ts +2 -0
  126. package/esm/NavigationMenu/index.js +1 -0
  127. package/esm/NavigationMenu/index.props.d.ts +40 -0
  128. package/esm/NavigationMenu/splitItems.d.ts +30 -0
  129. package/esm/NavigationMenu/splitItems.js +51 -0
  130. package/esm/NavigationMenu/useOverflowSplit.d.ts +25 -0
  131. package/esm/NavigationMenu/useOverflowSplit.js +61 -0
  132. package/esm/RadioGroup/Radio.js +2 -2
  133. package/esm/RadioGroup/index.js +1 -1
  134. package/esm/RadioGroup/index.props.d.ts +1 -1
  135. package/esm/Select/index.d.ts +1 -1
  136. package/esm/Select/index.js +4 -3
  137. package/esm/Separator/index.js +6 -4
  138. package/esm/Separator/index.props.d.ts +2 -1
  139. package/esm/Skeleton/index.js +6 -2
  140. package/esm/Skeleton/index.props.d.ts +3 -2
  141. package/esm/Status/index.d.ts +1 -1
  142. package/esm/Status/index.js +3 -1
  143. package/esm/Status/index.props.d.ts +2 -1
  144. package/esm/TextArea/index.js +0 -1
  145. package/esm/Theme/index.d.ts +8 -0
  146. package/esm/Theme/themes/buenos_aires.d.ts +4 -0
  147. package/esm/Theme/themes/buenos_aires.js +4 -0
  148. package/esm/Theme/themes/jaipur.d.ts +4 -0
  149. package/esm/Theme/themes/jaipur.js +4 -0
  150. package/esm/TimeSelect/index.js +3 -1
  151. package/esm/Tooltip/index.js +3 -1
  152. package/esm/Tooltip/index.props.d.ts +2 -1
  153. package/esm/base/FormField/index.d.ts +1 -1
  154. package/esm/base/FormField/index.js +4 -2
  155. package/esm/base/FormField/index.props.d.ts +2 -1
  156. package/esm/helpers/testId.d.ts +18 -0
  157. package/esm/index.d.ts +3 -0
  158. package/esm/index.js +1 -0
  159. package/esm/theme_ba_variables.css +4 -0
  160. package/esm/theme_jaipur_variables.css +4 -0
  161. package/helpers/testId.d.ts +18 -0
  162. package/index.d.ts +3 -0
  163. package/index.js +3 -0
  164. package/package.json +1 -1
  165. package/theme_ba_variables.css +4 -0
  166. package/theme_jaipur_variables.css +4 -0
@@ -0,0 +1,147 @@
1
+ import React, { useState, useRef } from 'react';
2
+ import clsx from 'clsx';
3
+ import { Dropdown } from '../../../Dropdown/index.js';
4
+ import 'tslib';
5
+ import { Chevron } from '../../../Icons/Chevron.js';
6
+ import { useIsomorphicLayoutEffect, getItemKey } from '../../helpers.js';
7
+ import { getLinkClassName, NavigationLink } from '../NavigationLink/index.js';
8
+ import styles from './MoreMenu.module.css';
9
+
10
+ const getLinkPosition = event => {
11
+ const links = Array.from(event.currentTarget.querySelectorAll('[role="menuitem"]'));
12
+ return {
13
+ links,
14
+ index: links.indexOf(event.target)
15
+ };
16
+ };
17
+ const MoreOptions = ({
18
+ items,
19
+ onItemClick,
20
+ renderLink,
21
+ rootRef,
22
+ onItemFocus,
23
+ onTabOut
24
+ }) => {
25
+ // The dropdown is portaled to the end of the document: move focus into it,
26
+ // to the current section or to the first link.
27
+ useIsomorphicLayoutEffect(() => {
28
+ var _a, _b;
29
+ const root = rootRef.current;
30
+ (_b = (_a = root === null || root === void 0 ? void 0 : root.querySelector('[aria-current="page"]')) !== null && _a !== void 0 ? _a : root === null || root === void 0 ? void 0 : root.querySelector('[role="menuitem"]')) === null || _b === void 0 ? void 0 : _b.focus();
31
+ }, [rootRef]);
32
+ // Focus events bubble from the links, whatever `renderLink` renders.
33
+ const handleFocus = event => {
34
+ const {
35
+ index
36
+ } = getLinkPosition(event);
37
+ if (index !== -1) onItemFocus(items[index]);
38
+ };
39
+ // Arrow keys move focus through the links, Home/End go to the first and the
40
+ // last one. Tab leaves through the trigger, so focus continues from it.
41
+ const handleKeyDown = event => {
42
+ var _a;
43
+ if (event.key === "Tab") {
44
+ onTabOut();
45
+ return;
46
+ }
47
+ const {
48
+ links,
49
+ index
50
+ } = getLinkPosition(event);
51
+ const last = links.length - 1;
52
+ const targets = {
53
+ ArrowDown: index === last ? 0 : index + 1,
54
+ ArrowUp: index === 0 ? last : index - 1,
55
+ Home: 0,
56
+ End: last
57
+ };
58
+ if (index === -1 || !(event.key in targets)) return;
59
+ event.preventDefault();
60
+ (_a = links[targets[event.key]]) === null || _a === void 0 ? void 0 : _a.focus();
61
+ };
62
+ return (
63
+ /*#__PURE__*/
64
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- events come from the focused links
65
+ React.createElement("div", {
66
+ ref: rootRef,
67
+ className: styles.options,
68
+ onKeyDown: handleKeyDown,
69
+ onFocus: handleFocus
70
+ }, items.map((item, index) => (/*#__PURE__*/React.createElement(NavigationLink, {
71
+ key: getItemKey(item, index),
72
+ item: item,
73
+ view: "option",
74
+ role: "menuitem",
75
+ onClick: onItemClick,
76
+ renderLink: renderLink
77
+ }))))
78
+ );
79
+ };
80
+ /** "More" trigger with a dropdown of the items that do not fit. */
81
+ const MoreMenu = ({
82
+ items,
83
+ label,
84
+ hidden,
85
+ onItemClick,
86
+ renderLink,
87
+ onFocusLost
88
+ }) => {
89
+ const [isOpen, setOpen] = useState(false);
90
+ const rootRef = useRef(null);
91
+ const overlayRef = useRef(null);
92
+ const focusedItemRef = useRef(null);
93
+ // A resize hides "More" and removes the dropdown with the focused link.
94
+ // Layout effect: focus moves before the browser paints the new row.
95
+ useIsomorphicLayoutEffect(() => {
96
+ if (!hidden) return;
97
+ const item = focusedItemRef.current;
98
+ const isFocusLost = !document.activeElement || document.activeElement === document.body;
99
+ focusedItemRef.current = null;
100
+ setOpen(false);
101
+ if (item && isFocusLost) onFocusLost(item);
102
+ }, [hidden]);
103
+ // Selection, Escape and Tab remove the focused link together with the
104
+ // dropdown: return focus to the trigger.
105
+ const close = () => {
106
+ var _a, _b;
107
+ const trigger = (_a = rootRef.current) === null || _a === void 0 ? void 0 : _a.firstElementChild;
108
+ if (((_b = overlayRef.current) === null || _b === void 0 ? void 0 : _b.contains(document.activeElement)) && trigger instanceof HTMLElement) {
109
+ trigger.focus();
110
+ }
111
+ focusedItemRef.current = null;
112
+ setOpen(false);
113
+ };
114
+ return /*#__PURE__*/React.createElement("span", {
115
+ ref: rootRef,
116
+ className: styles.root
117
+ }, /*#__PURE__*/React.createElement(Dropdown, {
118
+ trigger: ["click"],
119
+ placement: "bottom-end",
120
+ visible: isOpen,
121
+ disabled: hidden,
122
+ onVisibleChange: visible => visible ? setOpen(true) : close(),
123
+ overlay: /*#__PURE__*/React.createElement(MoreOptions, {
124
+ items: items,
125
+ onItemClick: onItemClick,
126
+ renderLink: renderLink,
127
+ rootRef: overlayRef,
128
+ onItemFocus: item => {
129
+ focusedItemRef.current = item;
130
+ },
131
+ onTabOut: close
132
+ })
133
+ }, /*#__PURE__*/React.createElement("button", {
134
+ type: "button",
135
+ className: getLinkClassName(),
136
+ tabIndex: hidden ? -1 : undefined
137
+ }, label, /*#__PURE__*/React.createElement(Chevron, {
138
+ width: 16,
139
+ height: 16,
140
+ "aria-hidden": "true",
141
+ className: clsx(styles.chevron, {
142
+ [styles.chevron_open]: isOpen
143
+ })
144
+ }))));
145
+ };
146
+
147
+ export { MoreMenu };
@@ -0,0 +1,59 @@
1
+ .item {
2
+ align-items: center;
3
+ background: transparent;
4
+ block-size: var(--navigation-item-height);
5
+ border: none;
6
+ border-radius: var(--radius-full);
7
+ box-sizing: border-box;
8
+ color: var(--component-navigation-item-text);
9
+ cursor: pointer;
10
+ display: inline-flex;
11
+ font: inherit;
12
+ font-size: var(--navigation-item-font-size);
13
+ gap: var(--space-2);
14
+ line-height: var(--navigation-item-line-height);
15
+ padding: 0 var(--navigation-item-padding);
16
+ text-decoration: none;
17
+ transition: background-color 0.15s ease;
18
+ white-space: nowrap;
19
+ }
20
+ .item:hover {
21
+ background: var(--component-navigation-item-bg-hover);
22
+ }
23
+ .item:focus-visible {
24
+ outline: var(--focus-ring-width) solid var(--component-navigation-item-focus-ring);
25
+ outline-offset: calc(var(--focus-ring-width) * -1);
26
+ }
27
+ .item_selected, .item_selected:hover {
28
+ background: var(--component-navigation-item-bg-selected);
29
+ color: var(--component-navigation-item-text-selected);
30
+ }
31
+
32
+ .option {
33
+ align-items: center;
34
+ border-radius: var(--radius-xs);
35
+ color: var(--component-menu-text);
36
+ display: flex;
37
+ font-size: var(--font-size-text-sm);
38
+ gap: var(--space-2);
39
+ line-height: var(--line-height-text-sm);
40
+ outline: none;
41
+ padding: var(--space-2) var(--space-3);
42
+ text-decoration: none;
43
+ }
44
+ .option:hover, .option:focus-visible {
45
+ background: var(--component-menu-item-bg-hover);
46
+ }
47
+ .option_selected, .option_selected:hover, .option_selected:focus-visible {
48
+ background: var(--component-navigation-item-bg-selected);
49
+ color: var(--component-navigation-item-text-selected);
50
+ }
51
+ .option_selected:focus-visible {
52
+ outline: var(--focus-ring-width) solid var(--component-navigation-item-focus-ring);
53
+ outline-offset: calc(var(--focus-ring-width) * -1);
54
+ }
55
+
56
+ .icon {
57
+ display: inline-flex;
58
+ flex-shrink: 0;
59
+ }
@@ -0,0 +1,16 @@
1
+ import React from "react";
2
+ import type { NavigationMenuItem, NavigationMenuRenderLink } from "../../index.props";
3
+ interface NavigationLinkProps {
4
+ item: NavigationMenuItem;
5
+ /** `item` is a pill in the row, `option` is a line inside "More". */
6
+ view?: "item" | "option";
7
+ role?: "menuitem";
8
+ onClick?: (item: NavigationMenuItem) => void;
9
+ renderLink?: NavigationMenuRenderLink;
10
+ }
11
+ export declare const getLinkClassName: (view?: NavigationLinkProps["view"], active?: boolean) => string;
12
+ export declare const LinkContent: ({ item }: {
13
+ item: NavigationMenuItem;
14
+ }) => React.JSX.Element;
15
+ export declare const NavigationLink: ({ item, view, role, onClick, renderLink, }: NavigationLinkProps) => React.JSX.Element;
16
+ export {};
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import clsx from 'clsx';
3
+ import styles from './NavigationLink.module.css';
4
+
5
+ const getLinkClassName = (view = "item", active) => clsx(styles[view], {
6
+ [styles[`${view}_selected`]]: active
7
+ });
8
+ const LinkContent = ({
9
+ item
10
+ }) => (/*#__PURE__*/React.createElement(React.Fragment, null, item.icon && (/*#__PURE__*/React.createElement("span", {
11
+ className: styles.icon,
12
+ "aria-hidden": "true"
13
+ }, item.icon)), /*#__PURE__*/React.createElement("span", null, item.label)));
14
+ const NavigationLink = ({
15
+ item,
16
+ view,
17
+ role,
18
+ onClick,
19
+ renderLink
20
+ }) => {
21
+ const props = {
22
+ item,
23
+ className: getLinkClassName(view, item.active),
24
+ children: /*#__PURE__*/React.createElement(LinkContent, {
25
+ item: item
26
+ }),
27
+ onClick: onClick ? () => onClick(item) : undefined,
28
+ role,
29
+ "aria-current": item.active ? "page" : undefined
30
+ };
31
+ if (renderLink) return /*#__PURE__*/React.createElement(React.Fragment, null, renderLink(props));
32
+ return /*#__PURE__*/React.createElement("a", {
33
+ href: item.href,
34
+ className: props.className,
35
+ onClick: props.onClick,
36
+ role: role,
37
+ "aria-current": props["aria-current"]
38
+ }, props.children);
39
+ };
40
+
41
+ export { LinkContent, NavigationLink, getLinkClassName };
@@ -0,0 +1,9 @@
1
+ import { useLayoutEffect } from "react";
2
+ import type { Ref } from "react";
3
+ import type { NavigationMenuItem } from "./index.props";
4
+ export declare const getItemKey: (item: NavigationMenuItem, index: number) => string;
5
+ /** Items are often recreated on every render: compare them by content. */
6
+ export declare const isSameItem: (a: NavigationMenuItem, b: NavigationMenuItem) => boolean;
7
+ /** `useLayoutEffect` without the server rendering warning. */
8
+ export declare const useIsomorphicLayoutEffect: typeof useLayoutEffect;
9
+ export declare const assignRef: <T>(ref: Ref<T> | undefined, value: T | null) => void;
@@ -0,0 +1,15 @@
1
+ import { useLayoutEffect, useEffect } from 'react';
2
+
3
+ const getItemKey = (item, index) => {
4
+ var _a, _b;
5
+ return (_b = (_a = item.id) !== null && _a !== void 0 ? _a : item.href) !== null && _b !== void 0 ? _b : `${item.label}-${index}`;
6
+ };
7
+ /** Items are often recreated on every render: compare them by content. */
8
+ const isSameItem = (a, b) => getItemKey(a, 0) === getItemKey(b, 0);
9
+ /** `useLayoutEffect` without the server rendering warning. */
10
+ const useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
11
+ const assignRef = (ref, value) => {
12
+ if (typeof ref === "function") ref(value);else if (ref) ref.current = value;
13
+ };
14
+
15
+ export { assignRef, getItemKey, isSameItem, useIsomorphicLayoutEffect };
@@ -0,0 +1,2 @@
1
+ export { NavigationMenu } from "./NavigationMenu";
2
+ export type { NavigationMenuItem, NavigationMenuLayout, NavigationMenuLinkProps, NavigationMenuProps, NavigationMenuRenderLink, NavigationMenuSize, } from "./index.props";
@@ -0,0 +1 @@
1
+ export { NavigationMenu } from './NavigationMenu.js';
@@ -0,0 +1,40 @@
1
+ import type { HTMLAttributes, MouseEventHandler, ReactNode } from "react";
2
+ /** `overflow` keeps items in a row and moves the rest to "More", `scroll` is a horizontal strip. */
3
+ export type NavigationMenuLayout = "overflow" | "scroll";
4
+ export type NavigationMenuSize = "s" | "m";
5
+ export interface NavigationMenuItem {
6
+ /** Unique key. Defaults to `href`, then to `label`. */
7
+ id?: string;
8
+ label: string;
9
+ href?: string;
10
+ /** Current section: highlighted, gets `aria-current="page"` and never moves to "More". */
11
+ active?: boolean;
12
+ /** Icon before the label. */
13
+ icon?: ReactNode;
14
+ }
15
+ export interface NavigationMenuLinkProps {
16
+ item: NavigationMenuItem;
17
+ className: string;
18
+ children: ReactNode;
19
+ onClick?: MouseEventHandler<HTMLElement>;
20
+ tabIndex?: number;
21
+ /** Set for links inside the "More" dropdown. */
22
+ role?: "menuitem";
23
+ "aria-current"?: "page";
24
+ }
25
+ /** Renders an item link, for example a router `Link`. Pass every prop except `item` to the element. */
26
+ export type NavigationMenuRenderLink = (props: NavigationMenuLinkProps) => ReactNode;
27
+ export interface NavigationMenuProps extends Omit<HTMLAttributes<HTMLElement>, "children"> {
28
+ items: NavigationMenuItem[];
29
+ /** Always visible after `items`. */
30
+ pinnedItems?: NavigationMenuItem[];
31
+ /** Always inside "More", after the items that do not fit. In the `scroll` layout they end the strip. */
32
+ moreItems?: NavigationMenuItem[];
33
+ layout?: NavigationMenuLayout;
34
+ size?: NavigationMenuSize;
35
+ moreLabel?: ReactNode;
36
+ /** Called on a click on any item, including the ones inside "More". */
37
+ onItemClick?: (item: NavigationMenuItem) => void;
38
+ /** Custom link. Defaults to `<a href>`. */
39
+ renderLink?: NavigationMenuRenderLink;
40
+ }
@@ -0,0 +1,30 @@
1
+ export interface SplitInput {
2
+ itemWidths: number[];
3
+ /** Pinned items are always visible and go after the main items. */
4
+ pinnedWidths: number[];
5
+ moreWidth: number;
6
+ containerWidth: number;
7
+ /** CSS gap between items. */
8
+ gap: number;
9
+ /** Index of the active item or -1. */
10
+ activeIndex: number;
11
+ /** "More" is needed anyway, for example for `moreItems`. */
12
+ forceMore: boolean;
13
+ }
14
+ export interface SplitResult {
15
+ /** Indexes of the items left in the row, in the original order. */
16
+ visible: number[];
17
+ /** Indexes of the items moved to "More", in the original order. */
18
+ hidden: number[];
19
+ }
20
+ /**
21
+ * Splits items into the row and "More".
22
+ *
23
+ * Items go from left to right while they fit; the first one that does not fit
24
+ * and all after it move to "More". Space for the active item is reserved
25
+ * first, so it stays in the row and its neighbours move instead. Every element
26
+ * costs its width plus gap; the budget has one extra gap because N elements
27
+ * have N − 1 gaps.
28
+ */
29
+ export declare const splitItems: ({ itemWidths, pinnedWidths, moreWidth, containerWidth, gap, activeIndex, forceMore, }: SplitInput) => SplitResult;
30
+ export declare const isSameSplit: (a: SplitResult, b: SplitResult) => boolean;
@@ -0,0 +1,51 @@
1
+ const sum = list => list.reduce((acc, n) => acc + n, 0);
2
+ /**
3
+ * Splits items into the row and "More".
4
+ *
5
+ * Items go from left to right while they fit; the first one that does not fit
6
+ * and all after it move to "More". Space for the active item is reserved
7
+ * first, so it stays in the row and its neighbours move instead. Every element
8
+ * costs its width plus gap; the budget has one extra gap because N elements
9
+ * have N − 1 gaps.
10
+ */
11
+ const splitItems = ({
12
+ itemWidths,
13
+ pinnedWidths,
14
+ moreWidth,
15
+ containerWidth,
16
+ gap,
17
+ activeIndex,
18
+ forceMore
19
+ }) => {
20
+ const all = itemWidths.map((_, index) => index);
21
+ const cost = width => width + gap;
22
+ const pinnedCost = sum(pinnedWidths.map(cost));
23
+ if (!forceMore && sum(itemWidths.map(cost)) + pinnedCost <= containerWidth + gap) {
24
+ return {
25
+ visible: all,
26
+ hidden: []
27
+ };
28
+ }
29
+ const hasActive = activeIndex >= 0 && activeIndex < itemWidths.length;
30
+ let budget = containerWidth + gap - pinnedCost - cost(moreWidth);
31
+ if (hasActive) budget -= cost(itemWidths[activeIndex]);
32
+ const visible = [];
33
+ for (let index = 0; index < itemWidths.length; index++) {
34
+ if (index === activeIndex) continue;
35
+ if (cost(itemWidths[index]) > budget) break;
36
+ budget -= cost(itemWidths[index]);
37
+ visible.push(index);
38
+ }
39
+ if (hasActive) {
40
+ visible.push(activeIndex);
41
+ visible.sort((a, b) => a - b);
42
+ }
43
+ return {
44
+ visible,
45
+ hidden: all.filter(index => !visible.includes(index))
46
+ };
47
+ };
48
+ const isSameList = (a, b) => a.length === b.length && a.every((index, position) => index === b[position]);
49
+ const isSameSplit = (a, b) => isSameList(a.visible, b.visible) && isSameList(a.hidden, b.hidden);
50
+
51
+ export { isSameSplit, splitItems };
@@ -0,0 +1,25 @@
1
+ import type { SplitResult } from "./splitItems";
2
+ interface Options {
3
+ itemCount: number;
4
+ pinnedCount: number;
5
+ activeIndex: number;
6
+ forceMore: boolean;
7
+ /** Changes with the item labels to recalculate. */
8
+ contentKey: string;
9
+ }
10
+ /**
11
+ * Measures the items and splits them into the row and "More".
12
+ *
13
+ * Widths come from a hidden measurer that always renders every item, so the
14
+ * items inside "More" are measured too, for example after a web font loads.
15
+ * ResizeObserver watches the container, the measurer and the "More" trigger.
16
+ * Before the first measurement, and on the server, `split` is `null`.
17
+ */
18
+ export declare const useOverflowSplit: ({ itemCount, pinnedCount, activeIndex, forceMore, contentKey, }: Options) => {
19
+ split: SplitResult | null;
20
+ containerRef: import("react").RefObject<HTMLElement | null>;
21
+ listRef: import("react").RefObject<HTMLUListElement | null>;
22
+ measurerRef: import("react").RefObject<HTMLDivElement | null>;
23
+ moreRef: import("react").RefObject<HTMLLIElement | null>;
24
+ };
25
+ export {};
@@ -0,0 +1,61 @@
1
+ import { useRef, useState } from 'react';
2
+ import { useIsomorphicLayoutEffect } from './helpers.js';
3
+ import { splitItems, isSameSplit } from './splitItems.js';
4
+
5
+ const getWidth = element => element ? element.getBoundingClientRect().width : 0;
6
+ /**
7
+ * Measures the items and splits them into the row and "More".
8
+ *
9
+ * Widths come from a hidden measurer that always renders every item, so the
10
+ * items inside "More" are measured too, for example after a web font loads.
11
+ * ResizeObserver watches the container, the measurer and the "More" trigger.
12
+ * Before the first measurement, and on the server, `split` is `null`.
13
+ */
14
+ const useOverflowSplit = ({
15
+ itemCount,
16
+ pinnedCount,
17
+ activeIndex,
18
+ forceMore,
19
+ contentKey
20
+ }) => {
21
+ const containerRef = useRef(null);
22
+ const listRef = useRef(null);
23
+ const measurerRef = useRef(null);
24
+ const moreRef = useRef(null);
25
+ const [split, setSplit] = useState(null);
26
+ useIsomorphicLayoutEffect(() => {
27
+ const container = containerRef.current;
28
+ const measurer = measurerRef.current;
29
+ if (!container || !measurer) return undefined;
30
+ const recalculate = () => {
31
+ const widths = Array.from(measurer.children, getWidth);
32
+ const gap = listRef.current ? parseFloat(getComputedStyle(listRef.current).columnGap) || 0 : 0;
33
+ const next = splitItems({
34
+ itemWidths: widths.slice(0, itemCount),
35
+ pinnedWidths: widths.slice(itemCount, itemCount + pinnedCount),
36
+ moreWidth: getWidth(moreRef.current),
37
+ containerWidth: getWidth(container),
38
+ gap,
39
+ activeIndex,
40
+ forceMore
41
+ });
42
+ setSplit(prev => prev && isSameSplit(prev, next) ? prev : next);
43
+ };
44
+ recalculate();
45
+ if (typeof ResizeObserver === "undefined") return undefined;
46
+ const observer = new ResizeObserver(recalculate);
47
+ observer.observe(container);
48
+ observer.observe(measurer);
49
+ if (moreRef.current) observer.observe(moreRef.current);
50
+ return () => observer.disconnect();
51
+ }, [itemCount, pinnedCount, activeIndex, forceMore, contentKey]);
52
+ return {
53
+ split,
54
+ containerRef,
55
+ listRef,
56
+ measurerRef,
57
+ moreRef
58
+ };
59
+ };
60
+
61
+ export { useOverflowSplit };
@@ -14,7 +14,7 @@ const Radio = ({
14
14
  onMouseEnter,
15
15
  className,
16
16
  disabled = false,
17
- testId
17
+ "data-testid": testId
18
18
  }) => (/*#__PURE__*/React.createElement(InputRadio, {
19
19
  id: id,
20
20
  value: value,
@@ -28,7 +28,7 @@ const Radio = ({
28
28
  name: name,
29
29
  className: className,
30
30
  disabled: disabled,
31
- testid: testId
31
+ "data-testid": testId
32
32
  }));
33
33
 
34
34
  export { Radio };
@@ -32,7 +32,7 @@ const RadioGroup = /*#__PURE__*/memo(({
32
32
  return /*#__PURE__*/React.createElement(Radio, {
33
33
  key: optionVal,
34
34
  id: id,
35
- testId: id,
35
+ "data-testid": id,
36
36
  value: optionVal,
37
37
  text: text,
38
38
  note: note,
@@ -34,5 +34,5 @@ export type RadioProps = {
34
34
  onMouseEnter?: (event: React.MouseEvent<HTMLLabelElement, MouseEvent>) => void;
35
35
  className: string;
36
36
  disabled?: boolean;
37
- testId?: string;
37
+ "data-testid"?: string;
38
38
  };
@@ -1,3 +1,3 @@
1
1
  import type { JSX } from "react";
2
2
  import type { SelectProps } from "./index.props";
3
- export declare const Select: <T extends string>({ options, value, name, zIndex, fontSize, label, hint, hasError, required, placeholder, disabled, size, fullWidth, customInputRoot, className, onChange, "aria-label": ariaLabel, }: SelectProps<T>) => JSX.Element;
3
+ export declare const Select: <T extends string>({ options, value, name, zIndex, fontSize, label, hint, hasError, required, placeholder, disabled, size, fullWidth, customInputRoot, className, onChange, "aria-label": ariaLabel, "data-testid": testId, }: SelectProps<T>) => JSX.Element;
@@ -24,7 +24,8 @@ const Select = ({
24
24
  customInputRoot,
25
25
  className,
26
26
  onChange,
27
- "aria-label": ariaLabel
27
+ "aria-label": ariaLabel,
28
+ "data-testid": testId
28
29
  }) => {
29
30
  const [isOpen, setIsOpen] = useState(false);
30
31
  const id = useId();
@@ -48,6 +49,7 @@ const Select = ({
48
49
  }) => selected)) === null || _a === void 0 ? void 0 : _a.label;
49
50
  }, [preparedOptions]);
50
51
  return /*#__PURE__*/React.createElement(FormField, {
52
+ "data-testid": testId,
51
53
  className: clsx(styles["form-field"], {
52
54
  [styles["full-width"]]: Boolean(fullWidth)
53
55
  }, className),
@@ -96,8 +98,7 @@ const Select = ({
96
98
  disabled: Boolean(disabled)
97
99
  }, /*#__PURE__*/React.createElement("div", {
98
100
  id: valueId,
99
- className: styles["base-input"],
100
- "data-testid": "base-input"
101
+ className: styles["base-input"]
101
102
  }, preparedLabel !== null && preparedLabel !== void 0 ? preparedLabel : (/*#__PURE__*/React.createElement("span", {
102
103
  className: styles.placeholder
103
104
  }, placeholder))), /*#__PURE__*/React.createElement("input", {
@@ -3,9 +3,11 @@ import clsx from 'clsx';
3
3
  import styles from './Separator.module.css';
4
4
 
5
5
  const Separator = ({
6
- className
7
- }) => (/*#__PURE__*/React.createElement("div", {
8
- className: clsx(styles.root, className)
9
- }));
6
+ className,
7
+ "data-testid": testId
8
+ }) => /*#__PURE__*/React.createElement("div", {
9
+ className: clsx(styles.root, className),
10
+ "data-testid": testId
11
+ });
10
12
 
11
13
  export { Separator };
@@ -1,3 +1,4 @@
1
- export type SeparatorProps = {
1
+ import type { TestIdProps } from "../helpers/testId";
2
+ export type SeparatorProps = TestIdProps & {
2
3
  className?: string;
3
4
  };
@@ -7,9 +7,11 @@ const SkeletonRect = ({
7
7
  className,
8
8
  width,
9
9
  height,
10
- radius = "sm"
10
+ radius = "sm",
11
+ "data-testid": testId
11
12
  }) => (/*#__PURE__*/React.createElement("div", {
12
13
  className: clsx(styles.rect, className),
14
+ "data-testid": testId,
13
15
  style: {
14
16
  width: toRemLength(width),
15
17
  height: toRemLength(height),
@@ -19,9 +21,11 @@ const SkeletonRect = ({
19
21
  const SkeletonText = ({
20
22
  className,
21
23
  width,
22
- size
24
+ size,
25
+ "data-testid": testId
23
26
  }) => (/*#__PURE__*/React.createElement("div", {
24
27
  className: clsx(styles.text, className),
28
+ "data-testid": testId,
25
29
  style: {
26
30
  width: toRemLength(width),
27
31
  height: toRemLength(size)
@@ -1,9 +1,10 @@
1
- export type TextProps = {
1
+ import type { TestIdProps } from "../helpers/testId";
2
+ export type TextProps = TestIdProps & {
2
3
  className?: string;
3
4
  width?: number | string;
4
5
  size?: number | string;
5
6
  };
6
- export type RectProps = {
7
+ export type RectProps = TestIdProps & {
7
8
  className?: string;
8
9
  width?: number | string;
9
10
  height?: number | string;
@@ -1,4 +1,4 @@
1
1
  import React from "react";
2
2
  import type { PropsWithChildren } from "react";
3
3
  import type { StatusProps } from "./index.props";
4
- export declare const Status: ({ className, indicator, showIndicator, tag: Component, children, type, }: PropsWithChildren<StatusProps>) => React.JSX.Element;
4
+ export declare const Status: ({ className, indicator, showIndicator, tag: Component, children, type, "data-testid": testId, }: PropsWithChildren<StatusProps>) => React.JSX.Element;
@@ -8,8 +8,10 @@ const Status = ({
8
8
  showIndicator = true,
9
9
  tag: Component = "span",
10
10
  children,
11
- type
11
+ type,
12
+ "data-testid": testId
12
13
  }) => (/*#__PURE__*/React.createElement(Component, {
14
+ "data-testid": testId,
13
15
  className: clsx(styles.root, {
14
16
  [styles[`root_${type}`]]: Boolean(type)
15
17
  }, className)