@via-profit/ui-kit 0.9.1 → 0.10.1

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 (65) hide show
  1. package/Badge/BadgeContainer.js +1 -1
  2. package/Badge/BadgeTextWrapper.js +3 -1
  3. package/Button/buttonVariants.d.ts +1 -1
  4. package/Button/buttonVariants.js +57 -8
  5. package/Button/index.d.ts +4 -4
  6. package/CHANGELOG.md +27 -0
  7. package/Calendar/CalendarControlButton.d.ts +1 -1
  8. package/Calendar/CalendarWeekDayLabel.js +1 -1
  9. package/DatePicker/index.js +3 -1
  10. package/HintPopover/index.js +1 -1
  11. package/Menu/MenuContainer.d.ts +26 -0
  12. package/Menu/MenuContainer.js +45 -20
  13. package/Menu/MenuList.d.ts +4 -0
  14. package/Menu/MenuList.js +3 -3
  15. package/Menu/MenuSeparator.d.ts +8 -0
  16. package/Menu/MenuSeparator.js +29 -0
  17. package/Menu/index.d.ts +1 -0
  18. package/Menu/index.js +1 -0
  19. package/Modal/BaseModal/ModalInner.js +2 -1
  20. package/Pagination/PaginationItem.d.ts +1 -1
  21. package/Selectbox/SelectboxMenu.js +1 -1
  22. package/Stepper/Step.d.ts +51 -0
  23. package/Stepper/Step.js +169 -0
  24. package/Stepper/Stepper.d.ts +49 -0
  25. package/Stepper/Stepper.js +74 -0
  26. package/Stepper/context.d.ts +21 -0
  27. package/Stepper/context.js +12 -0
  28. package/Stepper/index.d.ts +6 -0
  29. package/Stepper/index.js +10 -0
  30. package/Surface/SurfaceSubheader.js +4 -3
  31. package/Tabs/Tab.d.ts +8 -0
  32. package/Tabs/Tab.js +75 -32
  33. package/Tabs/TabList.js +24 -5
  34. package/Tabs/Tabs.d.ts +25 -1
  35. package/Tabs/Tabs.js +5 -1
  36. package/Tabs/context.d.ts +18 -0
  37. package/Tabs/context.js +9 -1
  38. package/Tabs/index.d.ts +1 -1
  39. package/ThemeProvider/index.d.ts +6 -1
  40. package/ThemeProvider/tokens.js +1 -0
  41. package/esm/Badge/BadgeContainer.js +1 -1
  42. package/esm/Badge/BadgeTextWrapper.js +3 -1
  43. package/esm/Button/buttonVariants.js +57 -8
  44. package/esm/Calendar/CalendarWeekDayLabel.js +1 -1
  45. package/esm/DatePicker/index.js +3 -1
  46. package/esm/HintPopover/index.js +1 -1
  47. package/esm/Menu/MenuContainer.js +45 -20
  48. package/esm/Menu/MenuList.js +3 -3
  49. package/esm/Menu/MenuSeparator.js +23 -0
  50. package/esm/Menu/index.js +1 -0
  51. package/esm/Modal/BaseModal/ModalInner.js +2 -1
  52. package/esm/Selectbox/SelectboxMenu.js +1 -1
  53. package/esm/Stepper/Step.js +164 -0
  54. package/esm/Stepper/Stepper.js +69 -0
  55. package/esm/Stepper/context.js +6 -0
  56. package/esm/Stepper/index.js +3 -0
  57. package/esm/Surface/SurfaceSubheader.js +4 -3
  58. package/esm/Tabs/Tab.js +77 -34
  59. package/esm/Tabs/TabList.js +24 -5
  60. package/esm/Tabs/Tabs.js +5 -1
  61. package/esm/Tabs/context.js +8 -0
  62. package/esm/ThemeProvider/tokens.js +1 -0
  63. package/esm/utils/TabManager.js +1 -1
  64. package/package.json +31 -1
  65. package/utils/TabManager.js +1 -1
@@ -17,7 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
17
17
  const react_1 = __importDefault(require("react"));
18
18
  const styled_1 = __importDefault(require("@emotion/styled"));
19
19
  const StyledBadge = styled_1.default.span `
20
- border-radius: ${({ theme }) => theme.shape.radiusFactor * 2.5}em;
20
+ border-radius: ${({ theme }) => theme.shape.radiusFactor * 2}em;
21
21
  font-size: 1em;
22
22
  border-width: 0;
23
23
  outline-style: solid;
@@ -18,7 +18,9 @@ const react_1 = __importDefault(require("react"));
18
18
  const styled_1 = __importDefault(require("@emotion/styled"));
19
19
  const TextWrapper = styled_1.default.span `
20
20
  font-size: 0.86em;
21
- padding: 0.3em 0.5em;
21
+ /* the badge is about the height of the line of the text around it, not taller */
22
+ line-height: 1.35;
23
+ padding: 0.15em 0.5em;
22
24
  display: flex;
23
25
  align-items: center;
24
26
  user-select: none;
@@ -1,6 +1,6 @@
1
1
  import { Theme } from '@emotion/react';
2
2
  import type { ButtonGroupOrientation } from '../ButtonGroup/ButtonGroupContext';
3
- export type ButtonVariant = 'standard' | 'outlined' | 'plain';
3
+ export type ButtonVariant = 'standard' | 'outlined' | 'plain' | 'soft';
4
4
  type ButtonColor = 'default' | 'primary' | 'secondary' | string | undefined;
5
5
  /**
6
6
  * The styles of the variant. They are applied inside the Container part,
@@ -43,7 +43,7 @@ const getStandardColors = (color, theme) => {
43
43
  }
44
44
  };
45
45
  /**
46
- * The main color of the outlined and plain variants (text and border)
46
+ * The main color of the outlined, plain and soft variants (text and border)
47
47
  */
48
48
  const getAccentColor = (color, theme) => {
49
49
  switch (true) {
@@ -82,11 +82,23 @@ const standardStyles = ({ $color, $background, disabled, theme }) => (0, react_1
82
82
  }
83
83
  `}
84
84
  `;
85
- const outlinedStyles = ({ $color, disabled, theme }) => (0, react_1.css) `
86
- color: ${disabled ? theme.color.textPrimary.alpha(0.4).toString() : $color.darken(30).toString()};
85
+ /**
86
+ * The border of the neutral outlined button: light, as the lines of the tables. The dark border
87
+ * of the text color makes «Отмена», «Сбросить» and the groups of the filters heavy;
88
+ * the colored outlined buttons keep the border of their color
89
+ */
90
+ const neutralBorder = (theme) => theme.color.textPrimary.alpha(0.18).toString();
91
+ const outlinedStyles = ({ $color, $neutral, disabled, theme }) => (0, react_1.css) `
92
+ color: ${disabled
93
+ ? theme.color.textPrimary.alpha(0.4).toString()
94
+ : $neutral
95
+ ? $color.toString()
96
+ : $color.darken(30).toString()};
87
97
  border-color: ${disabled
88
98
  ? theme.color.surface.darken(80).alpha(0.4).toString()
89
- : $color.toString()};
99
+ : $neutral
100
+ ? neutralBorder(theme)
101
+ : $color.toString()};
90
102
  background-color: transparent;
91
103
  border-style: solid;
92
104
  border-width: ${tokens_1.CONTROL_BORDER};
@@ -94,10 +106,18 @@ const outlinedStyles = ({ $color, disabled, theme }) => (0, react_1.css) `
94
106
  ${!disabled &&
95
107
  (0, react_1.css) `
96
108
  &:hover {
97
- background-color: ${$color.darken(20).alpha(0.1).toString()};
109
+ background-color: ${$neutral
110
+ ? $color.alpha(0.05).toString()
111
+ : $color.darken(20).alpha(0.1).toString()};
112
+ ${$neutral &&
113
+ (0, react_1.css) `
114
+ border-color: ${theme.color.textPrimary.alpha(0.35).toString()};
115
+ `}
98
116
  }
99
117
  &:active {
100
- background-color: ${$color.darken(50).alpha(0.3).toString()};
118
+ background-color: ${$neutral
119
+ ? $color.alpha(0.12).toString()
120
+ : $color.darken(50).alpha(0.3).toString()};
101
121
  }
102
122
  &:focus-visible {
103
123
  outline-color: ${focusOutlineColor($color, theme)};
@@ -126,6 +146,32 @@ const plainStyles = ({ $color, disabled, theme }) => (0, react_1.css) `
126
146
  }
127
147
  `}
128
148
  `;
149
+ /**
150
+ * The tonal button: the pale background of its color and the text of the same hue,
151
+ * as the plain button under the cursor. Quieter than the standard button, more visible than the plain one
152
+ */
153
+ const softStyles = ({ $color, disabled, theme }) => (0, react_1.css) `
154
+ box-shadow: none;
155
+ color: ${disabled ? $color.alpha(0.4).toString() : $color.darken(30).toString()};
156
+ background-color: ${disabled ? $color.alpha(0.05).toString() : $color.alpha(0.1).toString()};
157
+
158
+ ${!disabled &&
159
+ (0, react_1.css) `
160
+ &:hover {
161
+ color: ${$color.darken(50).toString()};
162
+ background-color: ${$color.alpha(0.18).toString()};
163
+ }
164
+ &:active {
165
+ color: ${$color.darken(80).toString()};
166
+ background-color: ${$color.alpha(0.3).toString()};
167
+ }
168
+ &:focus-visible {
169
+ outline-style: solid;
170
+ outline-width: 0.14em;
171
+ outline-color: ${focusOutlineColor($color, theme)};
172
+ }
173
+ `}
174
+ `;
129
175
  /**
130
176
  * Inside the ButtonGroup all the variants have the same border width, so the buttons have the same size
131
177
  * and the selected (standard) button does not shrink. Two adjacent standard buttons are separated by a thin line
@@ -156,13 +202,16 @@ const buttonVariantStyles = (params) => {
156
202
  const colors = variant === 'standard'
157
203
  ? getStandardColors(color, theme)
158
204
  : { $color: getAccentColor(color, theme), $background: getAccentColor(color, theme) };
159
- const props = Object.assign(Object.assign({}, colors), { $variant: variant, $group: group, disabled, theme });
205
+ const props = Object.assign(Object.assign({}, colors), { $variant: variant, $group: group, $neutral: typeof color === 'undefined' || color === 'default', disabled,
206
+ theme });
160
207
  return (0, react_1.css) `
161
208
  ${variant === 'outlined'
162
209
  ? outlinedStyles(props)
163
210
  : variant === 'plain'
164
211
  ? plainStyles(props)
165
- : standardStyles(props)}
212
+ : variant === 'soft'
213
+ ? softStyles(props)
214
+ : standardStyles(props)}
166
215
  ${groupStyles(props)}
167
216
  `;
168
217
  };
package/Button/index.d.ts CHANGED
@@ -5,19 +5,19 @@ import type { ButtonPlainProps } from './ButtonPlain';
5
5
  export type ButtonProps = (ButtonStandardProps | ButtonOutlinedProps | ButtonPlainProps) & {
6
6
  /**
7
7
  * Button style variant\
8
- * Allowed variants: `standard`, `outlined` or `plain`\
8
+ * Allowed variants: `standard`, `outlined`, `plain` or `soft` (the pale background of the color)\
9
9
  * \
10
10
  * **Default**: `standard`
11
11
  */
12
- readonly variant?: 'standard' | 'outlined' | 'plain';
12
+ readonly variant?: 'standard' | 'outlined' | 'plain' | 'soft';
13
13
  };
14
14
  declare const _default: React.ForwardRefExoticComponent<import("./ButtonBase").ButtonBaseProps & {
15
15
  /**
16
16
  * Button style variant\
17
- * Allowed variants: `standard`, `outlined` or `plain`\
17
+ * Allowed variants: `standard`, `outlined`, `plain` or `soft` (the pale background of the color)\
18
18
  * \
19
19
  * **Default**: `standard`
20
20
  */
21
- readonly variant?: "standard" | "outlined" | "plain";
21
+ readonly variant?: "standard" | "outlined" | "plain" | "soft";
22
22
  } & React.RefAttributes<HTMLButtonElement>>;
23
23
  export default _default;
package/CHANGELOG.md CHANGED
@@ -2,6 +2,33 @@
2
2
 
3
3
  Все заметные изменения пакета `@via-profit/ui-kit`. Формат основан на [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/), версии следуют [семантическому версионированию](https://semver.org/lang/ru/).
4
4
 
5
+ ## Не выпущено
6
+
7
+ ## 0.10.1 — 2026-10-23
8
+
9
+ ### Исправлено
10
+
11
+ - **Menu**, **Selectbox**, **DatePicker**, **HintPopover**, модальные окна: возвращая фокус на кнопку или поле после закрытия, кит больше не прокручивает страницу (`focus({ preventScroll: true })`). Если кнопка-якорь стоит в закреплённой шапке (`position: sticky`), Chrome прокручивал страницу к исходному месту шапки в документе, и после закрытия меню щелчком мимо страница прыгала. Список Menu при открытии тоже получает фокус без прокрутки.
12
+
13
+ ## 0.10.0 — 2026-10-21
14
+
15
+ ### Добавлено
16
+
17
+ - **Menu**: свойство `maxHeight` — максимальная высота списка (по умолчанию, как раньше, `18em`). Меню действий с разделителями переросло 18em и прокручивалось на пару пикселей;
18
+ - **Menu**: разделители групп пунктов — свойство `isSeparator` отмечает элементы `items`, которые меню рисует само линией с отступами (часть `Separator`, компонент `MenuSeparator`). Стрелки, Home и End их пропускают, выбрать их нельзя, программы чтения с экрана их не объявляют;
19
+ - **Stepper** — шаги процесса: выполненные (бледная подложка и галочка цвета), текущий (подложка контрастного цвета темы, обводка и номер цвета), будущие (бледно-серые), между ними линии — цветом после выполненных. В строку или столбец (`orientation`), подписи под кружками (`alternativeLabel`), у шага — `caption`, `completed`, `status` (`warning` / `error`), своя `icon`;
20
+ - **Tabs**: `variant` — `underline` (линия под текстом выбранной вкладки со скруглёнными концами, скруглённая подложка при наведении) и `segmented` (вкладки в лотке, выбранная — плашка); `size` — `s` / `m` / `l` (32 / 40 / 48px при тексте 14px); `counter` у `Tab` — счётчик: бледный бейдж цвета вкладок (`soft`), серый при нуле;
21
+ - **Button**: вариант `soft` — тональная кнопка: бледная подложка цвета кнопки и текст того же оттенка, как у `plain` под курсором. Тише `standard`, заметнее `plain`; под курсором подложка темнеет. Работает с любым `color`;
22
+ - **ThemeProvider**: уровень тени `elevation.modal` — у модальных окон (Dialog, Drawer, MessageBox, ConfirmBox), глубже, чем у всплывающих элементов. Если в теме его нет, окна берут `elevation.popup` темы, как раньше, а без него — новую тень уровня по умолчанию.
23
+
24
+ ### Изменено
25
+
26
+ - **Tabs**: новый вид по умолчанию — линия только под текстом выбранной вкладки (а не во всю её ширину), концы скруглены, подложка при наведении скруглена; текст вкладки — 0.929em (13px при тексте 14px; S — 12px, L — 14px), вес 400, выбранная — 500 и темнее цвета вкладок. Линия под рядом и у вертикальных вкладок светлее.
27
+ - **Badge**: ниже — вертикальный отступ текста 0.15em вместо 0.3em, высота строки 1.35: бейдж примерно на высоту строки текста рядом, а не выше неё. Размер текста прежний. Скругление — как у кнопок (`radiusFactor × 2` вместо `× 2.5`).
28
+ - **Surface**: подзаголовок — размер текста (1em вместо 0.9em), вес 300 вместо 200: тише текста за счёт цвета и Light, но не мелкий.
29
+ - **Button**: у нейтральной кнопки `outlined` (`color="default"` или без `color`) — светлая рамка (цвет текста с прозрачностью 0.18, под курсором 0.35) и текст цвета темы без затемнения. Тёмная рамка цвета текста делала второстепенные кнопки («Отмена», «Сбросить») и группы кнопок-фильтров тяжёлыми. Цветные `outlined` (`primary`, `secondary`, свой цвет) не изменились.
30
+ - **Calendar** (дни недели): вес шрифта 300 вместо 200 — начертания 200 нет в распространённых шрифтах, браузер всё равно рисовал 300.
31
+
5
32
  ## 0.9.1 — 2026-10-19
6
33
 
7
34
  ### Исправлено
@@ -4,7 +4,7 @@ export type CalendarControlButtonProps = ButtonProps & {
4
4
  readonly isActive?: boolean;
5
5
  };
6
6
  declare const _default: React.ForwardRefExoticComponent<import("../Button/ButtonBase").ButtonBaseProps & {
7
- readonly variant?: "standard" | "outlined" | "plain";
7
+ readonly variant?: "standard" | "outlined" | "plain" | "soft";
8
8
  } & {
9
9
  readonly isActive?: boolean;
10
10
  } & React.RefAttributes<HTMLButtonElement>>;
@@ -9,7 +9,7 @@ const CalendarWeekDayLabel = styled_1.default.div `
9
9
  display: flex;
10
10
  align-items: center;
11
11
  justify-content: center;
12
- font-weight: 200;
12
+ font-weight: 300;
13
13
  font-size: 0.8em;
14
14
  user-select: none;
15
15
  width: 3em;
@@ -130,7 +130,9 @@ const DatePicker = props => {
130
130
  var _a, _b;
131
131
  setOpenSate(false);
132
132
  // Back to the field, so the keyboard user continues from the same place
133
- (_b = (readOnly ? inputRef.current : ((_a = buttonRef.current) !== null && _a !== void 0 ? _a : inputRef.current))) === null || _b === void 0 ? void 0 : _b.focus();
133
+ (_b = (readOnly ? inputRef.current : ((_a = buttonRef.current) !== null && _a !== void 0 ? _a : inputRef.current))) === null || _b === void 0 ? void 0 : _b.focus({
134
+ preventScroll: true,
135
+ });
134
136
  }, [readOnly]);
135
137
  const handlePopperKeyDown = react_1.default.useCallback((event) => {
136
138
  if (event.key === 'Escape') {
@@ -54,7 +54,7 @@ const HintPopover = props => {
54
54
  const onKeyDown = (event) => {
55
55
  if (event.key === 'Escape') {
56
56
  setOpen(false);
57
- button === null || button === void 0 ? void 0 : button.focus();
57
+ button === null || button === void 0 ? void 0 : button.focus({ preventScroll: true });
58
58
  }
59
59
  };
60
60
  document.addEventListener('keydown', onKeyDown);
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { MenuListProps } from './MenuList';
3
+ import { MenuSeparatorProps } from './MenuSeparator';
3
4
  import { AnchorPos, PopperProps, PositionStrategy } from '../Popper';
4
5
  import type { MenuItemProps } from './MenuItem';
5
6
  export type AnchorElement<E extends HTMLElement = HTMLElement> = E;
@@ -195,6 +196,27 @@ export interface MenuProps<T, Multiple extends boolean | undefined = undefined>
195
196
  * ```
196
197
  */
197
198
  readonly maxWidth?: number | string;
199
+ /**
200
+ * List menu maximum height (px, em, etc.): the longer list scrolls.
201
+ * The menu of the actions should not scroll — give it the height of the window,
202
+ * e.g. `maxHeight="80vh"`\
203
+ * \
204
+ * **Default**: `18em`
205
+ */
206
+ readonly maxHeight?: number | string;
207
+ /**
208
+ * Marks the items which are the separators between the groups of the items.
209
+ * The menu renders them itself (the `Separator` part): the keyboard skips them,
210
+ * they are not selected and `children` is not called for them\
211
+ * Example:
212
+ * ```tsx
213
+ * <Menu
214
+ * items={[{ id: 'open' }, { id: 'separator' }, { id: 'delete' }]}
215
+ * isSeparator={item => item.id === 'separator'}
216
+ * >
217
+ * ```
218
+ */
219
+ readonly isSeparator?: (item: T) => boolean;
198
220
  }
199
221
  export interface MenuOverrides {
200
222
  /**
@@ -205,6 +227,10 @@ export interface MenuOverrides {
205
227
  * Popper wrapper
206
228
  */
207
229
  readonly Popper?: React.ComponentType<PopperProps & React.RefAttributes<HTMLDivElement>>;
230
+ /**
231
+ * The separator between the groups of the items (see `isSeparator`)
232
+ */
233
+ readonly Separator?: React.ComponentType<MenuSeparatorProps & React.RefAttributes<HTMLDivElement>>;
208
234
  }
209
235
  export type MenuRef = {
210
236
  /**
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const react_1 = __importDefault(require("react"));
40
40
  const context_1 = __importStar(require("./context"));
41
41
  const MenuList_1 = __importDefault(require("./MenuList"));
42
+ const MenuSeparator_1 = __importDefault(require("./MenuSeparator"));
42
43
  const Popper_1 = __importDefault(require("../Popper"));
43
44
  const ClickOutside_1 = __importDefault(require("../ClickOutside"));
44
45
  const onRequestCloseDefault = () => undefined;
@@ -56,11 +57,27 @@ const isEqual = (a, b) => {
56
57
  return keysA.every(key => Object.prototype.hasOwnProperty.call(b, key) && a[key] === b[key]);
57
58
  };
58
59
  const MenuContainer = react_1.default.forwardRef((props, ref) => {
59
- const { items, value, anchorElement = null, overrides, children, closeOutsideClick = true, closeOnAnchorClick = false, isOpen = false, anchorPos = 'bottom', alternativePlacements = ['bottom', 'top'], positionStrategy, autoFlip, viewportMargin, offset, multiple = false, autofocus = true, closeOnSelect = !multiple, onRequestClose = onRequestCloseDefault, onAnchorPosChanged, zIndex, onSelectItem, getOptionSelected, maxWidth, } = props;
60
+ const { items, value, anchorElement = null, overrides, children, closeOutsideClick = true, closeOnAnchorClick = false, isOpen = false, anchorPos = 'bottom', alternativePlacements = ['bottom', 'top'], positionStrategy, autoFlip, viewportMargin, offset, multiple = false, autofocus = true, closeOnSelect = !multiple, onRequestClose = onRequestCloseDefault, onAnchorPosChanged, zIndex, onSelectItem, getOptionSelected, maxWidth, maxHeight, isSeparator, } = props;
60
61
  const overridesMap = react_1.default.useMemo(() => ({
61
62
  List: (overrides === null || overrides === void 0 ? void 0 : overrides.List) || MenuList_1.default,
62
63
  Popper: (overrides === null || overrides === void 0 ? void 0 : overrides.Popper) || Popper_1.default,
63
- }), [overrides === null || overrides === void 0 ? void 0 : overrides.List, overrides === null || overrides === void 0 ? void 0 : overrides.Popper]);
64
+ Separator: (overrides === null || overrides === void 0 ? void 0 : overrides.Separator) || MenuSeparator_1.default,
65
+ }), [overrides === null || overrides === void 0 ? void 0 : overrides.List, overrides === null || overrides === void 0 ? void 0 : overrides.Popper, overrides === null || overrides === void 0 ? void 0 : overrides.Separator]);
66
+ // The separators are not the options: the keyboard skips them, they are not selected
67
+ const isOption = react_1.default.useCallback((index) => index >= 0 &&
68
+ index < items.length &&
69
+ !(typeof isSeparator === 'function' && isSeparator(items[index])), [items, isSeparator]);
70
+ /**
71
+ * The nearest option from the index in the direction (1 — down, -1 — up), -1 when there is none
72
+ */
73
+ const findOption = react_1.default.useCallback((from, direction) => {
74
+ for (let index = from; index >= 0 && index < items.length; index += direction) {
75
+ if (isOption(index)) {
76
+ return index;
77
+ }
78
+ }
79
+ return -1;
80
+ }, [items.length, isOption]);
64
81
  const menuListRef = react_1.default.useRef(null);
65
82
  const menuPopperRef = react_1.default.useRef(null);
66
83
  // Starts as `false` so that a menu mounted in the open state is focused and scrolled too
@@ -134,7 +151,7 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
134
151
  return;
135
152
  const item = items[index];
136
153
  // Falsy items such as 0 or an empty string are valid options
137
- if (item === undefined)
154
+ if (item === undefined || !isOption(index))
138
155
  return;
139
156
  if (typeof onSelectItem === 'function') {
140
157
  if (multiple) {
@@ -155,7 +172,7 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
155
172
  onRequestClose();
156
173
  }
157
174
  }
158
- }, [onSelectItem, onRequestClose, compareFunc, closeOnSelect, items, value, multiple]);
175
+ }, [onSelectItem, onRequestClose, compareFunc, closeOnSelect, items, value, multiple, isOption]);
159
176
  const highlightIndex = react_1.default.useCallback((index) => {
160
177
  const validIndex = Math.max(-1, Math.min(index, items.length - 1));
161
178
  isKeyboardNavigationRef.current = true;
@@ -165,20 +182,21 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
165
182
  dispatch((0, context_1.actionSetmenuState)({ markedIndex: validIndex, hoveredIndex: -1 }));
166
183
  scrollToIndex(validIndex);
167
184
  }, [dispatch, items.length, scrollToIndex]);
185
+ // Without the previous (next) option the highlight stays where it is
168
186
  const highlightPrevItem = react_1.default.useCallback(() => {
169
- const newIndex = markedIndex - 1;
170
- highlightIndex(newIndex >= 0 ? newIndex : 0);
171
- }, [markedIndex, highlightIndex]);
187
+ const newIndex = findOption(markedIndex - 1, -1);
188
+ highlightIndex(newIndex >= 0 ? newIndex : findOption(0, 1));
189
+ }, [markedIndex, highlightIndex, findOption]);
172
190
  const highlightNextItem = react_1.default.useCallback(() => {
173
- const newIndex = markedIndex + 1;
174
- highlightIndex(newIndex < items.length ? newIndex : items.length - 1);
175
- }, [markedIndex, items.length, highlightIndex]);
191
+ const newIndex = findOption(markedIndex + 1, 1);
192
+ highlightIndex(newIndex >= 0 ? newIndex : findOption(items.length - 1, -1));
193
+ }, [markedIndex, items.length, highlightIndex, findOption]);
176
194
  const highlightFirstItem = react_1.default.useCallback(() => {
177
- highlightIndex(0);
178
- }, [highlightIndex]);
195
+ highlightIndex(findOption(0, 1));
196
+ }, [highlightIndex, findOption]);
179
197
  const highlightLastItem = react_1.default.useCallback(() => {
180
- highlightIndex(items.length - 1);
181
- }, [highlightIndex, items.length]);
198
+ highlightIndex(findOption(items.length - 1, -1));
199
+ }, [highlightIndex, items.length, findOption]);
182
200
  const selectHighlightedItem = react_1.default.useCallback(() => {
183
201
  if (markedIndex > -1 && markedIndex < items.length) {
184
202
  selectItem(markedIndex);
@@ -267,10 +285,11 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
267
285
  if (!isOpen) {
268
286
  // The menu is already closed by the parent, so onRequestClose must not be called again
269
287
  dispatch((0, context_1.actionSetmenuState)({ markedIndex: -1, hoveredIndex: -1 }));
270
- // Otherwise the focus is lost together with the unmounted list
288
+ // Otherwise the focus is lost together with the unmounted list.
289
+ // Without the scroll: an anchor in a sticky header is scrolled to its place in the document
271
290
  if (hasFocusRef.current) {
272
291
  hasFocusRef.current = false;
273
- (_a = lastAnchorRef.current) === null || _a === void 0 ? void 0 : _a.focus();
292
+ (_a = lastAnchorRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
274
293
  }
275
294
  return;
276
295
  }
@@ -285,7 +304,8 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
285
304
  var _a;
286
305
  scrollToFirstSelected();
287
306
  if (autofocus) {
288
- (_a = menuListRef.current) === null || _a === void 0 ? void 0 : _a.focus();
307
+ // The list is already placed by the popper: focusing must not scroll the page
308
+ (_a = menuListRef.current) === null || _a === void 0 ? void 0 : _a.focus({ preventScroll: true });
289
309
  }
290
310
  }, 15);
291
311
  }, [isOpen, autofocus, dispatch, scrollToFirstSelected]);
@@ -367,7 +387,10 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
367
387
  onAnchorPosChanged(newPlacement);
368
388
  }
369
389
  }, [onAnchorPosChanged]);
370
- const renderedChildren = react_1.default.useMemo(() => items.map((item, index) => children({ item, index }, {
390
+ const SeparatorComponent = overridesMap.Separator;
391
+ const renderedChildren = react_1.default.useMemo(() => items.map((item, index) =>
392
+ // the separator stays among the children: the index of the child is the index of the item
393
+ !isOption(index) ? (react_1.default.createElement(SeparatorComponent, { key: index })) : (children({ item, index }, {
371
394
  key: index,
372
395
  onMouseEnter: itemMouseEnterHandler(index),
373
396
  // mouseenter is skipped during the keyboard navigation, so the item under a moving
@@ -377,8 +400,10 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
377
400
  onClick: itemClickHandler(index),
378
401
  selected: selectedIndexes.includes(index),
379
402
  hovered: markedIndex === index,
380
- })), [
403
+ }))), [
381
404
  items,
405
+ isOption,
406
+ SeparatorComponent,
382
407
  children,
383
408
  selectedIndexes,
384
409
  markedIndex,
@@ -397,7 +422,7 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
397
422
  const ListComponent = overridesMap.List;
398
423
  return (react_1.default.createElement(ClickOutside_1.default, { onOutsideClick: onRequestClose, mouseEvent: isOpen && closeOutsideClick ? 'onMouseDown' : false, ignoreElements: closeOnAnchorClick ? undefined : [anchorElement] },
399
424
  react_1.default.createElement(PopperComponent, { isOpen: Boolean(isOpen), ref: menuPopperRef, zIndex: zIndex, anchorPos: anchorPos, alternativePlacements: alternativePlacements, anchorElement: anchorElement, onAnchorPosChanged: onAnchorPosChangedMemo, positionStrategy: positionStrategy, autoFlip: autoFlip, viewportMargin: viewportMargin, offset: offset },
400
- react_1.default.createElement(ListComponent, { anchorPos: anchorPos, isOpen: Boolean(isOpen), ref: menuListRef, maxWidth: maxWidth, onKeyDown: listKeydownEvent, onMouseMoveCapture: listMouseMoveHandler, onFocus: listFocusHandler, onBlur: listBlurHandler, role: "listbox", "aria-multiselectable": multiple || undefined }, renderedChildren))));
425
+ react_1.default.createElement(ListComponent, { anchorPos: anchorPos, isOpen: Boolean(isOpen), ref: menuListRef, maxWidth: maxWidth, maxHeight: maxHeight, onKeyDown: listKeydownEvent, onMouseMoveCapture: listMouseMoveHandler, onFocus: listFocusHandler, onBlur: listBlurHandler, role: "listbox", "aria-multiselectable": multiple || undefined }, renderedChildren))));
401
426
  });
402
427
  MenuContainer.displayName = 'MenuContainer';
403
428
  exports.default = MenuContainer;
@@ -4,6 +4,10 @@ export interface MenuListProps extends React.HTMLAttributes<HTMLDivElement> {
4
4
  readonly isOpen: boolean;
5
5
  readonly anchorPos: AnchorPos;
6
6
  readonly maxWidth?: number | string;
7
+ /**
8
+ * The maximum height of the list: the longer list scrolls. Default: `18em`
9
+ */
10
+ readonly maxHeight?: number | string;
7
11
  }
8
12
  export declare const MenuList: React.ForwardRefExoticComponent<MenuListProps & React.RefAttributes<HTMLDivElement>>;
9
13
  export default MenuList;
package/Menu/MenuList.js CHANGED
@@ -77,11 +77,11 @@ const StyledMenuList = styled_1.default.div `
77
77
  box-sizing: border-box;
78
78
  overflow-y: auto;
79
79
  padding: 0.4em;
80
- max-height: 18em;
80
+ max-height: ${({ $maxHeight = '18em' }) => typeof $maxHeight === 'number' ? `${$maxHeight}px` : $maxHeight};
81
81
  `;
82
82
  exports.MenuList = react_1.default.forwardRef((props, ref) => {
83
- const { isOpen, anchorPos, maxWidth, children } = props, nativeProps = __rest(props, ["isOpen", "anchorPos", "maxWidth", "children"]);
84
- return (react_1.default.createElement(StyledMenuList, Object.assign({ tabIndex: -1, "$isOpen": isOpen, "$maxWidth": maxWidth, "$anchorPos": anchorPos }, nativeProps, { ref: ref }), children));
83
+ const { isOpen, anchorPos, maxWidth, maxHeight, children } = props, nativeProps = __rest(props, ["isOpen", "anchorPos", "maxWidth", "maxHeight", "children"]);
84
+ return (react_1.default.createElement(StyledMenuList, Object.assign({ tabIndex: -1, "$isOpen": isOpen, "$maxWidth": maxWidth, "$maxHeight": maxHeight, "$anchorPos": anchorPos }, nativeProps, { ref: ref }), children));
85
85
  });
86
86
  exports.MenuList.displayName = 'MenuList';
87
87
  exports.default = exports.MenuList;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export type MenuSeparatorProps = React.HTMLAttributes<HTMLDivElement>;
3
+ /**
4
+ * The separator of the menu: not an option — it is not highlighted, selected or announced
5
+ * (a listbox may contain the options only, so it is hidden from the screen readers)
6
+ */
7
+ export declare const MenuSeparator: React.ForwardRefExoticComponent<MenuSeparatorProps & React.RefAttributes<HTMLDivElement>>;
8
+ export default MenuSeparator;
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MenuSeparator = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const styled_1 = __importDefault(require("@emotion/styled"));
9
+ const tokens_1 = require("../ThemeProvider/tokens");
10
+ /**
11
+ * The line between the groups of the items. The space around it separates the groups:
12
+ * a line flush with the items is not seen
13
+ */
14
+ const StyledMenuSeparator = styled_1.default.div `
15
+ flex: none;
16
+ height: 1px;
17
+ margin: ${({ theme }) => {
18
+ const { y, x } = (0, tokens_1.themePadding)(theme, 'item');
19
+ return `${y} ${x}`;
20
+ }};
21
+ background-color: ${({ theme }) => theme.color.textPrimary.alpha(0.12).toString()};
22
+ `;
23
+ /**
24
+ * The separator of the menu: not an option — it is not highlighted, selected or announced
25
+ * (a listbox may contain the options only, so it is hidden from the screen readers)
26
+ */
27
+ exports.MenuSeparator = react_1.default.forwardRef((props, ref) => (react_1.default.createElement(StyledMenuSeparator, Object.assign({ role: "presentation", "aria-hidden": true }, props, { ref: ref }))));
28
+ exports.MenuSeparator.displayName = 'MenuSeparator';
29
+ exports.default = exports.MenuSeparator;
package/Menu/index.d.ts CHANGED
@@ -5,6 +5,7 @@ export type AnchorPos = PopperAnchorPos;
5
5
  export * from './MenuContainer';
6
6
  export * from './MenuItem';
7
7
  export * from './MenuList';
8
+ export * from './MenuSeparator';
8
9
  declare const _default: <T, Multiple extends boolean | undefined = undefined>(props: MenuProps<T, Multiple> & {
9
10
  ref?: React.Ref<MenuRef>;
10
11
  }) => React.ReactElement;
package/Menu/index.js CHANGED
@@ -23,6 +23,7 @@ const MenuContainer_1 = __importDefault(require("./MenuContainer"));
23
23
  __exportStar(require("./MenuContainer"), exports);
24
24
  __exportStar(require("./MenuItem"), exports);
25
25
  __exportStar(require("./MenuList"), exports);
26
+ __exportStar(require("./MenuSeparator"), exports);
26
27
  const Menu = react_1.default.forwardRef((props, ref) => (react_1.default.createElement(context_1.MenuContextProvider, null,
27
28
  react_1.default.createElement(MenuContainer_1.default, Object.assign({}, props, { ref: ref })))));
28
29
  Menu.displayName = 'Menu';
@@ -25,7 +25,8 @@ const StyledModalInner = styled_1.default.div `
25
25
  pointer-events: all;
26
26
  position: relative;
27
27
  background-color: ${({ theme }) => theme.color.surface.toString()};
28
- box-shadow: ${({ theme }) => (0, tokens_1.elevation)(theme, 'popup')};
28
+ /* the themes made before the modal level keep the popup shadow for the windows */
29
+ box-shadow: ${({ theme }) => (0, tokens_1.elevation)(theme, 'modal', theme.elevation.popup)};
29
30
  border-radius: ${({ theme }) => theme.shape.radiusFactor * 2}em;
30
31
  margin-top: 2em;
31
32
  transition:
@@ -21,7 +21,7 @@ export type PaginationItemProps = ButtonProps & {
21
21
  readonly soft?: boolean;
22
22
  };
23
23
  declare const _default: React.ForwardRefExoticComponent<import("../Button/ButtonBase").ButtonBaseProps & {
24
- readonly variant?: "standard" | "outlined" | "plain";
24
+ readonly variant?: "standard" | "outlined" | "plain" | "soft";
25
25
  } & {
26
26
  /**
27
27
  * What the button does: opens a page or goes to the first, previous, next or last page
@@ -59,7 +59,7 @@ const SelectboxMenu = React.forwardRef((props, ref) => {
59
59
  if (onRequestClose) {
60
60
  onRequestClose(evt);
61
61
  }
62
- anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.focus();
62
+ anchorElement === null || anchorElement === void 0 ? void 0 : anchorElement.focus({ preventScroll: true });
63
63
  }
64
64
  }, [onRequestClose, anchorElement]);
65
65
  return (React.createElement(Menu_1.default, Object.assign({ ref: ref, offset: 0, anchorPos: anchorPos, multiple: multiple, items: items, value: value, isOpen: isOpen && items.length > 0, autofocus: true, anchorElement: anchorElement, closeOutsideClick: false, getOptionSelected: getOptionSelected, onSelectItem: handleSelectItem, closeOnSelect: !multiple, onRequestClose: handleRequestClose }, restMenuProps), ({ index, item }, itemProps) => children({ index, item: item }, itemProps)));
@@ -0,0 +1,51 @@
1
+ import React from 'react';
2
+ export type StepState = 'completed' | 'active' | 'pending';
3
+ export type StepProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'children'> & {
4
+ /**
5
+ * The label of the step
6
+ */
7
+ readonly children: React.ReactNode;
8
+ /**
9
+ * The small text under the label: «optional», «1 of 2», the time
10
+ */
11
+ readonly caption?: React.ReactNode;
12
+ /**
13
+ * The step is completed regardless of `activeStep` of the stepper
14
+ * (e.g. the payment came before the work)
15
+ */
16
+ readonly completed?: boolean;
17
+ /**
18
+ * The attention on the step: `warning` — something is not finished, `error` — something went wrong.
19
+ * Colors the icon of the step that is not completed
20
+ */
21
+ readonly status?: 'warning' | 'error';
22
+ /**
23
+ * Own icon instead of the number
24
+ */
25
+ readonly icon?: React.ReactNode;
26
+ };
27
+ declare const _default: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLDivElement>, "children"> & {
28
+ /**
29
+ * The label of the step
30
+ */
31
+ readonly children: React.ReactNode;
32
+ /**
33
+ * The small text under the label: «optional», «1 of 2», the time
34
+ */
35
+ readonly caption?: React.ReactNode;
36
+ /**
37
+ * The step is completed regardless of `activeStep` of the stepper
38
+ * (e.g. the payment came before the work)
39
+ */
40
+ readonly completed?: boolean;
41
+ /**
42
+ * The attention on the step: `warning` — something is not finished, `error` — something went wrong.
43
+ * Colors the icon of the step that is not completed
44
+ */
45
+ readonly status?: "warning" | "error";
46
+ /**
47
+ * Own icon instead of the number
48
+ */
49
+ readonly icon?: React.ReactNode;
50
+ } & React.RefAttributes<HTMLDivElement>>;
51
+ export default _default;