@via-profit/ui-kit 0.9.1 → 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.
- package/Badge/BadgeContainer.js +1 -1
- package/Badge/BadgeTextWrapper.js +3 -1
- package/Button/buttonVariants.d.ts +1 -1
- package/Button/buttonVariants.js +57 -8
- package/Button/index.d.ts +4 -4
- package/CHANGELOG.md +21 -0
- package/Calendar/CalendarControlButton.d.ts +1 -1
- package/Calendar/CalendarWeekDayLabel.js +1 -1
- package/Menu/MenuContainer.d.ts +26 -0
- package/Menu/MenuContainer.js +40 -17
- package/Menu/MenuList.d.ts +4 -0
- package/Menu/MenuList.js +3 -3
- package/Menu/MenuSeparator.d.ts +8 -0
- package/Menu/MenuSeparator.js +29 -0
- package/Menu/index.d.ts +1 -0
- package/Menu/index.js +1 -0
- package/Modal/BaseModal/ModalInner.js +2 -1
- package/Pagination/PaginationItem.d.ts +1 -1
- package/Stepper/Step.d.ts +51 -0
- package/Stepper/Step.js +169 -0
- package/Stepper/Stepper.d.ts +49 -0
- package/Stepper/Stepper.js +74 -0
- package/Stepper/context.d.ts +21 -0
- package/Stepper/context.js +12 -0
- package/Stepper/index.d.ts +6 -0
- package/Stepper/index.js +10 -0
- package/Surface/SurfaceSubheader.js +4 -3
- package/Tabs/Tab.d.ts +8 -0
- package/Tabs/Tab.js +75 -32
- package/Tabs/TabList.js +24 -5
- package/Tabs/Tabs.d.ts +25 -1
- package/Tabs/Tabs.js +5 -1
- package/Tabs/context.d.ts +18 -0
- package/Tabs/context.js +9 -1
- package/Tabs/index.d.ts +1 -1
- package/ThemeProvider/index.d.ts +6 -1
- package/ThemeProvider/tokens.js +1 -0
- package/esm/Badge/BadgeContainer.js +1 -1
- package/esm/Badge/BadgeTextWrapper.js +3 -1
- package/esm/Button/buttonVariants.js +57 -8
- package/esm/Calendar/CalendarWeekDayLabel.js +1 -1
- package/esm/Menu/MenuContainer.js +40 -17
- package/esm/Menu/MenuList.js +3 -3
- package/esm/Menu/MenuSeparator.js +23 -0
- package/esm/Menu/index.js +1 -0
- package/esm/Modal/BaseModal/ModalInner.js +2 -1
- package/esm/Stepper/Step.js +164 -0
- package/esm/Stepper/Stepper.js +69 -0
- package/esm/Stepper/context.js +6 -0
- package/esm/Stepper/index.js +3 -0
- package/esm/Surface/SurfaceSubheader.js +4 -3
- package/esm/Tabs/Tab.js +77 -34
- package/esm/Tabs/TabList.js +24 -5
- package/esm/Tabs/Tabs.js +5 -1
- package/esm/Tabs/context.js +8 -0
- package/esm/ThemeProvider/tokens.js +1 -0
- package/package.json +31 -1
package/Tabs/Tabs.js
CHANGED
|
@@ -27,7 +27,7 @@ const StyledTabs = styled_1.default.div `
|
|
|
27
27
|
*/
|
|
28
28
|
const escapeId = (value) => value.replace(/[^\w-]/g, char => `_${char.charCodeAt(0).toString(16)}`);
|
|
29
29
|
const Tabs = (props, ref) => {
|
|
30
|
-
const { value, defaultValue, onChange, orientation = 'horizontal', activation = 'auto', fullWidth = false, color, children } = props, nativeProps = __rest(props, ["value", "defaultValue", "onChange", "orientation", "activation", "fullWidth", "color", "children"]);
|
|
30
|
+
const { value, defaultValue, onChange, orientation = 'horizontal', activation = 'auto', fullWidth = false, variant = 'underline', size = 'm', color, children } = props, nativeProps = __rest(props, ["value", "defaultValue", "onChange", "orientation", "activation", "fullWidth", "variant", "size", "color", "children"]);
|
|
31
31
|
const isControlled = typeof value !== 'undefined';
|
|
32
32
|
const [internalValue, setInternalValue] = react_1.default.useState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : null);
|
|
33
33
|
const currentValue = isControlled ? value : internalValue;
|
|
@@ -60,6 +60,8 @@ const Tabs = (props, ref) => {
|
|
|
60
60
|
orientation,
|
|
61
61
|
activation,
|
|
62
62
|
fullWidth,
|
|
63
|
+
variant,
|
|
64
|
+
size,
|
|
63
65
|
color: $color,
|
|
64
66
|
getTabId: tabValue => `${baseId}-tab-${escapeId(tabValue)}`,
|
|
65
67
|
getPanelId: tabValue => `${baseId}-panel-${escapeId(tabValue)}`,
|
|
@@ -71,6 +73,8 @@ const Tabs = (props, ref) => {
|
|
|
71
73
|
orientation,
|
|
72
74
|
activation,
|
|
73
75
|
fullWidth,
|
|
76
|
+
variant,
|
|
77
|
+
size,
|
|
74
78
|
$color,
|
|
75
79
|
baseId,
|
|
76
80
|
]);
|
package/Tabs/context.d.ts
CHANGED
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Color from '../Color';
|
|
3
3
|
export type TabsOrientation = 'horizontal' | 'vertical';
|
|
4
|
+
/**
|
|
5
|
+
* `underline` — the line under the selected tab, `segmented` — the tabs in the tray, the selected one is the plate
|
|
6
|
+
*/
|
|
7
|
+
export type TabsVariant = 'underline' | 'segmented';
|
|
8
|
+
/**
|
|
9
|
+
* The height of the tab at the text of 14px: `s` — 32px, `m` — 40px, `l` — 48px
|
|
10
|
+
*/
|
|
11
|
+
export type TabsSize = 's' | 'm' | 'l';
|
|
12
|
+
/**
|
|
13
|
+
* The size of the text and the paddings of the tab, in the `em` of the tabs
|
|
14
|
+
*/
|
|
15
|
+
export declare const TAB_SIZES: Record<TabsSize, {
|
|
16
|
+
readonly fontSize: string;
|
|
17
|
+
readonly y: string;
|
|
18
|
+
readonly x: string;
|
|
19
|
+
}>;
|
|
4
20
|
export interface TabsContextValue {
|
|
5
21
|
/**
|
|
6
22
|
* The selected tab or `null` if nothing is selected
|
|
@@ -15,6 +31,8 @@ export interface TabsContextValue {
|
|
|
15
31
|
readonly orientation: TabsOrientation;
|
|
16
32
|
readonly activation: 'auto' | 'manual';
|
|
17
33
|
readonly fullWidth: boolean;
|
|
34
|
+
readonly variant: TabsVariant;
|
|
35
|
+
readonly size: TabsSize;
|
|
18
36
|
readonly color: Color;
|
|
19
37
|
readonly getTabId: (value: string) => string;
|
|
20
38
|
readonly getPanelId: (value: string) => string;
|
package/Tabs/context.js
CHANGED
|
@@ -3,8 +3,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.useTabsContext = exports.TabsContext = void 0;
|
|
6
|
+
exports.useTabsContext = exports.TabsContext = exports.TAB_SIZES = void 0;
|
|
7
7
|
const react_1 = __importDefault(require("react"));
|
|
8
|
+
/**
|
|
9
|
+
* The size of the text and the paddings of the tab, in the `em` of the tabs
|
|
10
|
+
*/
|
|
11
|
+
exports.TAB_SIZES = {
|
|
12
|
+
s: { fontSize: '0.857em', y: '0.5em', x: '0.7em' },
|
|
13
|
+
m: { fontSize: '0.929em', y: '0.75em', x: '0.9em' },
|
|
14
|
+
l: { fontSize: '1em', y: '1em', x: '1.05em' },
|
|
15
|
+
};
|
|
8
16
|
exports.TabsContext = react_1.default.createContext(null);
|
|
9
17
|
const useTabsContext = (component) => {
|
|
10
18
|
const context = react_1.default.useContext(exports.TabsContext);
|
package/Tabs/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export type { TabsProps } from './Tabs';
|
|
|
6
6
|
export type { TabProps } from './Tab';
|
|
7
7
|
export type { TabListProps } from './TabList';
|
|
8
8
|
export type { TabPanelProps } from './TabPanel';
|
|
9
|
-
export type { TabsOrientation } from './context';
|
|
9
|
+
export type { TabsOrientation, TabsVariant, TabsSize } from './context';
|
|
10
10
|
export default Tabs;
|
package/ThemeProvider/index.d.ts
CHANGED
|
@@ -63,9 +63,14 @@ export interface UIThemeOverrideFocusRing {
|
|
|
63
63
|
*/
|
|
64
64
|
export interface UIThemeOverrideElevation {
|
|
65
65
|
/**
|
|
66
|
-
* The elements over the page: Menu, Calendar, Toast, Tooltip
|
|
66
|
+
* The elements over the page: Menu, Calendar, Toast, Tooltip
|
|
67
67
|
*/
|
|
68
68
|
readonly popup?: string;
|
|
69
|
+
/**
|
|
70
|
+
* The modal windows over the page: Dialog, Drawer, MessageBox, ConfirmBox.
|
|
71
|
+
* Without a value the windows get the `popup` shadow of the theme, then the default shadow of the level
|
|
72
|
+
*/
|
|
73
|
+
readonly modal?: string;
|
|
69
74
|
/**
|
|
70
75
|
* The cards and the panels on the page: Surface, Accordion, Table
|
|
71
76
|
*/
|
package/ThemeProvider/tokens.js
CHANGED
|
@@ -31,6 +31,7 @@ exports.themeFocusRing = themeFocusRing;
|
|
|
31
31
|
*/
|
|
32
32
|
const defaultElevation = (theme) => ({
|
|
33
33
|
popup: `0 8px 24px ${theme.color.surface.darken(50).alpha(0.6).toString()}`,
|
|
34
|
+
modal: `0 16px 48px -12px ${theme.color.surface.darken(100).alpha(0.5).toString()}`,
|
|
34
35
|
surface: `0 8px 32px -13px ${theme.color.surface.darken(100).alpha(0.4).toString()}`,
|
|
35
36
|
control: `0 1px 3px ${theme.color.surface.darken(100).alpha(0.35).toString()}`,
|
|
36
37
|
});
|
|
@@ -12,7 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import styled from '@emotion/styled';
|
|
14
14
|
const StyledBadge = styled.span `
|
|
15
|
-
border-radius: ${({ theme }) => theme.shape.radiusFactor * 2
|
|
15
|
+
border-radius: ${({ theme }) => theme.shape.radiusFactor * 2}em;
|
|
16
16
|
font-size: 1em;
|
|
17
17
|
border-width: 0;
|
|
18
18
|
outline-style: solid;
|
|
@@ -13,7 +13,9 @@ import React from 'react';
|
|
|
13
13
|
import styled from '@emotion/styled';
|
|
14
14
|
const TextWrapper = styled.span `
|
|
15
15
|
font-size: 0.86em;
|
|
16
|
-
|
|
16
|
+
/* the badge is about the height of the line of the text around it, not taller */
|
|
17
|
+
line-height: 1.35;
|
|
18
|
+
padding: 0.15em 0.5em;
|
|
17
19
|
display: flex;
|
|
18
20
|
align-items: center;
|
|
19
21
|
user-select: none;
|
|
@@ -37,7 +37,7 @@ const getStandardColors = (color, theme) => {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
/**
|
|
40
|
-
* The main color of the outlined and
|
|
40
|
+
* The main color of the outlined, plain and soft variants (text and border)
|
|
41
41
|
*/
|
|
42
42
|
const getAccentColor = (color, theme) => {
|
|
43
43
|
switch (true) {
|
|
@@ -76,11 +76,23 @@ const standardStyles = ({ $color, $background, disabled, theme }) => css `
|
|
|
76
76
|
}
|
|
77
77
|
`}
|
|
78
78
|
`;
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* The border of the neutral outlined button: light, as the lines of the tables. The dark border
|
|
81
|
+
* of the text color makes «Отмена», «Сбросить» and the groups of the filters heavy;
|
|
82
|
+
* the colored outlined buttons keep the border of their color
|
|
83
|
+
*/
|
|
84
|
+
const neutralBorder = (theme) => theme.color.textPrimary.alpha(0.18).toString();
|
|
85
|
+
const outlinedStyles = ({ $color, $neutral, disabled, theme }) => css `
|
|
86
|
+
color: ${disabled
|
|
87
|
+
? theme.color.textPrimary.alpha(0.4).toString()
|
|
88
|
+
: $neutral
|
|
89
|
+
? $color.toString()
|
|
90
|
+
: $color.darken(30).toString()};
|
|
81
91
|
border-color: ${disabled
|
|
82
92
|
? theme.color.surface.darken(80).alpha(0.4).toString()
|
|
83
|
-
: $
|
|
93
|
+
: $neutral
|
|
94
|
+
? neutralBorder(theme)
|
|
95
|
+
: $color.toString()};
|
|
84
96
|
background-color: transparent;
|
|
85
97
|
border-style: solid;
|
|
86
98
|
border-width: ${CONTROL_BORDER};
|
|
@@ -88,10 +100,18 @@ const outlinedStyles = ({ $color, disabled, theme }) => css `
|
|
|
88
100
|
${!disabled &&
|
|
89
101
|
css `
|
|
90
102
|
&:hover {
|
|
91
|
-
background-color: ${$
|
|
103
|
+
background-color: ${$neutral
|
|
104
|
+
? $color.alpha(0.05).toString()
|
|
105
|
+
: $color.darken(20).alpha(0.1).toString()};
|
|
106
|
+
${$neutral &&
|
|
107
|
+
css `
|
|
108
|
+
border-color: ${theme.color.textPrimary.alpha(0.35).toString()};
|
|
109
|
+
`}
|
|
92
110
|
}
|
|
93
111
|
&:active {
|
|
94
|
-
background-color: ${$
|
|
112
|
+
background-color: ${$neutral
|
|
113
|
+
? $color.alpha(0.12).toString()
|
|
114
|
+
: $color.darken(50).alpha(0.3).toString()};
|
|
95
115
|
}
|
|
96
116
|
&:focus-visible {
|
|
97
117
|
outline-color: ${focusOutlineColor($color, theme)};
|
|
@@ -120,6 +140,32 @@ const plainStyles = ({ $color, disabled, theme }) => css `
|
|
|
120
140
|
}
|
|
121
141
|
`}
|
|
122
142
|
`;
|
|
143
|
+
/**
|
|
144
|
+
* The tonal button: the pale background of its color and the text of the same hue,
|
|
145
|
+
* as the plain button under the cursor. Quieter than the standard button, more visible than the plain one
|
|
146
|
+
*/
|
|
147
|
+
const softStyles = ({ $color, disabled, theme }) => css `
|
|
148
|
+
box-shadow: none;
|
|
149
|
+
color: ${disabled ? $color.alpha(0.4).toString() : $color.darken(30).toString()};
|
|
150
|
+
background-color: ${disabled ? $color.alpha(0.05).toString() : $color.alpha(0.1).toString()};
|
|
151
|
+
|
|
152
|
+
${!disabled &&
|
|
153
|
+
css `
|
|
154
|
+
&:hover {
|
|
155
|
+
color: ${$color.darken(50).toString()};
|
|
156
|
+
background-color: ${$color.alpha(0.18).toString()};
|
|
157
|
+
}
|
|
158
|
+
&:active {
|
|
159
|
+
color: ${$color.darken(80).toString()};
|
|
160
|
+
background-color: ${$color.alpha(0.3).toString()};
|
|
161
|
+
}
|
|
162
|
+
&:focus-visible {
|
|
163
|
+
outline-style: solid;
|
|
164
|
+
outline-width: 0.14em;
|
|
165
|
+
outline-color: ${focusOutlineColor($color, theme)};
|
|
166
|
+
}
|
|
167
|
+
`}
|
|
168
|
+
`;
|
|
123
169
|
/**
|
|
124
170
|
* Inside the ButtonGroup all the variants have the same border width, so the buttons have the same size
|
|
125
171
|
* and the selected (standard) button does not shrink. Two adjacent standard buttons are separated by a thin line
|
|
@@ -150,13 +196,16 @@ export const buttonVariantStyles = (params) => {
|
|
|
150
196
|
const colors = variant === 'standard'
|
|
151
197
|
? getStandardColors(color, theme)
|
|
152
198
|
: { $color: getAccentColor(color, theme), $background: getAccentColor(color, theme) };
|
|
153
|
-
const props = Object.assign(Object.assign({}, colors), { $variant: variant, $group: group, disabled,
|
|
199
|
+
const props = Object.assign(Object.assign({}, colors), { $variant: variant, $group: group, $neutral: typeof color === 'undefined' || color === 'default', disabled,
|
|
200
|
+
theme });
|
|
154
201
|
return css `
|
|
155
202
|
${variant === 'outlined'
|
|
156
203
|
? outlinedStyles(props)
|
|
157
204
|
: variant === 'plain'
|
|
158
205
|
? plainStyles(props)
|
|
159
|
-
:
|
|
206
|
+
: variant === 'soft'
|
|
207
|
+
? softStyles(props)
|
|
208
|
+
: standardStyles(props)}
|
|
160
209
|
${groupStyles(props)}
|
|
161
210
|
`;
|
|
162
211
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import useContext, { actionSetmenuState } from './context';
|
|
3
3
|
import List from './MenuList';
|
|
4
|
+
import Separator from './MenuSeparator';
|
|
4
5
|
import Popper from '../Popper';
|
|
5
6
|
import ClickOutside from '../ClickOutside';
|
|
6
7
|
const onRequestCloseDefault = () => undefined;
|
|
@@ -18,11 +19,27 @@ const isEqual = (a, b) => {
|
|
|
18
19
|
return keysA.every(key => Object.prototype.hasOwnProperty.call(b, key) && a[key] === b[key]);
|
|
19
20
|
};
|
|
20
21
|
const MenuContainer = React.forwardRef((props, ref) => {
|
|
21
|
-
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;
|
|
22
|
+
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;
|
|
22
23
|
const overridesMap = React.useMemo(() => ({
|
|
23
24
|
List: (overrides === null || overrides === void 0 ? void 0 : overrides.List) || List,
|
|
24
25
|
Popper: (overrides === null || overrides === void 0 ? void 0 : overrides.Popper) || Popper,
|
|
25
|
-
|
|
26
|
+
Separator: (overrides === null || overrides === void 0 ? void 0 : overrides.Separator) || Separator,
|
|
27
|
+
}), [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]);
|
|
28
|
+
// The separators are not the options: the keyboard skips them, they are not selected
|
|
29
|
+
const isOption = React.useCallback((index) => index >= 0 &&
|
|
30
|
+
index < items.length &&
|
|
31
|
+
!(typeof isSeparator === 'function' && isSeparator(items[index])), [items, isSeparator]);
|
|
32
|
+
/**
|
|
33
|
+
* The nearest option from the index in the direction (1 — down, -1 — up), -1 when there is none
|
|
34
|
+
*/
|
|
35
|
+
const findOption = React.useCallback((from, direction) => {
|
|
36
|
+
for (let index = from; index >= 0 && index < items.length; index += direction) {
|
|
37
|
+
if (isOption(index)) {
|
|
38
|
+
return index;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return -1;
|
|
42
|
+
}, [items.length, isOption]);
|
|
26
43
|
const menuListRef = React.useRef(null);
|
|
27
44
|
const menuPopperRef = React.useRef(null);
|
|
28
45
|
// Starts as `false` so that a menu mounted in the open state is focused and scrolled too
|
|
@@ -96,7 +113,7 @@ const MenuContainer = React.forwardRef((props, ref) => {
|
|
|
96
113
|
return;
|
|
97
114
|
const item = items[index];
|
|
98
115
|
// Falsy items such as 0 or an empty string are valid options
|
|
99
|
-
if (item === undefined)
|
|
116
|
+
if (item === undefined || !isOption(index))
|
|
100
117
|
return;
|
|
101
118
|
if (typeof onSelectItem === 'function') {
|
|
102
119
|
if (multiple) {
|
|
@@ -117,7 +134,7 @@ const MenuContainer = React.forwardRef((props, ref) => {
|
|
|
117
134
|
onRequestClose();
|
|
118
135
|
}
|
|
119
136
|
}
|
|
120
|
-
}, [onSelectItem, onRequestClose, compareFunc, closeOnSelect, items, value, multiple]);
|
|
137
|
+
}, [onSelectItem, onRequestClose, compareFunc, closeOnSelect, items, value, multiple, isOption]);
|
|
121
138
|
const highlightIndex = React.useCallback((index) => {
|
|
122
139
|
const validIndex = Math.max(-1, Math.min(index, items.length - 1));
|
|
123
140
|
isKeyboardNavigationRef.current = true;
|
|
@@ -127,20 +144,21 @@ const MenuContainer = React.forwardRef((props, ref) => {
|
|
|
127
144
|
dispatch(actionSetmenuState({ markedIndex: validIndex, hoveredIndex: -1 }));
|
|
128
145
|
scrollToIndex(validIndex);
|
|
129
146
|
}, [dispatch, items.length, scrollToIndex]);
|
|
147
|
+
// Without the previous (next) option the highlight stays where it is
|
|
130
148
|
const highlightPrevItem = React.useCallback(() => {
|
|
131
|
-
const newIndex = markedIndex - 1;
|
|
132
|
-
highlightIndex(newIndex >= 0 ? newIndex : 0);
|
|
133
|
-
}, [markedIndex, highlightIndex]);
|
|
149
|
+
const newIndex = findOption(markedIndex - 1, -1);
|
|
150
|
+
highlightIndex(newIndex >= 0 ? newIndex : findOption(0, 1));
|
|
151
|
+
}, [markedIndex, highlightIndex, findOption]);
|
|
134
152
|
const highlightNextItem = React.useCallback(() => {
|
|
135
|
-
const newIndex = markedIndex + 1;
|
|
136
|
-
highlightIndex(newIndex
|
|
137
|
-
}, [markedIndex, items.length, highlightIndex]);
|
|
153
|
+
const newIndex = findOption(markedIndex + 1, 1);
|
|
154
|
+
highlightIndex(newIndex >= 0 ? newIndex : findOption(items.length - 1, -1));
|
|
155
|
+
}, [markedIndex, items.length, highlightIndex, findOption]);
|
|
138
156
|
const highlightFirstItem = React.useCallback(() => {
|
|
139
|
-
highlightIndex(0);
|
|
140
|
-
}, [highlightIndex]);
|
|
157
|
+
highlightIndex(findOption(0, 1));
|
|
158
|
+
}, [highlightIndex, findOption]);
|
|
141
159
|
const highlightLastItem = React.useCallback(() => {
|
|
142
|
-
highlightIndex(items.length - 1);
|
|
143
|
-
}, [highlightIndex, items.length]);
|
|
160
|
+
highlightIndex(findOption(items.length - 1, -1));
|
|
161
|
+
}, [highlightIndex, items.length, findOption]);
|
|
144
162
|
const selectHighlightedItem = React.useCallback(() => {
|
|
145
163
|
if (markedIndex > -1 && markedIndex < items.length) {
|
|
146
164
|
selectItem(markedIndex);
|
|
@@ -329,7 +347,10 @@ const MenuContainer = React.forwardRef((props, ref) => {
|
|
|
329
347
|
onAnchorPosChanged(newPlacement);
|
|
330
348
|
}
|
|
331
349
|
}, [onAnchorPosChanged]);
|
|
332
|
-
const
|
|
350
|
+
const SeparatorComponent = overridesMap.Separator;
|
|
351
|
+
const renderedChildren = React.useMemo(() => items.map((item, index) =>
|
|
352
|
+
// the separator stays among the children: the index of the child is the index of the item
|
|
353
|
+
!isOption(index) ? (React.createElement(SeparatorComponent, { key: index })) : (children({ item, index }, {
|
|
333
354
|
key: index,
|
|
334
355
|
onMouseEnter: itemMouseEnterHandler(index),
|
|
335
356
|
// mouseenter is skipped during the keyboard navigation, so the item under a moving
|
|
@@ -339,8 +360,10 @@ const MenuContainer = React.forwardRef((props, ref) => {
|
|
|
339
360
|
onClick: itemClickHandler(index),
|
|
340
361
|
selected: selectedIndexes.includes(index),
|
|
341
362
|
hovered: markedIndex === index,
|
|
342
|
-
})), [
|
|
363
|
+
}))), [
|
|
343
364
|
items,
|
|
365
|
+
isOption,
|
|
366
|
+
SeparatorComponent,
|
|
344
367
|
children,
|
|
345
368
|
selectedIndexes,
|
|
346
369
|
markedIndex,
|
|
@@ -359,7 +382,7 @@ const MenuContainer = React.forwardRef((props, ref) => {
|
|
|
359
382
|
const ListComponent = overridesMap.List;
|
|
360
383
|
return (React.createElement(ClickOutside, { onOutsideClick: onRequestClose, mouseEvent: isOpen && closeOutsideClick ? 'onMouseDown' : false, ignoreElements: closeOnAnchorClick ? undefined : [anchorElement] },
|
|
361
384
|
React.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 },
|
|
362
|
-
React.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))));
|
|
385
|
+
React.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))));
|
|
363
386
|
});
|
|
364
387
|
MenuContainer.displayName = 'MenuContainer';
|
|
365
388
|
export default MenuContainer;
|
package/esm/Menu/MenuList.js
CHANGED
|
@@ -71,11 +71,11 @@ const StyledMenuList = styled.div `
|
|
|
71
71
|
box-sizing: border-box;
|
|
72
72
|
overflow-y: auto;
|
|
73
73
|
padding: 0.4em;
|
|
74
|
-
max-height: 18em;
|
|
74
|
+
max-height: ${({ $maxHeight = '18em' }) => typeof $maxHeight === 'number' ? `${$maxHeight}px` : $maxHeight};
|
|
75
75
|
`;
|
|
76
76
|
export const MenuList = React.forwardRef((props, ref) => {
|
|
77
|
-
const { isOpen, anchorPos, maxWidth, children } = props, nativeProps = __rest(props, ["isOpen", "anchorPos", "maxWidth", "children"]);
|
|
78
|
-
return (React.createElement(StyledMenuList, Object.assign({ tabIndex: -1, "$isOpen": isOpen, "$maxWidth": maxWidth, "$anchorPos": anchorPos }, nativeProps, { ref: ref }), children));
|
|
77
|
+
const { isOpen, anchorPos, maxWidth, maxHeight, children } = props, nativeProps = __rest(props, ["isOpen", "anchorPos", "maxWidth", "maxHeight", "children"]);
|
|
78
|
+
return (React.createElement(StyledMenuList, Object.assign({ tabIndex: -1, "$isOpen": isOpen, "$maxWidth": maxWidth, "$maxHeight": maxHeight, "$anchorPos": anchorPos }, nativeProps, { ref: ref }), children));
|
|
79
79
|
});
|
|
80
80
|
MenuList.displayName = 'MenuList';
|
|
81
81
|
export default MenuList;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import styled from '@emotion/styled';
|
|
3
|
+
import { themePadding } from '../ThemeProvider/tokens';
|
|
4
|
+
/**
|
|
5
|
+
* The line between the groups of the items. The space around it separates the groups:
|
|
6
|
+
* a line flush with the items is not seen
|
|
7
|
+
*/
|
|
8
|
+
const StyledMenuSeparator = styled.div `
|
|
9
|
+
flex: none;
|
|
10
|
+
height: 1px;
|
|
11
|
+
margin: ${({ theme }) => {
|
|
12
|
+
const { y, x } = themePadding(theme, 'item');
|
|
13
|
+
return `${y} ${x}`;
|
|
14
|
+
}};
|
|
15
|
+
background-color: ${({ theme }) => theme.color.textPrimary.alpha(0.12).toString()};
|
|
16
|
+
`;
|
|
17
|
+
/**
|
|
18
|
+
* The separator of the menu: not an option — it is not highlighted, selected or announced
|
|
19
|
+
* (a listbox may contain the options only, so it is hidden from the screen readers)
|
|
20
|
+
*/
|
|
21
|
+
export const MenuSeparator = React.forwardRef((props, ref) => (React.createElement(StyledMenuSeparator, Object.assign({ role: "presentation", "aria-hidden": true }, props, { ref: ref }))));
|
|
22
|
+
MenuSeparator.displayName = 'MenuSeparator';
|
|
23
|
+
export default MenuSeparator;
|
package/esm/Menu/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import MenuContainer from './MenuContainer';
|
|
|
4
4
|
export * from './MenuContainer';
|
|
5
5
|
export * from './MenuItem';
|
|
6
6
|
export * from './MenuList';
|
|
7
|
+
export * from './MenuSeparator';
|
|
7
8
|
const Menu = React.forwardRef((props, ref) => (React.createElement(MenuContextProvider, null,
|
|
8
9
|
React.createElement(MenuContainer, Object.assign({}, props, { ref: ref })))));
|
|
9
10
|
Menu.displayName = 'Menu';
|
|
@@ -20,7 +20,8 @@ const StyledModalInner = styled.div `
|
|
|
20
20
|
pointer-events: all;
|
|
21
21
|
position: relative;
|
|
22
22
|
background-color: ${({ theme }) => theme.color.surface.toString()};
|
|
23
|
-
|
|
23
|
+
/* the themes made before the modal level keep the popup shadow for the windows */
|
|
24
|
+
box-shadow: ${({ theme }) => elevation(theme, 'modal', theme.elevation.popup)};
|
|
24
25
|
border-radius: ${({ theme }) => theme.shape.radiusFactor * 2}em;
|
|
25
26
|
margin-top: 2em;
|
|
26
27
|
transition:
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import styled from '@emotion/styled';
|
|
14
|
+
import { css } from '@emotion/react';
|
|
15
|
+
import { StepIndexContext, StepperContext } from './context';
|
|
16
|
+
const Container = styled.div `
|
|
17
|
+
position: relative;
|
|
18
|
+
display: flex;
|
|
19
|
+
${({ $orientation, $alternativeLabel, $last }) => $orientation === 'vertical'
|
|
20
|
+
? css `
|
|
21
|
+
flex-direction: column;
|
|
22
|
+
`
|
|
23
|
+
: $alternativeLabel
|
|
24
|
+
? css `
|
|
25
|
+
flex: 1 1 0;
|
|
26
|
+
flex-direction: column;
|
|
27
|
+
align-items: center;
|
|
28
|
+
text-align: center;
|
|
29
|
+
`
|
|
30
|
+
: css `
|
|
31
|
+
flex: ${$last ? '0 0 auto' : '1 1 auto'};
|
|
32
|
+
align-items: center;
|
|
33
|
+
`}
|
|
34
|
+
`;
|
|
35
|
+
const Head = styled.div `
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: ${({ $alternativeLabel }) => ($alternativeLabel ? 'center' : 'flex-start')};
|
|
38
|
+
flex-direction: ${({ $alternativeLabel }) => ($alternativeLabel ? 'column' : 'row')};
|
|
39
|
+
gap: ${({ $alternativeLabel }) => ($alternativeLabel ? '0.35em' : '0.5em')};
|
|
40
|
+
flex-shrink: 0;
|
|
41
|
+
`;
|
|
42
|
+
/**
|
|
43
|
+
* The circle of the step: the check of the completed one, the number of the others
|
|
44
|
+
*/
|
|
45
|
+
const Icon = styled.span `
|
|
46
|
+
position: relative;
|
|
47
|
+
z-index: 1;
|
|
48
|
+
display: inline-flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: center;
|
|
51
|
+
flex-shrink: 0;
|
|
52
|
+
box-sizing: border-box;
|
|
53
|
+
width: 1.6em;
|
|
54
|
+
height: 1.6em;
|
|
55
|
+
border-radius: 50%;
|
|
56
|
+
font-size: 1em;
|
|
57
|
+
font-weight: 600;
|
|
58
|
+
${({ theme, $state, $color, $contrastColor, $status }) => {
|
|
59
|
+
// the attention (warning, error) colors the step that is not completed
|
|
60
|
+
const main = $status ? theme.color[$status] : $color;
|
|
61
|
+
switch ($state) {
|
|
62
|
+
// done: the pale plate of the color and the check of the color
|
|
63
|
+
case 'completed':
|
|
64
|
+
return css `
|
|
65
|
+
background-color: ${$color.alpha(0.15).toString()};
|
|
66
|
+
color: ${$color.toString()};
|
|
67
|
+
`;
|
|
68
|
+
// now: the contrast plate of the color, the border and the number of the color
|
|
69
|
+
case 'active':
|
|
70
|
+
return css `
|
|
71
|
+
border: 2px solid ${main.toString()};
|
|
72
|
+
background-color: ${($status ? theme.color.surface : $contrastColor).toString()};
|
|
73
|
+
color: ${main.toString()};
|
|
74
|
+
`;
|
|
75
|
+
// ahead: pale grey
|
|
76
|
+
default:
|
|
77
|
+
return $status
|
|
78
|
+
? css `
|
|
79
|
+
border: 1.5px solid ${main.toString()};
|
|
80
|
+
background-color: ${theme.color.surface.toString()};
|
|
81
|
+
color: ${main.darken(20).toString()};
|
|
82
|
+
`
|
|
83
|
+
: css `
|
|
84
|
+
background-color: ${theme.color.textPrimary.alpha(0.06).toString()};
|
|
85
|
+
color: ${theme.color.textSecondary.alpha(0.7).toString()};
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
}}
|
|
89
|
+
& > span {
|
|
90
|
+
font-size: 0.75em;
|
|
91
|
+
}
|
|
92
|
+
& > svg {
|
|
93
|
+
width: 0.85em;
|
|
94
|
+
height: 0.85em;
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
const Label = styled.span `
|
|
98
|
+
display: flex;
|
|
99
|
+
flex-direction: column;
|
|
100
|
+
padding-top: 0.15em;
|
|
101
|
+
line-height: 1.3;
|
|
102
|
+
font-size: 0.929em;
|
|
103
|
+
font-weight: ${({ $state }) => ($state === 'active' ? 600 : 400)};
|
|
104
|
+
color: ${({ theme, $state }) => ($state === 'pending' ? theme.color.textSecondary : theme.color.textPrimary).toString()};
|
|
105
|
+
`;
|
|
106
|
+
const Caption = styled.span `
|
|
107
|
+
font-size: 0.857em;
|
|
108
|
+
font-weight: 400;
|
|
109
|
+
color: ${({ theme }) => theme.color.textSecondary.toString()};
|
|
110
|
+
`;
|
|
111
|
+
/**
|
|
112
|
+
* The line to the next step: accented after the completed step
|
|
113
|
+
*/
|
|
114
|
+
const Connector = styled.span `
|
|
115
|
+
background-color: ${({ theme, $done, $color }) => ($done ? $color : theme.color.textPrimary.alpha(0.15)).toString()};
|
|
116
|
+
${({ $orientation, $alternativeLabel }) => $orientation === 'vertical'
|
|
117
|
+
? css `
|
|
118
|
+
width: 1.5px;
|
|
119
|
+
min-height: 0.75em;
|
|
120
|
+
margin: 0.25em 0 0.25em calc(0.8em - 0.75px);
|
|
121
|
+
flex: 1 1 auto;
|
|
122
|
+
`
|
|
123
|
+
: $alternativeLabel
|
|
124
|
+
? css `
|
|
125
|
+
position: absolute;
|
|
126
|
+
top: calc(0.8em - 0.75px);
|
|
127
|
+
left: calc(50% + 1.1em);
|
|
128
|
+
right: calc(-50% + 1.1em);
|
|
129
|
+
height: 1.5px;
|
|
130
|
+
`
|
|
131
|
+
: css `
|
|
132
|
+
flex: 1 1 auto;
|
|
133
|
+
min-width: 1em;
|
|
134
|
+
height: 1.5px;
|
|
135
|
+
margin: 0 0.5em;
|
|
136
|
+
`}
|
|
137
|
+
`;
|
|
138
|
+
const Check = () => (React.createElement("svg", { viewBox: "0 0 24 24", "aria-hidden": true },
|
|
139
|
+
React.createElement("path", { d: "M5 12.5l4.5 4.5L19 7.5", fill: "none", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round", strokeLinejoin: "round" })));
|
|
140
|
+
/**
|
|
141
|
+
* One step of the `<Stepper>`
|
|
142
|
+
*/
|
|
143
|
+
const Step = (props, ref) => {
|
|
144
|
+
const { children, caption, completed, status, icon } = props, nativeProps = __rest(props, ["children", "caption", "completed", "status", "icon"]);
|
|
145
|
+
const stepper = React.useContext(StepperContext);
|
|
146
|
+
const { index, last } = React.useContext(StepIndexContext);
|
|
147
|
+
if (!stepper) {
|
|
148
|
+
throw new Error('<Step> should be placed inside <Stepper>');
|
|
149
|
+
}
|
|
150
|
+
const { activeStep, orientation, alternativeLabel, color, contrastColor } = stepper;
|
|
151
|
+
const state = completed || (typeof completed === 'undefined' && index < activeStep)
|
|
152
|
+
? 'completed'
|
|
153
|
+
: index === activeStep
|
|
154
|
+
? 'active'
|
|
155
|
+
: 'pending';
|
|
156
|
+
return (React.createElement(Container, Object.assign({ role: "listitem", "aria-current": state === 'active' ? 'step' : undefined }, nativeProps, { "$orientation": orientation, "$alternativeLabel": alternativeLabel, "$last": last, ref: ref }),
|
|
157
|
+
React.createElement(Head, { "$alternativeLabel": alternativeLabel },
|
|
158
|
+
React.createElement(Icon, { "$state": state, "$color": color, "$contrastColor": contrastColor, "$status": status !== null && status !== void 0 ? status : null }, state === 'completed' ? React.createElement(Check, null) : (icon !== null && icon !== void 0 ? icon : React.createElement("span", null, index + 1))),
|
|
159
|
+
React.createElement(Label, { "$state": state },
|
|
160
|
+
children,
|
|
161
|
+
caption && React.createElement(Caption, null, caption))),
|
|
162
|
+
!last && (React.createElement(Connector, { "$orientation": orientation, "$alternativeLabel": alternativeLabel, "$done": state === 'completed', "$color": color }))));
|
|
163
|
+
};
|
|
164
|
+
export default React.forwardRef(Step);
|