]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16830 [892932] State of active component lacks 3 to 1 contrast ratio
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Fri, 30 Sep 2022 13:08:40 +0000 (15:08 +0200)
committerPhilippe Perrin <philippe.perrin@sonarsource.com>
Fri, 7 Oct 2022 10:13:56 +0000 (12:13 +0200)
12 files changed:
server/sonar-web/src/main/js/app/components/search/SearchResult.tsx
server/sonar-web/src/main/js/app/components/search/SearchResults.tsx
server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResult-test.tsx.snap
server/sonar-web/src/main/js/app/components/search/__tests__/__snapshots__/SearchResults-test.tsx.snap
server/sonar-web/src/main/js/app/styles/components/menu.css
server/sonar-web/src/main/js/app/theme.js
server/sonar-web/src/main/js/components/common/SelectList.tsx
server/sonar-web/src/main/js/components/common/SelectListItem.tsx
server/sonar-web/src/main/js/components/common/__tests__/SelectList-test.tsx
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/SelectList-test.tsx.snap
server/sonar-web/src/main/js/components/controls/Select.tsx
server/sonar-web/src/main/js/components/ui/popups.css

index ff444714d0198d3a5a3f661b1abd5a564ca67ca6..31a39057c5d9b3654ae12a002f081ba216328efb 100644 (file)
@@ -43,13 +43,13 @@ export default class SearchResult extends React.PureComponent<Props> {
     const to = getComponentOverviewUrl(component.key, component.qualifier);
 
     return (
-      <li
-        className={this.props.selected ? 'active' : undefined}
-        key={component.key}
-        ref={node => this.props.innerRef(component.key, node)}
-        role="option"
-        aria-selected={this.props.selected}>
-        <Link data-key={component.key} onClick={this.props.onClose} onFocus={this.doSelect} to={to}>
+      <li key={component.key} ref={node => this.props.innerRef(component.key, node)}>
+        <Link
+          className={this.props.selected ? 'hover' : undefined}
+          data-key={component.key}
+          onClick={this.props.onClose}
+          onFocus={this.doSelect}
+          to={to}>
           <div className="navbar-search-item-link little-padded-top" onMouseEnter={this.doSelect}>
             <div className="display-flex-center">
               <span className="navbar-search-item-icons little-spacer-right">
index c0ccf411c8698333bea1a051bc5b44dfe46ed810..66160c2dd138559d85dfc4ca31aaf0016ff24521 100644 (file)
@@ -45,7 +45,7 @@ export default function SearchResults(props: Props): React.ReactElement<Props> {
       const more = props.more[qualifier];
 
       renderedComponents.push(
-        <ul className="menu" key={`header-${qualifier}`} role="group">
+        <ul className="menu" key={`header-${qualifier}`}>
           <li className="menu-header" role="presentation">
             {translate('qualifiers', qualifier)}
           </li>
index ac7e9e5b4e6288e8acf4bfb4e22282454b68d3d7..0ab574771a1525c5a764cb6e5917e4425c60d424 100644 (file)
@@ -2,9 +2,7 @@
 
 exports[`renders favorite 1`] = `
 <li
-  aria-selected={false}
   key="foo"
-  role="option"
 >
   <ForwardRef(Link)
     data-key="foo"
@@ -54,9 +52,7 @@ exports[`renders favorite 1`] = `
 
 exports[`renders match 1`] = `
 <li
-  aria-selected={false}
   key="foo"
-  role="option"
 >
   <ForwardRef(Link)
     data-key="foo"
@@ -105,9 +101,7 @@ exports[`renders match 1`] = `
 
 exports[`renders recently browsed 1`] = `
 <li
-  aria-selected={false}
   key="foo"
-  role="option"
 >
   <ForwardRef(Link)
     data-key="foo"
@@ -156,9 +150,7 @@ exports[`renders recently browsed 1`] = `
 
 exports[`renders selected 1`] = `
 <li
-  aria-selected={false}
   key="foo"
-  role="option"
 >
   <ForwardRef(Link)
     data-key="foo"
@@ -204,12 +196,10 @@ exports[`renders selected 1`] = `
 
 exports[`renders selected 2`] = `
 <li
-  aria-selected={true}
-  className="active"
   key="foo"
-  role="option"
 >
   <ForwardRef(Link)
+    className="hover"
     data-key="foo"
     onClick={[MockFunction]}
     onFocus={[Function]}
index c0668213eed0bf040086c6e99420662499108d68..136288b0952ebe0c1da989e3f6f2f27715f69128 100644 (file)
@@ -5,7 +5,6 @@ exports[`renders "Show More" link 1`] = `
   <ul
     className="menu"
     key="header-TRK"
-    role="group"
   >
     <li
       className="menu-header"
@@ -40,7 +39,6 @@ exports[`renders different components and dividers between them 1`] = `
   <ul
     className="menu"
     key="header-FIL"
-    role="group"
   >
     <li
       className="menu-header"
@@ -57,7 +55,6 @@ exports[`renders different components and dividers between them 1`] = `
   <ul
     className="menu"
     key="header-TRK"
-    role="group"
   >
     <li
       className="menu-header"
index 1d48642153b20573d9620b2d5729a1100af8692a..0b2a879f2aa38ed5972f6d6414ebdbbc634e4339 100644 (file)
   border-top: 1px solid var(--barBorderColor);
 }
 
-.menu:focus {
-  outline: none;
-}
-
 .menu.is-container {
   padding: 5px;
 }
   white-space: nowrap;
 }
 
-.menu > li > a {
-  color: var(--baseFontColor);
-  border-bottom: none;
+.menu > li > a,
+.menu > li > button {
+  color: var(--neutral800);
+  border-width: 0 0 0 2px;
+  border-style: solid;
+  border-color: transparent;
   transition: none;
 }
 
 .menu > li > button {
-  color: var(--baseFontColor);
   text-align: left;
   width: 100%;
 }
 
-.menu > li > a.rich-item {
-  display: flex;
-  align-items: center;
-  border: 1px solid var(--gray80);
-  border-radius: 4px;
-  margin: 4px 10px;
-  padding: 2px 8px;
-  white-space: normal;
-}
-
-.menu .divider {
-  height: 1px;
-  margin: 6px 0;
-  overflow: hidden;
-  background-color: var(--barBorderColor);
-}
-
 .menu > li > a.disabled {
   color: var(--disableGrayText) !important;
   cursor: not-allowed !important;
 }
 
 .menu > li > a:hover,
-.menu > li > a:focus,
-.menu > li > button:hover,
-.menu > li > button:focus {
-  text-decoration: none;
-  color: var(--baseFontColor);
-  background-color: var(--barBackgroundColor);
+.menu > li > a.hover,
+.menu > li > button:hover {
+  background-color: var(--neutral50);
+  border-left-color: var(--blacka60);
 }
 
-.menu > .active > a,
-.menu > li > .active,
-.menu > .active > a:hover,
-.menu > li > .active:hover,
-.menu > .active > a:focus,
-.menu > li > .active:focus {
-  color: var(--baseFontColor);
-  text-decoration: none;
-  outline: 0;
-  background-color: var(--barBackgroundColor);
+.menu > li > a.active,
+.menu > li > button.active {
+  background-color: var(--info50);
+  border-left-color: var(--info500);
+}
+
+.menu > li > a.active:hover,
+.menu > li > a.active.hover,
+.menu > li > button.active:hover {
+  background-color: var(--info100);
 }
 
-.menu .menu-vertically-limited,
 .menu.menu-vertically-limited {
   max-height: 300px;
   overflow-y: auto;
 }
 
+.menu .divider {
+  height: 1px;
+  margin: 6px 0;
+  overflow: hidden;
+  background-color: var(--barBorderColor);
+}
+
 .menu-vertically-limited.with-top-separator {
   border-top: 1px solid #e6e6e6;
 }
index f51756fb1b33fc814ab5dec7e9f983635c099fd0..d2284771f5bf5053c14ec3c22a70e4f3e1cc4cd2 100644 (file)
@@ -149,6 +149,7 @@ module.exports = {
     primary400: '#297BAE',
 
     info50: '#ECF6FE',
+    info100: '#D9EDF7',
     info500: '#0271B9',
     info400: '#4B9FD5',
 
@@ -168,6 +169,7 @@ module.exports = {
     error500: '#D02F3A',
     error500a20: 'rgba(208, 47, 58, 0.20)',
 
+    neutral50: '#F3F3F3',
     neutral200: '#CCCCCC',
     neutral600: '#666666',
     neutral800: '#333333',
@@ -175,8 +177,9 @@ module.exports = {
     white: '#FFFFFF',
 
     black: '#000000',
-    blacka87: 'rgba(0, 0, 0, 0.87)',
-    blacka38: 'rgba(0, 0, 0, 0.38)'
+    blacka38: 'rgba(0, 0, 0, 0.38)',
+    blacka60: 'rgba(0, 0, 0, 0.60)',
+    blacka87: 'rgba(0, 0, 0, 0.87)'
   },
 
   sizes: {
index 16f93925d429b38e46f4e650842ffe3cb1a3a1c8..1ccd8dee6e8a75dcc27a6075c91ed1423b1d3b3f 100644 (file)
@@ -31,13 +31,15 @@ interface Props {
 
 interface State {
   active: string;
+  selected: string;
 }
 
 export default class SelectList extends React.PureComponent<Props, State> {
   constructor(props: Props) {
     super(props);
     this.state = {
-      active: props.currentItem
+      active: props.currentItem,
+      selected: props.currentItem
     };
   }
 
@@ -50,7 +52,7 @@ export default class SelectList extends React.PureComponent<Props, State> {
       prevProps.currentItem !== this.props.currentItem &&
       !this.props.items.includes(this.state.active)
     ) {
-      this.setState({ active: this.props.currentItem });
+      this.setState({ active: this.props.currentItem, selected: this.props.currentItem });
     }
   }
 
@@ -70,8 +72,8 @@ export default class SelectList extends React.PureComponent<Props, State> {
     } else if (event.key === KeyboardKeys.Enter) {
       event.preventDefault();
       event.stopImmediatePropagation();
-      if (this.state.active != null) {
-        this.handleSelect(this.state.active);
+      if (this.state.selected != null) {
+        this.handleSelect(this.state.selected);
       }
     }
   };
@@ -80,24 +82,24 @@ export default class SelectList extends React.PureComponent<Props, State> {
     this.props.onSelect(item);
   };
 
-  handleHover = (item: string) => {
-    this.setState({ active: item });
+  handleHover = (selected: string) => {
+    this.setState({ selected });
   };
 
   selectNextElement = (state: State, props: Props) => {
-    const idx = props.items.indexOf(state.active);
+    const idx = props.items.indexOf(state.selected);
     if (idx < 0) {
-      return { active: props.items[0] };
+      return { selected: props.items[0] };
     }
-    return { active: props.items[(idx + 1) % props.items.length] };
+    return { selected: props.items[(idx + 1) % props.items.length] };
   };
 
   selectPreviousElement = (state: State, props: Props) => {
-    const idx = props.items.indexOf(state.active);
+    const idx = props.items.indexOf(state.selected);
     if (idx <= 0) {
-      return { active: props.items[props.items.length - 1] };
+      return { selected: props.items[props.items.length - 1] };
     }
-    return { active: props.items[idx - 1] };
+    return { selected: props.items[idx - 1] };
   };
 
   renderChild = (child: any) => {
@@ -110,6 +112,7 @@ export default class SelectList extends React.PureComponent<Props, State> {
     }
     return React.cloneElement(child, {
       active: this.state.active,
+      selected: this.state.selected,
       onHover: this.handleHover,
       onSelect: this.handleSelect
     });
@@ -125,6 +128,7 @@ export default class SelectList extends React.PureComponent<Props, State> {
           this.props.items.map(item => (
             <SelectListItem
               active={this.state.active}
+              selected={this.state.selected}
               item={item}
               key={item}
               onHover={this.handleHover}
index 798cbe040a87e1da271e08a2ce16eac869c602c6..cbd66ee8899c77ddec66a3ca306494a74ec1b10b 100644 (file)
@@ -27,6 +27,7 @@ interface Props {
   item: string;
   onHover?: (item: string) => void;
   onSelect?: (item: string) => void;
+  selected?: string;
   title?: React.ReactNode;
 }
 
@@ -50,7 +51,10 @@ export default class SelectListItem extends React.PureComponent<Props> {
       <li>
         <a
           className={classNames(
-            { active: this.props.active === this.props.item },
+            {
+              active: this.props.active === this.props.item,
+              hover: this.props.selected === this.props.item
+            },
             this.props.className
           )}
           href="#"
index 3fee816dd25d69a8c2d23c090a145ff26ebfe0e6..66d6dbd063b3d61bbba532c4058e0058e62fca98 100644 (file)
@@ -53,15 +53,15 @@ it('should correclty handle user actions', () => {
     </SelectListItem>
   ));
   const list = shallowRender({ items, onSelect }, children);
-  expect(list.state().active).toBe('seconditem');
+  expect(list.state().selected).toBe('seconditem');
   keydown({ key: KeyboardKeys.DownArrow });
-  expect(list.state().active).toBe('third');
+  expect(list.state().selected).toBe('third');
   keydown({ key: KeyboardKeys.DownArrow });
-  expect(list.state().active).toBe('item');
+  expect(list.state().selected).toBe('item');
   keydown({ key: KeyboardKeys.UpArrow });
-  expect(list.state().active).toBe('third');
+  expect(list.state().selected).toBe('third');
   keydown({ key: KeyboardKeys.UpArrow });
-  expect(list.state().active).toBe('seconditem');
+  expect(list.state().selected).toBe('seconditem');
   keydown({ key: KeyboardKeys.Enter });
   expect(onSelect).toBeCalledWith('seconditem');
   list.instance().componentWillUnmount!();
index af2d76a8216bac11df8e60cfaff0a60586a29de0..03783efa411d160e960b65f91dc1b645d7033774 100644 (file)
@@ -10,6 +10,7 @@ exports[`should render correctly with children 1`] = `
     key=".$item"
     onHover={[Function]}
     onSelect={[Function]}
+    selected="seconditem"
   >
     <i
       className="myicon"
@@ -22,6 +23,7 @@ exports[`should render correctly with children 1`] = `
     key=".$seconditem"
     onHover={[Function]}
     onSelect={[Function]}
+    selected="seconditem"
   >
     <i
       className="myicon"
@@ -34,6 +36,7 @@ exports[`should render correctly with children 1`] = `
     key=".$third"
     onHover={[Function]}
     onSelect={[Function]}
+    selected="seconditem"
   >
     <i
       className="myicon"
@@ -53,6 +56,7 @@ exports[`should render correctly without children 1`] = `
     key="item"
     onHover={[Function]}
     onSelect={[Function]}
+    selected="seconditem"
   />
   <SelectListItem
     active="seconditem"
@@ -60,6 +64,7 @@ exports[`should render correctly without children 1`] = `
     key="seconditem"
     onHover={[Function]}
     onSelect={[Function]}
+    selected="seconditem"
   />
   <SelectListItem
     active="seconditem"
@@ -67,6 +72,7 @@ exports[`should render correctly without children 1`] = `
     key="third"
     onHover={[Function]}
     onSelect={[Function]}
+    selected="seconditem"
   />
 </ul>
 `;
index bb80725f6e6e1705aa1621b0cd948fba3fe4c990..af43b811421222ca14f91cdd2e0ed681de79cfa3 100644 (file)
@@ -300,19 +300,37 @@ export function selectStyle<Option, IsMulti extends boolean, Group extends Group
       position: 'absolute',
       color: '#666'
     }),
-    option: (_provided, state) => ({
-      display: 'block',
-      lineHeight: '20px',
-      padding: props?.large ? '4px 8px' : '0 8px',
-      boxSizing: 'border-box',
-      color: state.isDisabled ? colors.disableGrayText : colors.baseFontColor,
-      backgroundColor: state.isFocused ? colors.barBackgroundColor : colors.white,
-      fontSize: `${sizes.smallFontSize}`,
-      cursor: state.isDisabled ? 'default' : 'pointer',
-      whiteSpace: 'nowrap',
-      overflow: 'hidden',
-      textOverflow: 'ellipsis'
-    }),
+    option: (_provided, state) => {
+      let borderLeftColor = 'transparent';
+      let backgroundColor = colors.white;
+
+      if (state.isFocused && state.isSelected) {
+        borderLeftColor = colors.info500;
+        backgroundColor = colors.info100;
+      } else if (state.isFocused) {
+        borderLeftColor = colors.blacka60;
+        backgroundColor = colors.neutral50;
+      } else if (state.isSelected) {
+        borderLeftColor = colors.info500;
+        backgroundColor = colors.info50;
+      }
+
+      return {
+        display: 'block',
+        lineHeight: '20px',
+        padding: props?.large ? '4px 8px' : '0 8px',
+        boxSizing: 'border-box',
+        color: state.isDisabled ? colors.disableGrayText : colors.neutral800,
+        backgroundColor,
+        borderLeft: '2px solid transparent',
+        borderLeftColor,
+        fontSize: `${sizes.smallFontSize}`,
+        cursor: state.isDisabled ? 'default' : 'pointer',
+        whiteSpace: 'nowrap',
+        overflow: 'hidden',
+        textOverflow: 'ellipsis'
+      };
+    },
     input: () => ({
       display: 'flex',
       alignItems: 'center'
index d2a6b5bd3da4b967b4b51137fe8dce401073b025..0aaa48da2184d3539fb9ff35a86af13f70872086 100644 (file)
@@ -23,7 +23,7 @@
   margin-top: -16px;
   margin-left: 8px;
   padding: var(--gridSize);
-  border: 1px solid var(--barBorderColor);
+  border: 1px solid var(--neutral200);
   border-radius: 3px;
   box-sizing: border-box;
   background-color: #ffffff;