@jetbrains/ring-ui 4.0.39 → 4.0.43

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.
@@ -1,6 +1,7 @@
1
1
  @import "../global/variables.css";
2
2
 
3
3
  .avatar {
4
+ display: inline-block;
4
5
  object-fit: cover;
5
6
  object-position: center;
6
7
 
@@ -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]} url={avatarDataUri} round/>
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: 200px;
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 {size, url, dpr, style, round, subavatar, subavatarSize, ...restProps} = this.props;
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={classNames(styles.avatar, styles.empty, this.props.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
+ };
@@ -3,12 +3,14 @@ import chevronDownIcon from '@jetbrains/icons/chevron-down';
3
3
 
4
4
  import reactDecorator from '../../.storybook/react-decorator';
5
5
 
6
+ import {ActiveItemContext} from '../list/list';
7
+
6
8
  import Popup from '@jetbrains/ring-ui/components/popup/popup';
7
9
  import PopupMenu from '@jetbrains/ring-ui/components/popup-menu/popup-menu';
8
10
  import Button from '@jetbrains/ring-ui/components/button/button';
9
11
  import Link from '@jetbrains/ring-ui/components/link/link';
10
12
  import {Input} from '@jetbrains/ring-ui/components/input/input';
11
-
13
+ import getUID from '@jetbrains/ring-ui/components/global/get-uid';
12
14
  import Dropdown from '@jetbrains/ring-ui/components/dropdown/dropdown';
13
15
 
14
16
  export default {
@@ -45,6 +47,39 @@ export const withCustomAnchorAndPopup = () => (
45
47
  </Dropdown>
46
48
  );
47
49
 
50
+ export const withCustomAnchorAndPopupAndContentAccessibilityHandling = () => {
51
+ const listId = getUID('popup-menu-list-id');
52
+
53
+ return (
54
+ <ActiveItemContext.Provider>
55
+ <Dropdown anchor={({active}) => (
56
+ <ActiveItemContext.ValueContext.Consumer>
57
+ {activeItemId => {
58
+ const anchorAriaProps = active && activeItemId
59
+ ? {'aria-owns': listId, 'aria-activedescendant': activeItemId}
60
+ : {};
61
+ return (
62
+ <Button
63
+ {...anchorAriaProps}
64
+ delayed
65
+ >Edit</Button>
66
+ );
67
+ }}
68
+ </ActiveItemContext.ValueContext.Consumer>
69
+ )}
70
+ >
71
+ <PopupMenu
72
+ id={listId}
73
+ ariaLabel="My options menu"
74
+ closeOnSelect
75
+ activateFirstItem
76
+ data={['Cut', 'Copy', 'Paste'].map(label => ({label, key: label.toLowerCase()}))}
77
+ />
78
+ </Dropdown>
79
+ </ActiveItemContext.Provider>
80
+ );
81
+ };
82
+
48
83
  withCustomAnchorAndPopup.storyName = 'with custom anchor and popup';
49
84
 
50
85
  export const withActiveClassName = () => (
@@ -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,107 @@
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
+ } = PopupMenu.propTypes || {};
16
+
17
+ const defaultAriaLabel = 'Dropdown menu';
18
+
19
+ function DropdownAnchorWrapper({anchor, pinned, active, activeListItemId, listId, ...restProps}) {
20
+ const anchorAriaProps = useMemo(() => ({
21
+ ...(listId ? {'aria-haspopup': 'true'} : {}),
22
+ ...(activeListItemId ? {'aria-activedescendant': activeListItemId, 'aria-owns': listId} : {}),
23
+ ...(active ? {'aria-expanded': 'true'} : {})
24
+ }), [active, activeListItemId, listId]);
25
+
26
+ const anchorProps = useMemo(
27
+ () => ({active, pinned, ...restProps, ...anchorAriaProps}),
28
+ [pinned, active, restProps, anchorAriaProps]
29
+ );
30
+
31
+ if (typeof anchor === 'string') {
32
+ return (
33
+ <Anchor
34
+ {...anchorProps}
35
+ pinned={`${pinned}`}
36
+ >{anchor}</Anchor>
37
+ );
38
+ }
39
+ if (typeof anchor === 'function') {
40
+ return anchor(anchorProps);
41
+ }
42
+ if (!Array.isArray(anchor)) {
43
+ return cloneElement(anchor, typeof anchor.type === 'string' ? anchorAriaProps : anchorProps);
44
+ }
45
+ return (
46
+ <div {...anchorAriaProps}>{anchor}</div>
47
+ );
48
+ }
49
+
50
+ DropdownAnchorWrapper.propTypes = {
51
+ anchor: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.func]).isRequired,
52
+ pinned: PropTypes.bool,
53
+ active: PropTypes.bool,
54
+ activeListItemId: PropTypes.string,
55
+ listId: PropTypes.string
56
+ };
57
+
58
+ const DropdownMenu = React.forwardRef(function DropdownMenu(
59
+ {id, anchor, ariaLabel, data, menuProps, ...restDropdownProps},
60
+ forwardedRef
61
+ ) {
62
+ const listId = useMemo(() => id || getUID('dropdown-menu-list'), [id]);
63
+
64
+ return (
65
+ <ActiveItemContext.Provider>
66
+ <Dropdown
67
+ anchor={({pinned, active, ...restAnchorProps}) => (
68
+ <ActiveItemContext.ValueContext.Consumer>
69
+ {activeItemId => (
70
+ <DropdownAnchorWrapper
71
+ anchor={anchor}
72
+ pinned={pinned}
73
+ active={active}
74
+ activeListItemId={activeItemId}
75
+ listId={listId}
76
+ {...restAnchorProps}
77
+ />
78
+ )}
79
+ </ActiveItemContext.ValueContext.Consumer>
80
+ )}
81
+ {...restDropdownProps}
82
+ >
83
+ <PopupMenu
84
+ ref={forwardedRef}
85
+ id={listId}
86
+ ariaLabel={ariaLabel || defaultAriaLabel}
87
+ closeOnSelect
88
+ activateFirstItem
89
+ data={data}
90
+ {...menuProps}
91
+ />
92
+ </Dropdown>
93
+ </ActiveItemContext.Provider>
94
+ );
95
+ });
96
+
97
+ DropdownMenu.propTypes = {
98
+ id: idPropType,
99
+ data: dataPropType,
100
+ ariaLabel: ariaLabelPropType,
101
+ menuProps: PropTypes.object,
102
+ ...dropdownPropTypes
103
+ };
104
+
105
+ DropdownMenu.ListProps = List.ListProps;
106
+
107
+ 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 PopupMenu from '@jetbrains/ring-ui/components/popup-menu/popup-menu';
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
- <Dropdown
81
- anchor={({active}) => (
82
- <TrayIcon title="Settings" active={active} icon={settingsIcon}/>
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
- <PopupMenu top={-12} closeOnSelect data={[{label: 'Test'}, {label: 'Test2'}]}/>
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 Dropdown from '../dropdown/dropdown';
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
- <Dropdown
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
- <PopupMenu
173
- closeOnSelect={closeOnSelect}
174
- data={renderPopupItems(items)}
175
- left={-2}
176
- top={-8}
177
- sidePadding={32}
178
- />
179
- </Dropdown>
172
+ menuProps={{
173
+ closeOnSelect,
174
+ left: -2,
175
+ top: -8,
176
+ sidePadding: 32
177
+ }}
178
+ />
180
179
  );
181
180
  }
182
181
  }
@@ -39,7 +39,6 @@ export function linkHOC(ComposedComponent) {
39
39
  return class Link extends Component {
40
40
  static propTypes = {
41
41
  className: PropTypes.string,
42
- role: PropTypes.string,
43
42
  innerClassName: PropTypes.string,
44
43
  active: PropTypes.bool,
45
44
  inherit: PropTypes.bool,
@@ -71,7 +70,6 @@ export function linkHOC(ComposedComponent) {
71
70
  className,
72
71
  'data-test': dataTest,
73
72
  href,
74
- role,
75
73
  innerClassName, children, onPlainLeftClick, onClick,
76
74
  ...props
77
75
  } = this.props;
@@ -94,7 +92,6 @@ export function linkHOC(ComposedComponent) {
94
92
  return (
95
93
  <button
96
94
  type="button"
97
- role={role || 'button'}
98
95
  {...props}
99
96
  className={classes}
100
97
  onClick={onClick || onPlainLeftClick}
@@ -107,7 +104,6 @@ export function linkHOC(ComposedComponent) {
107
104
  <ComposedComponent
108
105
  {...props}
109
106
  href={href}
110
- role={role || 'link'}
111
107
  className={classes}
112
108
  onClick={onClick}
113
109
  onPlainLeftClick={onPlainLeftClick}
@@ -80,7 +80,6 @@ export default class List extends Component {
80
80
  static propTypes = {
81
81
  id: PropTypes.string,
82
82
  className: PropTypes.string,
83
- role: PropTypes.string,
84
83
  hint: PropTypes.node,
85
84
  hintOnSelection: PropTypes.string,
86
85
  data: PropTypes.array,
@@ -120,7 +119,6 @@ export default class List extends Component {
120
119
  shortcuts: false,
121
120
  renderOptimization: true,
122
121
  disableMoveDownOverflow: false,
123
- role: 'list',
124
122
  ariaLabel: 'List'
125
123
  };
126
124
 
@@ -492,10 +490,7 @@ export default class List extends Component {
492
490
 
493
491
  // Hack around SelectNG implementation
494
492
  const {selectedLabel, originalModel, ...cleanedProps} = item;
495
- const itemProps = Object.assign({
496
- rgItemType: DEFAULT_ITEM_TYPE,
497
- role: 'listitem'
498
- }, cleanedProps);
493
+ const itemProps = Object.assign({rgItemType: DEFAULT_ITEM_TYPE}, cleanedProps);
499
494
 
500
495
  if (itemProps.url) {
501
496
  itemProps.href = itemProps.url;
@@ -575,7 +570,11 @@ export default class List extends Component {
575
570
  )}
576
571
  </CellMeasurer>
577
572
  )
578
- : cloneElement(el, {key: itemKey});
573
+ : (
574
+ <div role="row" id={itemId} key={itemKey}>
575
+ <div role="cell">{cloneElement(el)}</div>
576
+ </div>
577
+ );
579
578
  };
580
579
 
581
580
  addItemDataTestToProp = props => {
@@ -684,6 +683,7 @@ export default class List extends Component {
684
683
  >
685
684
  <div
686
685
  aria-label={this.props.ariaLabel}
686
+ role="grid"
687
687
  style={maxHeight
688
688
  ? {maxHeight: this.getVisibleListHeight(this.props)}
689
689
  : null
@@ -729,8 +729,6 @@ export default class List extends Component {
729
729
  <div
730
730
  id={this.props.id}
731
731
  ref={this.containerRef}
732
- role={this.props.role}
733
- tabIndex={-1}
734
732
  className={classes}
735
733
  onMouseOut={this.props.onMouseOut}
736
734
  onBlur={this.props.onMouseOut}
@@ -61,7 +61,6 @@ export default class ListItem extends PureComponent {
61
61
  onMouseOver: PropTypes.func,
62
62
  onMouseDown: PropTypes.func,
63
63
  onMouseUp: PropTypes.func,
64
- role: PropTypes.string,
65
64
  'data-test': PropTypes.string
66
65
  };
67
66
 
@@ -98,7 +97,6 @@ export default class ListItem extends PureComponent {
98
97
  onMouseUp,
99
98
  rightNodes,
100
99
  leftNodes,
101
- role,
102
100
  ...restProps
103
101
  } = this.props;
104
102
 
@@ -159,7 +157,6 @@ export default class ListItem extends PureComponent {
159
157
  <button
160
158
  id={this.id}
161
159
  type="button"
162
- role={role}
163
160
  tabIndex={tabIndex}
164
161
  onClick={onClick}
165
162
  onMouseOver={onMouseOver}
@@ -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}
@@ -26,14 +26,6 @@ export default class PopupMenu extends Popup {
26
26
  closeOnSelect: false
27
27
  };
28
28
 
29
- static getDerivedStateFromProps(props) {
30
- return {
31
- data: (props.data || []).map(dataItem => ({
32
- role: 'menuitem', ...dataItem
33
- }))
34
- };
35
- }
36
-
37
29
  onSelect = (item, event) => {
38
30
  if (this.props.closeOnSelect) {
39
31
  this._onCloseAttempt(event);
@@ -47,14 +39,12 @@ export default class PopupMenu extends Popup {
47
39
 
48
40
  /** @override */
49
41
  getInternalContent() {
50
- const {className, data, ...props} = this.props;
42
+ const {className, ...props} = this.props;
51
43
 
52
44
  return (
53
45
  <div>
54
46
  <List
55
- role="menu"
56
47
  ref={this.listRef}
57
- data={this.state.data}
58
48
  {...props}
59
49
  maxHeight={this.popup && this.popup.style.maxHeight}
60
50
  shortcuts={this.shouldUseShortcuts()}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jetbrains/ring-ui",
3
- "version": "4.0.39",
3
+ "version": "4.0.43",
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.32",
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.0.0",
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.1.1",
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.3",
140
+ "terser-webpack-plugin": "^5.2.4",
141
141
  "wallaby-webpack": "^3.9.16",
142
- "webpack": "^5.52.0",
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.0"
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.23",
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.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": "d65158d2c7348b13f76ea18c4c0d6b5d656dd8d7"
210
+ "gitHead": "ef0147be4eea764b49b5cd663b3daf92a56e0618"
211
211
  }