@jetbrains/ring-ui 4.0.40 → 4.0.44
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/components/avatar/avatar.css +1 -0
- package/components/avatar/avatar.examples.js +3 -2
- package/components/avatar/avatar.js +26 -4
- package/components/avatar/fallback-avatar.js +136 -0
- package/components/dropdown/dropdown.examples.js +0 -1
- package/components/dropdown-menu/dropdown-menu.examples.js +47 -0
- package/components/dropdown-menu/dropdown-menu.js +110 -0
- package/components/dropdown-menu/dropdown-menu.test.js +76 -0
- package/components/header/header.examples.js +7 -8
- package/components/header/profile.js +10 -11
- package/components/pager/pager.js +5 -3
- package/package.json +10 -10
|
@@ -21,7 +21,8 @@ export const avatar = () => {
|
|
|
21
21
|
{Object.keys(Size).map(size => (
|
|
22
22
|
<div className="avatar-demo" key={size}>
|
|
23
23
|
<Avatar size={Size[size]} url={avatarDataUri}/>
|
|
24
|
-
<Avatar size={Size[size]}
|
|
24
|
+
<Avatar size={Size[size]} username="Jet Brains"/>
|
|
25
|
+
<Avatar size={Size[size]} username="Jet Brains" round/>
|
|
25
26
|
<Avatar size={Size[size]}/>
|
|
26
27
|
</div>
|
|
27
28
|
))}
|
|
@@ -38,7 +39,7 @@ avatar.parameters = {
|
|
|
38
39
|
.avatar-demo {
|
|
39
40
|
display: flex;
|
|
40
41
|
justify-content: space-between;
|
|
41
|
-
width:
|
|
42
|
+
width: 240px;
|
|
42
43
|
margin-bottom: 16px;
|
|
43
44
|
}
|
|
44
45
|
</style>`
|
|
@@ -6,6 +6,7 @@ import {encodeURL, isDataURI, parseQueryString} from '../global/url';
|
|
|
6
6
|
import {getPixelRatio} from '../global/dom';
|
|
7
7
|
|
|
8
8
|
import styles from './avatar.css';
|
|
9
|
+
import FallbackAvatar from './fallback-avatar';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* @name Avatar
|
|
@@ -30,7 +31,8 @@ export default class Avatar extends PureComponent {
|
|
|
30
31
|
url: PropTypes.string,
|
|
31
32
|
round: PropTypes.bool,
|
|
32
33
|
subavatar: PropTypes.string,
|
|
33
|
-
subavatarSize: PropTypes.number
|
|
34
|
+
subavatarSize: PropTypes.number,
|
|
35
|
+
username: PropTypes.string
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
static defaultProps = {
|
|
@@ -53,7 +55,17 @@ export default class Avatar extends PureComponent {
|
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
render() {
|
|
56
|
-
const {
|
|
58
|
+
const {
|
|
59
|
+
size,
|
|
60
|
+
url,
|
|
61
|
+
dpr,
|
|
62
|
+
style,
|
|
63
|
+
round,
|
|
64
|
+
subavatar,
|
|
65
|
+
subavatarSize,
|
|
66
|
+
username,
|
|
67
|
+
...restProps
|
|
68
|
+
} = this.props;
|
|
57
69
|
const sizeString = `${size}px`;
|
|
58
70
|
const subavatarSizeString = `${subavatarSize}px`;
|
|
59
71
|
const borderRadius = size <= Size.Size18 ? 'var(--ring-border-radius-small)' : 'var(--ring-border-radius)';
|
|
@@ -76,9 +88,19 @@ export default class Avatar extends PureComponent {
|
|
|
76
88
|
<span
|
|
77
89
|
{...restProps}
|
|
78
90
|
data-test="avatar"
|
|
79
|
-
className={
|
|
91
|
+
className={
|
|
92
|
+
classNames(styles.avatar, this.props.className, {[styles.empty]: username == null})
|
|
93
|
+
}
|
|
80
94
|
style={styleObj}
|
|
81
|
-
|
|
95
|
+
>{
|
|
96
|
+
username != null && (
|
|
97
|
+
<FallbackAvatar
|
|
98
|
+
size={size}
|
|
99
|
+
round={round}
|
|
100
|
+
username={username}
|
|
101
|
+
/>
|
|
102
|
+
)
|
|
103
|
+
}</span>
|
|
82
104
|
);
|
|
83
105
|
}
|
|
84
106
|
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import React, {useMemo} from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import getUID from '../global/get-uid';
|
|
5
|
+
|
|
6
|
+
const colorPairs = [
|
|
7
|
+
['#60A800', '#D5CA00'],
|
|
8
|
+
['#21D370', '#03E9E1'],
|
|
9
|
+
['#3BA1FF', '#36E97D'],
|
|
10
|
+
['#00C243', '#00FFFF'],
|
|
11
|
+
['#4BE098', '#627FFF'],
|
|
12
|
+
['#168BFA', '#26F7C7'],
|
|
13
|
+
['#9D4CFF', '#39D3C3'],
|
|
14
|
+
['#0A81F6', '#0ACFF6'],
|
|
15
|
+
['#765AF8', '#5A91F8'],
|
|
16
|
+
['#9E54FF', '#0ACFF6'],
|
|
17
|
+
['#B345F1', '#669DFF'],
|
|
18
|
+
['#765AF8', '#C059EE'],
|
|
19
|
+
['#9039D0', '#C239D0'],
|
|
20
|
+
['#9F2AFF', '#FD56FD'],
|
|
21
|
+
['#AB3AF2', '#E40568'],
|
|
22
|
+
['#9F2AFF', '#E9A80B'],
|
|
23
|
+
['#D50F6B', '#E73AE8'],
|
|
24
|
+
['#ED5502', '#E73AE8'],
|
|
25
|
+
['#ED358C', '#DBED18'],
|
|
26
|
+
['#ED358C', '#F9902E'],
|
|
27
|
+
['#FF7500', '#FFCA00']
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const Sizes = {
|
|
31
|
+
18: {
|
|
32
|
+
radius: 2,
|
|
33
|
+
text: {x: 9, y: 13},
|
|
34
|
+
fontSize: '11px',
|
|
35
|
+
textAnchor: 'middle'
|
|
36
|
+
},
|
|
37
|
+
24: {
|
|
38
|
+
radius: 3,
|
|
39
|
+
text: {x: 2, y: 13},
|
|
40
|
+
fontSize: '11px',
|
|
41
|
+
underscore: {x: 3, y: 17}
|
|
42
|
+
},
|
|
43
|
+
32: {
|
|
44
|
+
radius: 3,
|
|
45
|
+
text: {x: 3, y: 17},
|
|
46
|
+
fontSize: '13px',
|
|
47
|
+
letterSpacing: 1,
|
|
48
|
+
underscore: {x: 4, y: 22}
|
|
49
|
+
},
|
|
50
|
+
40: {
|
|
51
|
+
radius: 3,
|
|
52
|
+
text: {x: 5, y: 19},
|
|
53
|
+
fontSize: '15px',
|
|
54
|
+
letterSpacing: 1,
|
|
55
|
+
underscore: {x: 6, y: 28}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const sizeKeys = Object.keys(Sizes).map(Number);
|
|
60
|
+
|
|
61
|
+
function extractLetters(name) {
|
|
62
|
+
const names = name.split(/[\s._]+/).filter(Boolean);
|
|
63
|
+
if (names.length >= 2) {
|
|
64
|
+
return names[0][0].toUpperCase() + names[1][0].toUpperCase();
|
|
65
|
+
} else if (names.length === 1) {
|
|
66
|
+
if (names[0].length >= 2) {
|
|
67
|
+
return names[0].slice(0, 2).toUpperCase();
|
|
68
|
+
} else {
|
|
69
|
+
return `${names[0][0].toUpperCase()}X`;
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
return 'XX';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// https://gist.github.com/hyamamoto/fd435505d29ebfa3d9716fd2be8d42f0#gistcomment-2775538
|
|
77
|
+
const BASE = 32;
|
|
78
|
+
function hashCode(s) {
|
|
79
|
+
let h = 0;
|
|
80
|
+
for (let i = 0; i < s.length; i++) {
|
|
81
|
+
h = Math.imul(BASE - 1, h) + s.charCodeAt(i) | 0;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return h;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export default function FallbackAvatar({username, size, round}) {
|
|
88
|
+
const hash = Math.abs(hashCode(username.toLowerCase()));
|
|
89
|
+
const [fromColor, toColor] = colorPairs[hash % colorPairs.length];
|
|
90
|
+
const possibleSizeKeys = sizeKeys.filter(key => key >= size);
|
|
91
|
+
const sizeKey = possibleSizeKeys.length > 0
|
|
92
|
+
? Math.min(...possibleSizeKeys)
|
|
93
|
+
: Math.max(...sizeKeys);
|
|
94
|
+
const sizes = Sizes[sizeKey];
|
|
95
|
+
const radius = round ? '50%' : sizes.radius;
|
|
96
|
+
const gradientId = useMemo(() => getUID('gradient-'), []);
|
|
97
|
+
return (
|
|
98
|
+
<svg viewBox={`0 0 ${sizeKey} ${sizeKey}`} xmlns="http://www.w3.org/2000/svg">
|
|
99
|
+
<defs>
|
|
100
|
+
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
|
101
|
+
<stop stopColor={fromColor} offset="0"/>
|
|
102
|
+
<stop stopColor={toColor} offset="1"/>
|
|
103
|
+
</linearGradient>
|
|
104
|
+
</defs>
|
|
105
|
+
<g>
|
|
106
|
+
<rect
|
|
107
|
+
fill={`url(#${gradientId})`}
|
|
108
|
+
x="0"
|
|
109
|
+
y="0"
|
|
110
|
+
width={sizeKey}
|
|
111
|
+
height={sizeKey}
|
|
112
|
+
rx={radius}
|
|
113
|
+
ry={radius}
|
|
114
|
+
/>
|
|
115
|
+
<text
|
|
116
|
+
x={sizes.text.x}
|
|
117
|
+
y={sizes.text.y}
|
|
118
|
+
fontFamily="Arial, Helvetica, sans-serif"
|
|
119
|
+
fontSize={sizes.fontSize}
|
|
120
|
+
letterSpacing={sizes.letterSpacing}
|
|
121
|
+
fill="#FFFFFF"
|
|
122
|
+
textAnchor={sizes.textAnchor}
|
|
123
|
+
>
|
|
124
|
+
<tspan>{extractLetters(username)}</tspan>
|
|
125
|
+
{sizes.underscore && <tspan x={sizes.underscore.x} y={sizes.underscore.y}>{'_'}</tspan>}
|
|
126
|
+
</text>
|
|
127
|
+
</g>
|
|
128
|
+
</svg>
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
FallbackAvatar.propTypes = {
|
|
133
|
+
username: PropTypes.string.isRequired,
|
|
134
|
+
size: PropTypes.number.isRequired,
|
|
135
|
+
round: PropTypes.bool
|
|
136
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import reactDecorator from '../../.storybook/react-decorator';
|
|
4
|
+
|
|
5
|
+
import DropdownMenu from '@jetbrains/ring-ui/components/dropdown-menu/dropdown-menu';
|
|
6
|
+
|
|
7
|
+
export default {
|
|
8
|
+
title: 'Components/DropdownMenu',
|
|
9
|
+
decorators: [reactDecorator()],
|
|
10
|
+
|
|
11
|
+
parameters: {
|
|
12
|
+
notes: 'Displays a menu in a dropdown.',
|
|
13
|
+
hermione: {
|
|
14
|
+
actions: [
|
|
15
|
+
{type: 'click', selector: '[data-test~=ring-dropdown]'},
|
|
16
|
+
{
|
|
17
|
+
type: 'capture',
|
|
18
|
+
name: 'dropdown',
|
|
19
|
+
selector: ['[data-test~=ring-dropdown]', '[data-test~=ring-popup]']
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
a11y: {element: '*[data-test~=ring-dropdown]'}
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const basic = () => {
|
|
28
|
+
const data = [
|
|
29
|
+
{label: 'Item'},
|
|
30
|
+
{label: 'Link to jetbrains.com', href: 'http://www.jetbrains.com'},
|
|
31
|
+
{rgItemType: DropdownMenu.ListProps.Type.SEPARATOR},
|
|
32
|
+
{rgItemType: DropdownMenu.ListProps.Type.LINK, label: 'Link Item'},
|
|
33
|
+
{
|
|
34
|
+
rgItemType: DropdownMenu.ListProps.Type.LINK,
|
|
35
|
+
label: 'Link Item With Additional Class',
|
|
36
|
+
className: 'test'
|
|
37
|
+
},
|
|
38
|
+
{rgItemType: DropdownMenu.ListProps.Type.SEPARATOR, description: 'Separator With Description'},
|
|
39
|
+
{rgItemType: DropdownMenu.ListProps.Type.TITLE, label: 'Title'},
|
|
40
|
+
{rgItemType: DropdownMenu.ListProps.Type.ITEM, label: '1 Element in group'},
|
|
41
|
+
{rgItemType: DropdownMenu.ListProps.Type.ITEM, label: '2 Element in group'}
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
return <DropdownMenu data={data} anchor={'Click me!'}/>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
basic.storyName = 'DropdownMenu';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React, {useMemo, cloneElement} from 'react';
|
|
2
|
+
import PropTypes from 'prop-types';
|
|
3
|
+
|
|
4
|
+
import List, {ActiveItemContext} from '../list/list';
|
|
5
|
+
import Dropdown from '../dropdown/dropdown';
|
|
6
|
+
import PopupMenu from '../popup-menu/popup-menu';
|
|
7
|
+
import getUID from '../global/get-uid';
|
|
8
|
+
import Anchor from '../dropdown/anchor';
|
|
9
|
+
|
|
10
|
+
const {children, ...dropdownPropTypes} = Dropdown.propTypes || {};
|
|
11
|
+
const {
|
|
12
|
+
id: idPropType,
|
|
13
|
+
data: dataPropType,
|
|
14
|
+
ariaLabel: ariaLabelPropType,
|
|
15
|
+
onSelect: onSelectPropType
|
|
16
|
+
} = PopupMenu.propTypes || {};
|
|
17
|
+
|
|
18
|
+
const defaultAriaLabel = 'Dropdown menu';
|
|
19
|
+
|
|
20
|
+
function DropdownAnchorWrapper({anchor, pinned, active, activeListItemId, listId, ...restProps}) {
|
|
21
|
+
const anchorAriaProps = useMemo(() => ({
|
|
22
|
+
...(listId ? {'aria-haspopup': 'true'} : {}),
|
|
23
|
+
...(activeListItemId ? {'aria-activedescendant': activeListItemId, 'aria-owns': listId} : {}),
|
|
24
|
+
...(active ? {'aria-expanded': 'true'} : {})
|
|
25
|
+
}), [active, activeListItemId, listId]);
|
|
26
|
+
|
|
27
|
+
const anchorProps = useMemo(
|
|
28
|
+
() => ({active, pinned, ...restProps, ...anchorAriaProps}),
|
|
29
|
+
[pinned, active, restProps, anchorAriaProps]
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (typeof anchor === 'string') {
|
|
33
|
+
return (
|
|
34
|
+
<Anchor
|
|
35
|
+
{...anchorProps}
|
|
36
|
+
pinned={`${pinned}`}
|
|
37
|
+
>{anchor}</Anchor>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (typeof anchor === 'function') {
|
|
41
|
+
return anchor(({active, pinned, ...restProps}), anchorAriaProps);
|
|
42
|
+
}
|
|
43
|
+
if (!Array.isArray(anchor)) {
|
|
44
|
+
return cloneElement(anchor, typeof anchor.type === 'string' ? anchorAriaProps : anchorProps);
|
|
45
|
+
}
|
|
46
|
+
return (
|
|
47
|
+
<div {...anchorAriaProps}>{anchor}</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
DropdownAnchorWrapper.propTypes = {
|
|
52
|
+
anchor: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.func]).isRequired,
|
|
53
|
+
pinned: PropTypes.bool,
|
|
54
|
+
active: PropTypes.bool,
|
|
55
|
+
activeListItemId: PropTypes.string,
|
|
56
|
+
listId: PropTypes.string
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const DropdownMenu = React.forwardRef(function DropdownMenu(
|
|
60
|
+
{id, anchor, ariaLabel, data, onSelect, menuProps, ...restDropdownProps},
|
|
61
|
+
forwardedRef
|
|
62
|
+
) {
|
|
63
|
+
const listId = useMemo(() => id || getUID('dropdown-menu-list'), [id]);
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<ActiveItemContext.Provider>
|
|
67
|
+
<Dropdown
|
|
68
|
+
anchor={({pinned, active, ...restAnchorProps}) => (
|
|
69
|
+
<ActiveItemContext.ValueContext.Consumer>
|
|
70
|
+
{activeItemId => (
|
|
71
|
+
<DropdownAnchorWrapper
|
|
72
|
+
anchor={anchor}
|
|
73
|
+
pinned={pinned}
|
|
74
|
+
active={active}
|
|
75
|
+
activeListItemId={activeItemId}
|
|
76
|
+
listId={listId}
|
|
77
|
+
{...restAnchorProps}
|
|
78
|
+
/>
|
|
79
|
+
)}
|
|
80
|
+
</ActiveItemContext.ValueContext.Consumer>
|
|
81
|
+
)}
|
|
82
|
+
{...restDropdownProps}
|
|
83
|
+
>
|
|
84
|
+
<PopupMenu
|
|
85
|
+
ref={forwardedRef}
|
|
86
|
+
id={listId}
|
|
87
|
+
ariaLabel={ariaLabel || defaultAriaLabel}
|
|
88
|
+
closeOnSelect
|
|
89
|
+
activateFirstItem
|
|
90
|
+
data={data}
|
|
91
|
+
onSelect={onSelect}
|
|
92
|
+
{...menuProps}
|
|
93
|
+
/>
|
|
94
|
+
</Dropdown>
|
|
95
|
+
</ActiveItemContext.Provider>
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
DropdownMenu.propTypes = {
|
|
100
|
+
id: idPropType,
|
|
101
|
+
data: dataPropType,
|
|
102
|
+
ariaLabel: ariaLabelPropType,
|
|
103
|
+
onSelect: onSelectPropType,
|
|
104
|
+
menuProps: PropTypes.object,
|
|
105
|
+
...dropdownPropTypes
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
DropdownMenu.ListProps = List.ListProps;
|
|
109
|
+
|
|
110
|
+
export default DropdownMenu;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {shallow, mount} from 'enzyme';
|
|
3
|
+
|
|
4
|
+
import PopupMenu from '../popup-menu/popup-menu';
|
|
5
|
+
import Anchor from '../dropdown/anchor';
|
|
6
|
+
|
|
7
|
+
import DropdownMenu from './dropdown-menu';
|
|
8
|
+
|
|
9
|
+
const waitForCondition = (condition, rejectMessage) => new Promise((resolve, reject) => {
|
|
10
|
+
const interval = 10;
|
|
11
|
+
const maxWaitingTime = 2000;
|
|
12
|
+
let remainingTime = maxWaitingTime;
|
|
13
|
+
|
|
14
|
+
const intervalId = setInterval(() => {
|
|
15
|
+
if (condition()) {
|
|
16
|
+
clearInterval(intervalId);
|
|
17
|
+
resolve();
|
|
18
|
+
} else if (remainingTime < 0) {
|
|
19
|
+
clearInterval(intervalId);
|
|
20
|
+
reject(new Error(rejectMessage));
|
|
21
|
+
} else {
|
|
22
|
+
remainingTime -= interval;
|
|
23
|
+
}
|
|
24
|
+
}, interval);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('Dropdown Menu', () => {
|
|
28
|
+
const shallowDropdownMenu = props => shallow(<DropdownMenu id="test-list-id" {...props}/>);
|
|
29
|
+
const mountDropdownMenu = props => mount(<DropdownMenu id="test-list-id" {...props}/>);
|
|
30
|
+
|
|
31
|
+
const mountAndWaitForMenuContent = async props => {
|
|
32
|
+
const wrapper = mountDropdownMenu(props);
|
|
33
|
+
|
|
34
|
+
wrapper.find('button').getDOMNode().click();
|
|
35
|
+
await waitForCondition(
|
|
36
|
+
() => !!wrapper.find(PopupMenu).length,
|
|
37
|
+
'List was not rendered in a dropdown menu'
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return wrapper;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
it('should create component', () => {
|
|
44
|
+
shallowDropdownMenu({anchor: 'Anchor text'}).should.exist;
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should open List', async () => {
|
|
48
|
+
const wrapper = await mountAndWaitForMenuContent({anchor: 'Anchor text'});
|
|
49
|
+
|
|
50
|
+
const list = wrapper.find(PopupMenu).instance().list;
|
|
51
|
+
list.should.exist;
|
|
52
|
+
|
|
53
|
+
//We need it to maintain compatibility between Dropdown Menu and List
|
|
54
|
+
list.props.data.length.should.equal(0);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should pass params to List', async () => {
|
|
58
|
+
const wrapper = await mountAndWaitForMenuContent({
|
|
59
|
+
anchor: 'Anchor text',
|
|
60
|
+
data: [{key: 'key1'}]
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
shallow(wrapper.find(PopupMenu).instance().list.renderItem({index: 1})).should.exist;
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should add accessibility attributes to anchor', async () => {
|
|
67
|
+
const wrapper = await mountAndWaitForMenuContent({
|
|
68
|
+
anchor: 'Anchor text',
|
|
69
|
+
data: [{key: 'key1'}, {key: 'key2'}]
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const anchorProps = wrapper.update().find(Anchor).props();
|
|
73
|
+
anchorProps['aria-owns'].should.equal('test-list-id');
|
|
74
|
+
anchorProps['aria-activedescendant'].should.contain(':key1');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -13,8 +13,7 @@ import hubConfig from '../../.storybook/hub-config';
|
|
|
13
13
|
import Link from '@jetbrains/ring-ui/components/link/link';
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
import
|
|
17
|
-
import Dropdown from '@jetbrains/ring-ui/components/dropdown/dropdown';
|
|
16
|
+
import DropdownMenu from '@jetbrains/ring-ui/components/dropdown-menu/dropdown-menu';
|
|
18
17
|
import showAuthDialog from '@jetbrains/ring-ui/components/auth-dialog-service/auth-dialog-service';
|
|
19
18
|
|
|
20
19
|
import Theme from '@jetbrains/ring-ui/components/global/theme';
|
|
@@ -77,13 +76,13 @@ export const header = ({isCompact, ...args}) => {
|
|
|
77
76
|
<TrayIcon title="Help" icon={helpIcon}/>
|
|
78
77
|
<TrayIcon title="What's new" icon={giftIcon}/>
|
|
79
78
|
<TrayIcon title="Search" icon={searchIcon}/>
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
<DropdownMenu
|
|
80
|
+
data={[{label: 'Test'}, {label: 'Test2'}]}
|
|
81
|
+
anchor={({active, pinned, ...ariaProps}) => (
|
|
82
|
+
<TrayIcon title="Settings" active={active} icon={settingsIcon} {...ariaProps}/>
|
|
83
83
|
)}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
</Dropdown>
|
|
84
|
+
menuProps={{top: -12}}
|
|
85
|
+
/>
|
|
87
86
|
<SmartServices auth={auth}/>
|
|
88
87
|
<SmartProfile auth={auth} hasUpdates LinkComponent={Comp}/>
|
|
89
88
|
</Tray>
|
|
@@ -4,7 +4,7 @@ import classNames from 'classnames';
|
|
|
4
4
|
|
|
5
5
|
import Avatar, {Size} from '../avatar/avatar';
|
|
6
6
|
import Button from '../button/button';
|
|
7
|
-
import
|
|
7
|
+
import DropdownMenu from '../dropdown-menu/dropdown-menu';
|
|
8
8
|
import PopupMenu from '../popup-menu/popup-menu';
|
|
9
9
|
|
|
10
10
|
import styles from './header.css';
|
|
@@ -162,21 +162,20 @@ export default class Profile extends PureComponent {
|
|
|
162
162
|
].filter(it => !!it);
|
|
163
163
|
|
|
164
164
|
return (
|
|
165
|
-
<
|
|
165
|
+
<DropdownMenu
|
|
166
166
|
{...props}
|
|
167
167
|
title={user.name}
|
|
168
168
|
anchor={anchor}
|
|
169
|
+
data={renderPopupItems(items)}
|
|
169
170
|
data-test="ring-profile"
|
|
170
171
|
className={classNames(styles.profile, className)}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/>
|
|
179
|
-
</Dropdown>
|
|
172
|
+
menuProps={{
|
|
173
|
+
closeOnSelect,
|
|
174
|
+
left: -2,
|
|
175
|
+
top: -8,
|
|
176
|
+
sidePadding: 32
|
|
177
|
+
}}
|
|
178
|
+
/>
|
|
180
179
|
);
|
|
181
180
|
}
|
|
182
181
|
}
|
|
@@ -33,6 +33,7 @@ export default class Pager extends PureComponent {
|
|
|
33
33
|
className: PropTypes.string,
|
|
34
34
|
translations: PropTypes.object,
|
|
35
35
|
loader: PropTypes.bool,
|
|
36
|
+
loaderNavigation: PropTypes.bool,
|
|
36
37
|
hrefFunc: PropTypes.func //function which generates href for all pager's buttons based on pager state passed as a function parameter, either this function or onPageChange should be provided
|
|
37
38
|
};
|
|
38
39
|
|
|
@@ -53,6 +54,7 @@ export default class Pager extends PureComponent {
|
|
|
53
54
|
previousPage: 'Previous'
|
|
54
55
|
},
|
|
55
56
|
loader: false,
|
|
57
|
+
loaderNavigation: false,
|
|
56
58
|
onPageSizeChange: () => {},
|
|
57
59
|
onLoadPage: () => {}
|
|
58
60
|
};
|
|
@@ -109,7 +111,7 @@ export default class Pager extends PureComponent {
|
|
|
109
111
|
href={this.generateHref(page)}
|
|
110
112
|
key={key}
|
|
111
113
|
active={active}
|
|
112
|
-
disabled={this.props.loader && !active}
|
|
114
|
+
disabled={this.props.loader && !active && !this.props.loaderNavigation}
|
|
113
115
|
loader={this.props.loader && active}
|
|
114
116
|
{...this.getClickProps(this.handlePageChange(page))}
|
|
115
117
|
>
|
|
@@ -187,7 +189,7 @@ export default class Pager extends PureComponent {
|
|
|
187
189
|
|
|
188
190
|
return (
|
|
189
191
|
<div className={style.links}>
|
|
190
|
-
{prevLinkAvailable && !this.props.loader
|
|
192
|
+
{prevLinkAvailable && (!this.props.loader || this.props.loaderNavigation)
|
|
191
193
|
? (
|
|
192
194
|
<Link
|
|
193
195
|
href={prevLinkHref}
|
|
@@ -202,7 +204,7 @@ export default class Pager extends PureComponent {
|
|
|
202
204
|
)
|
|
203
205
|
}
|
|
204
206
|
|
|
205
|
-
{nextLinkAvailable && !this.props.loader
|
|
207
|
+
{nextLinkAvailable && (!this.props.loader || this.props.loaderNavigation)
|
|
206
208
|
? (
|
|
207
209
|
<Link
|
|
208
210
|
href={nextLinkHref}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jetbrains/ring-ui",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.44",
|
|
4
4
|
"description": "JetBrains UI library",
|
|
5
5
|
"author": "JetBrains",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
"@babel/cli": "^7.15.4",
|
|
69
69
|
"@babel/eslint-parser": "^7.15.4",
|
|
70
70
|
"@jetbrains/eslint-config": "^5.3.1",
|
|
71
|
-
"@jetbrains/generator-ring-ui": "^4.0.
|
|
71
|
+
"@jetbrains/generator-ring-ui": "^4.0.33",
|
|
72
72
|
"@jetbrains/stylelint-config": "^2.0.1",
|
|
73
73
|
"@primer/octicons": "^15.1.0",
|
|
74
74
|
"@storybook/addon-a11y": "6.3.8",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"@storybook/manager-webpack5": "^6.3.8",
|
|
86
86
|
"@storybook/source-loader": "6.3.8",
|
|
87
87
|
"@storybook/theming": "6.3.8",
|
|
88
|
-
"@testing-library/react": "^12.
|
|
88
|
+
"@testing-library/react": "^12.1.0",
|
|
89
89
|
"@testing-library/user-event": "^13.2.1",
|
|
90
90
|
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.3",
|
|
91
91
|
"angular": "^1.8.2",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"husky": "^7.0.2",
|
|
113
113
|
"identity-obj-proxy": "^3.0.0",
|
|
114
114
|
"imports-loader": "^3.0.0",
|
|
115
|
-
"jest": "~27.
|
|
115
|
+
"jest": "~27.2.0",
|
|
116
116
|
"jest-teamcity": "^1.10.0",
|
|
117
117
|
"karma": "^6.3.4",
|
|
118
118
|
"karma-chrome-launcher": "3.1.0",
|
|
@@ -137,9 +137,9 @@
|
|
|
137
137
|
"stylelint": "^13.13.1",
|
|
138
138
|
"svg-inline-loader": "^0.8.2",
|
|
139
139
|
"teamcity-service-messages": "^0.1.11",
|
|
140
|
-
"terser-webpack-plugin": "^5.2.
|
|
140
|
+
"terser-webpack-plugin": "^5.2.4",
|
|
141
141
|
"wallaby-webpack": "^3.9.16",
|
|
142
|
-
"webpack": "^5.52.
|
|
142
|
+
"webpack": "^5.52.1",
|
|
143
143
|
"webpack-cli": "^4.8.0",
|
|
144
144
|
"xmlappend": "^1.0.4",
|
|
145
145
|
"yo": "^4.3.0"
|
|
@@ -148,18 +148,18 @@
|
|
|
148
148
|
"core-js": ">=3.0.0",
|
|
149
149
|
"react": ">=16.8.0",
|
|
150
150
|
"react-dom": ">=16.8.0",
|
|
151
|
-
"webpack": "^5.52.
|
|
151
|
+
"webpack": "^5.52.1"
|
|
152
152
|
},
|
|
153
153
|
"dependencies": {
|
|
154
154
|
"@babel/core": "^7.15.5",
|
|
155
155
|
"@jetbrains/angular-elastic": "^2.5.1",
|
|
156
156
|
"@jetbrains/babel-preset-jetbrains": "^2.3.1",
|
|
157
157
|
"@jetbrains/icons": "^3.17.1",
|
|
158
|
-
"@jetbrains/logos": "^1.4.
|
|
158
|
+
"@jetbrains/logos": "^1.4.24",
|
|
159
159
|
"@jetbrains/postcss-require-hover": "^0.1.2",
|
|
160
160
|
"@ungap/url-search-params": "^0.2.2",
|
|
161
161
|
"babel-loader": "^8.2.2",
|
|
162
|
-
"babel-plugin-transform-define": "^2.0.
|
|
162
|
+
"babel-plugin-transform-define": "^2.0.1",
|
|
163
163
|
"browserslist": "^4.16.6",
|
|
164
164
|
"change-case": "^4.1.1",
|
|
165
165
|
"classnames": "^2.3.1",
|
|
@@ -207,5 +207,5 @@
|
|
|
207
207
|
"node": ">=7.4",
|
|
208
208
|
"npm": ">=6.0.0"
|
|
209
209
|
},
|
|
210
|
-
"gitHead": "
|
|
210
|
+
"gitHead": "00b6bd2545cdb03747a9db9517bd063a1d4033b3"
|
|
211
211
|
}
|