Repository: uber-web/baseui
Pull request #258 (2018-09-17 00:32:54)

feat(menu): add compact styling to OptionList
### Menu Component

Adding compact style to `OptionList`. Design pasted below for reference:

![image](https://user-images.githubusercontent.com/2263443/45602641-83318280-b9d6-11e8-9218-3513561b27c0.png)

#### [Live Preview](https://deploy-preview-258--baseui.netlify.com/?selectedKind=Menu&selectedStory=Stateless%20Menu%20Compact&full=0&addons=1&stories=1&panelRight=0&addonPanel=REACT_STORYBOOK%2Freadme%2Fpanel)


Patch:
diff --git a/src/menu/constants.js b/src/menu/constants.js
index 27fa62807..d9293d579 100644
--- a/src/menu/constants.js
+++ b/src/menu/constants.js
@@ -5,7 +5,6 @@ This source code is licensed under the MIT license found in the
 LICENSE file in the root directory of this source tree.
 */
 // @flow
-/* eslint-disable import/prefer-default-export */
 
 export const STATE_CHANGE_TYPES = {
   moveUp: 'moveUp',
@@ -23,3 +22,8 @@ export const KEY_STRINGS = {
   ArrowDown: 'ArrowDown',
   Enter: 'Enter',
 };
+
+export const OPTION_LIST_SIZE = {
+  default: 'default',
+  compact: 'compact',
+};
diff --git a/src/menu/examples.js b/src/menu/examples.js
index 06c5595c0..bd912e2e5 100644
--- a/src/menu/examples.js
+++ b/src/menu/examples.js
@@ -12,6 +12,7 @@ import Menu from './menu';
 import StatefulMenu from './stateful-menu';
 
 import OptionProfile from './option-profile';
+import {OPTION_LIST_SIZE} from './constants';
 
 function CloudComponent() {
   return (
@@ -66,6 +67,7 @@ const PROFILE_ITEMS = [
 export const suite = 'Menu Test Suite';
 export const examples = {
   MENU: 'Stateless Menu',
+  MENU_COMPACT: 'Stateless Menu Compact',
   MENU_PROFILE: 'Stateless Menu Profile',
   MENU_STATEFUL: 'Stateful Menu with Keybindings',
   MENU_PROFILE_STATEFUL: 'Stateful Menu Profile with Keybindings',
@@ -92,6 +94,27 @@ export default {
       />
     );
   },
+  [examples.MENU_COMPACT]: function StoryMenuCompact() {
+    return (
+      <Menu
+        items={ITEMS}
+        rootRef={React.createRef()}
+        overrides={{
+          List: {
+            style: {
+              width: '200px',
+            },
+          },
+          Option: {
+            props: {
+              getItemLabel: item => item.label,
+              size: OPTION_LIST_SIZE.compact,
+            },
+          },
+        }}
+      />
+    );
+  },
   [examples.MENU_PROFILE]: function Story2() {
     return (
       <Menu
diff --git a/src/menu/option-list.js b/src/menu/option-list.js
index b946c467e..2caeb410e 100644
--- a/src/menu/option-list.js
+++ b/src/menu/option-list.js
@@ -2,23 +2,34 @@
 import * as React from 'react';
 // Components
 import {ListItem as StyledListItem} from './styled-components';
+import {OPTION_LIST_SIZE} from './constants';
 import {getOverrideObject} from '../helpers/overrides';
 // Types
 import type {OptionListPropsT} from './types';
 
 export default function OptionList({
   item,
-  getItemLabel = (item: *) => (item ? item.label : ''),
-  overrides = {},
+  getItemLabel,
+  size,
+  overrides,
   ...restProps
 }: OptionListPropsT) {
   const {component: ListItem, props: listItemProps} = getOverrideObject(
     overrides.ListItem,
     StyledListItem,
   );
+  const sharedProps = {
+    $size: size,
+  };
   return (
-    <ListItem {...restProps} {...listItemProps}>
+    <ListItem {...sharedProps} {...restProps} {...listItemProps}>
       {getItemLabel(item)}
     </ListItem>
   );
 }
+
+OptionList.defaultProps = {
+  getItemLabel: (item: *) => (item ? item.label : ''),
+  size: OPTION_LIST_SIZE.default,
+  overrides: {},
+};
diff --git a/src/menu/styled-components.js b/src/menu/styled-components.js
index f101a7923..33985a1f9 100644
--- a/src/menu/styled-components.js
+++ b/src/menu/styled-components.js
@@ -6,17 +6,16 @@ LICENSE file in the root directory of this source tree.
 */
 // @flow
 import {styled} from '../styles';
+import {OPTION_LIST_SIZE} from './constants';
 
 import type {ThemeT} from '../styles';
 
 type StyledPropsT = {
   $theme: ThemeT,
+  $isHighlighted?: boolean,
+  $size?: $Keys<typeof OPTION_LIST_SIZE>,
 };
 
-type StyledListItemPropsT = {
-  $isHighlighted: boolean,
-} & StyledPropsT;
-
 export const List = styled('ul', ({$theme}: StyledPropsT) => ({
   position: 'relative',
   margin: 0,
@@ -29,8 +28,10 @@ export const List = styled('ul', ({$theme}: StyledPropsT) => ({
 
 export const ListItem = styled(
   'li',
-  ({$theme, $isHighlighted}: StyledListItemPropsT) => ({
-    ...$theme.typography.font300,
+  ({$theme, $isHighlighted, $size}: StyledPropsT) => ({
+    ...($size === OPTION_LIST_SIZE.compact
+      ? $theme.typography.font200
+      : $theme.typography.font300),
     position: 'relative',
     display: 'block',
     color: $isHighlighted ? $theme.colors.primary : $theme.colors.black,
@@ -41,15 +42,33 @@ export const ListItem = styled(
     ':hover': {
       backgroundColor: $theme.colors.mono300,
     },
-    paddingTop: $theme.sizing.scale300,
-    paddingBottom: $theme.sizing.scale300,
-    paddingRight: $theme.sizing.scale600,
-    paddingLeft: $theme.sizing.scale600,
+    paddingTop:
+      $size === OPTION_LIST_SIZE.compact
+        ? $theme.sizing.scale100
+        : $theme.sizing.scale300,
+    paddingBottom:
+      $size === OPTION_LIST_SIZE.compact
+        ? $theme.sizing.scale100
+        : $theme.sizing.scale300,
+    paddingRight:
+      $size === OPTION_LIST_SIZE.compact
+        ? $theme.sizing.scale900
+        : $theme.sizing.scale600,
+    paddingLeft:
+      $size === OPTION_LIST_SIZE.compact
+        ? $theme.sizing.scale900
+        : $theme.sizing.scale600,
     ':first-child': {
-      paddingTop: $theme.sizing.scale600,
+      paddingTop:
+        $size === OPTION_LIST_SIZE.compact
+          ? $theme.sizing.scale500
+          : $theme.sizing.scale600,
     },
     ':last-child': {
-      paddingBottom: $theme.sizing.scale600,
+      paddingBottom:
+        $size === OPTION_LIST_SIZE.compact
+          ? $theme.sizing.scale500
+          : $theme.sizing.scale600,
     },
   }),
 );
diff --git a/src/menu/types.js b/src/menu/types.js
index 17dead152..50b28601b 100644
--- a/src/menu/types.js
+++ b/src/menu/types.js
@@ -5,10 +5,9 @@ This source code is licensed under the MIT license found in the
 LICENSE file in the root directory of this source tree.
 */
 // @flow
-/* eslint-disable import/prefer-default-export */
 import * as React from 'react';
 import type {OverrideT} from '../helpers/overrides';
-import {STATE_CHANGE_TYPES} from './constants';
+import {STATE_CHANGE_TYPES, OPTION_LIST_SIZE} from './constants';
 
 export type ItemT = *;
 export type ItemsT = Array<ItemT>;
@@ -134,7 +133,8 @@ export type StatelessMenuProfilePropsT = SharedStatelessPropsT &
 export type OptionListPropsT = {
   item: ItemT,
   getItemLabel: GetItemLabelFnT,
-  overrides?: {
+  size: $Keys<typeof OPTION_LIST_SIZE>,
+  overrides: {
     ListItem?: OverrideT<*>,
   },
 };


Test patch:
diff --git a/src/menu/__tests__/__snapshots__/styled-components.test.js.snap b/src/menu/__tests__/__snapshots__/styled-components.test.js.snap
index e8de604f3..0f26c302c 100644
--- a/src/menu/__tests__/__snapshots__/styled-components.test.js.snap
+++ b/src/menu/__tests__/__snapshots__/styled-components.test.js.snap
@@ -41,6 +41,35 @@ Object {
 }
 `;
 
+exports[`Menu Styled Components ListItem - compact: correct styles 1`] = `
+Object {
+  ":first-child": Object {
+    "paddingTop": "$theme.sizing.scale500",
+  },
+  ":hover": Object {
+    "backgroundColor": "$theme.colors.mono300",
+  },
+  ":last-child": Object {
+    "paddingBottom": "$theme.sizing.scale500",
+  },
+  "color": "$theme.colors.black",
+  "cursor": "pointer",
+  "display": "block",
+  "fontFamily": "$theme.typography.font200.fontFamily",
+  "fontSize": "$theme.typography.font200.fontSize",
+  "fontWeight": "$theme.typography.font200.fontWeight",
+  "lineHeight": "$theme.typography.font200.lineHeight",
+  "paddingBottom": "$theme.sizing.scale100",
+  "paddingLeft": "$theme.sizing.scale900",
+  "paddingRight": "$theme.sizing.scale900",
+  "paddingTop": "$theme.sizing.scale100",
+  "position": "relative",
+  "transitionDuration": "$theme.animation.timing100",
+  "transitionProperty": "color, background-color",
+  "transitionTimingFunction": "$theme.animation.easeOutCurve",
+}
+`;
+
 exports[`Menu Styled Components ListItem - highlighted render: correct styles 1`] = `
 Object {
   ":first-child": Object {
diff --git a/src/menu/__tests__/styled-components.test.js b/src/menu/__tests__/styled-components.test.js
index 01724725d..29d30310c 100644
--- a/src/menu/__tests__/styled-components.test.js
+++ b/src/menu/__tests__/styled-components.test.js
@@ -18,6 +18,7 @@ import {
   ProfileImgContainer,
   ProfileLabelsContainer,
 } from '../styled-components';
+import {OPTION_LIST_SIZE} from '../constants';
 
 function makeTest({
   title,
@@ -52,6 +53,14 @@ describe('Menu Styled Components', () => {
     },
   });
 
+  makeTest({
+    title: 'ListItem - compact',
+    component: ListItem,
+    props: {
+      $size: OPTION_LIST_SIZE.compact,
+    },
+  });
+
   makeTest({
     title: 'List - basic render',
     component: List,