@via-profit/ui-kit 0.9.0 → 0.10.0

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 (59) 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/Menu/MenuContainer.d.ts +26 -0
  10. package/Menu/MenuContainer.js +40 -17
  11. package/Menu/MenuList.d.ts +4 -0
  12. package/Menu/MenuList.js +3 -3
  13. package/Menu/MenuSeparator.d.ts +8 -0
  14. package/Menu/MenuSeparator.js +29 -0
  15. package/Menu/index.d.ts +1 -0
  16. package/Menu/index.js +1 -0
  17. package/Modal/BaseModal/ModalInner.js +2 -1
  18. package/Modal/BaseModal/ModalWrapper.js +3 -1
  19. package/Pagination/PaginationItem.d.ts +1 -1
  20. package/Stepper/Step.d.ts +51 -0
  21. package/Stepper/Step.js +169 -0
  22. package/Stepper/Stepper.d.ts +49 -0
  23. package/Stepper/Stepper.js +74 -0
  24. package/Stepper/context.d.ts +21 -0
  25. package/Stepper/context.js +12 -0
  26. package/Stepper/index.d.ts +6 -0
  27. package/Stepper/index.js +10 -0
  28. package/Surface/SurfaceSubheader.js +4 -3
  29. package/Tabs/Tab.d.ts +8 -0
  30. package/Tabs/Tab.js +75 -32
  31. package/Tabs/TabList.js +24 -5
  32. package/Tabs/Tabs.d.ts +25 -1
  33. package/Tabs/Tabs.js +5 -1
  34. package/Tabs/context.d.ts +18 -0
  35. package/Tabs/context.js +9 -1
  36. package/Tabs/index.d.ts +1 -1
  37. package/ThemeProvider/index.d.ts +6 -1
  38. package/ThemeProvider/tokens.js +1 -0
  39. package/esm/Badge/BadgeContainer.js +1 -1
  40. package/esm/Badge/BadgeTextWrapper.js +3 -1
  41. package/esm/Button/buttonVariants.js +57 -8
  42. package/esm/Calendar/CalendarWeekDayLabel.js +1 -1
  43. package/esm/Menu/MenuContainer.js +40 -17
  44. package/esm/Menu/MenuList.js +3 -3
  45. package/esm/Menu/MenuSeparator.js +23 -0
  46. package/esm/Menu/index.js +1 -0
  47. package/esm/Modal/BaseModal/ModalInner.js +2 -1
  48. package/esm/Modal/BaseModal/ModalWrapper.js +3 -1
  49. package/esm/Stepper/Step.js +164 -0
  50. package/esm/Stepper/Stepper.js +69 -0
  51. package/esm/Stepper/context.js +6 -0
  52. package/esm/Stepper/index.js +3 -0
  53. package/esm/Surface/SurfaceSubheader.js +4 -3
  54. package/esm/Tabs/Tab.js +77 -34
  55. package/esm/Tabs/TabList.js +24 -5
  56. package/esm/Tabs/Tabs.js +5 -1
  57. package/esm/Tabs/context.js +8 -0
  58. package/esm/ThemeProvider/tokens.js +1 -0
  59. package/package.json +31 -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.0 — 2026-10-21
8
+
9
+ ### Добавлено
10
+
11
+ - **Menu**: свойство `maxHeight` — максимальная высота списка (по умолчанию, как раньше, `18em`). Меню действий с разделителями переросло 18em и прокручивалось на пару пикселей;
12
+ - **Menu**: разделители групп пунктов — свойство `isSeparator` отмечает элементы `items`, которые меню рисует само линией с отступами (часть `Separator`, компонент `MenuSeparator`). Стрелки, Home и End их пропускают, выбрать их нельзя, программы чтения с экрана их не объявляют;
13
+ - **Stepper** — шаги процесса: выполненные (бледная подложка и галочка цвета), текущий (подложка контрастного цвета темы, обводка и номер цвета), будущие (бледно-серые), между ними линии — цветом после выполненных. В строку или столбец (`orientation`), подписи под кружками (`alternativeLabel`), у шага — `caption`, `completed`, `status` (`warning` / `error`), своя `icon`;
14
+ - **Tabs**: `variant` — `underline` (линия под текстом выбранной вкладки со скруглёнными концами, скруглённая подложка при наведении) и `segmented` (вкладки в лотке, выбранная — плашка); `size` — `s` / `m` / `l` (32 / 40 / 48px при тексте 14px); `counter` у `Tab` — счётчик: бледный бейдж цвета вкладок (`soft`), серый при нуле;
15
+ - **Button**: вариант `soft` — тональная кнопка: бледная подложка цвета кнопки и текст того же оттенка, как у `plain` под курсором. Тише `standard`, заметнее `plain`; под курсором подложка темнеет. Работает с любым `color`;
16
+ - **ThemeProvider**: уровень тени `elevation.modal` — у модальных окон (Dialog, Drawer, MessageBox, ConfirmBox), глубже, чем у всплывающих элементов. Если в теме его нет, окна берут `elevation.popup` темы, как раньше, а без него — новую тень уровня по умолчанию.
17
+
18
+ ### Изменено
19
+
20
+ - **Tabs**: новый вид по умолчанию — линия только под текстом выбранной вкладки (а не во всю её ширину), концы скруглены, подложка при наведении скруглена; текст вкладки — 0.929em (13px при тексте 14px; S — 12px, L — 14px), вес 400, выбранная — 500 и темнее цвета вкладок. Линия под рядом и у вертикальных вкладок светлее.
21
+ - **Badge**: ниже — вертикальный отступ текста 0.15em вместо 0.3em, высота строки 1.35: бейдж примерно на высоту строки текста рядом, а не выше неё. Размер текста прежний. Скругление — как у кнопок (`radiusFactor × 2` вместо `× 2.5`).
22
+ - **Surface**: подзаголовок — размер текста (1em вместо 0.9em), вес 300 вместо 200: тише текста за счёт цвета и Light, но не мелкий.
23
+ - **Button**: у нейтральной кнопки `outlined` (`color="default"` или без `color`) — светлая рамка (цвет текста с прозрачностью 0.18, под курсором 0.35) и текст цвета темы без затемнения. Тёмная рамка цвета текста делала второстепенные кнопки («Отмена», «Сбросить») и группы кнопок-фильтров тяжёлыми. Цветные `outlined` (`primary`, `secondary`, свой цвет) не изменились.
24
+ - **Calendar** (дни недели): вес шрифта 300 вместо 200 — начертания 200 нет в распространённых шрифтах, браузер всё равно рисовал 300.
25
+
26
+ ## 0.9.1 — 2026-10-19
27
+
28
+ ### Исправлено
29
+
30
+ - **Modal** (Dialog, Drawer, MessageBox, ConfirmBox): при открытии окно больше не уводит фокус с поля, которое содержимое окна сфокусировало само (например, поле поиска), на следующий элемент.
31
+
5
32
  ## 0.9.0 — 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;
@@ -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);
@@ -367,7 +385,10 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
367
385
  onAnchorPosChanged(newPlacement);
368
386
  }
369
387
  }, [onAnchorPosChanged]);
370
- const renderedChildren = react_1.default.useMemo(() => items.map((item, index) => children({ item, index }, {
388
+ const SeparatorComponent = overridesMap.Separator;
389
+ const renderedChildren = react_1.default.useMemo(() => items.map((item, index) =>
390
+ // the separator stays among the children: the index of the child is the index of the item
391
+ !isOption(index) ? (react_1.default.createElement(SeparatorComponent, { key: index })) : (children({ item, index }, {
371
392
  key: index,
372
393
  onMouseEnter: itemMouseEnterHandler(index),
373
394
  // mouseenter is skipped during the keyboard navigation, so the item under a moving
@@ -377,8 +398,10 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
377
398
  onClick: itemClickHandler(index),
378
399
  selected: selectedIndexes.includes(index),
379
400
  hovered: markedIndex === index,
380
- })), [
401
+ }))), [
381
402
  items,
403
+ isOption,
404
+ SeparatorComponent,
382
405
  children,
383
406
  selectedIndexes,
384
407
  markedIndex,
@@ -397,7 +420,7 @@ const MenuContainer = react_1.default.forwardRef((props, ref) => {
397
420
  const ListComponent = overridesMap.List;
398
421
  return (react_1.default.createElement(ClickOutside_1.default, { onOutsideClick: onRequestClose, mouseEvent: isOpen && closeOutsideClick ? 'onMouseDown' : false, ignoreElements: closeOnAnchorClick ? undefined : [anchorElement] },
399
422
  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))));
423
+ 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
424
  });
402
425
  MenuContainer.displayName = 'MenuContainer';
403
426
  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:
@@ -50,7 +50,9 @@ const ModalWrapper = props => {
50
50
  const container = containerRef.current;
51
51
  if (isOpen && container) {
52
52
  TabManager_1.default.registerContainer(container);
53
- if (autofocus) {
53
+ // the content may focus its field itself (the effects of the children run first):
54
+ // the modal must not move the focus away from it to the next element
55
+ if (autofocus && !container.contains(document.activeElement)) {
54
56
  TabManager_1.default.focusNext();
55
57
  }
56
58
  }
@@ -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
@@ -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;