Repository: mapbox/mr-ui
Pull request #115 (2020-10-05 05:04:49)

Add `inline` prop to IconText component
This PR adds an optional `inline` prop to the `IconText` component, with a default value of `false`. When set to `true`, the outer `<span>` wrapping the component will have a class of `'flex-parent-inline'` rather than the default `'flex-parent'`. This change will allow the `IconText` component to be horizontally centered as expected when used as the child of an element with class `'align-center'`.

Note that one can't get around this (though please do correct me if I'm wrong!) by adding a `className` prop with a value set to `'mx-auto'` since the outer `<span>` still would have `display: inline`.


Patch:
diff --git a/src/components/icon-text/icon-text.js b/src/components/icon-text/icon-text.js
index 2d370aa..d66d5fd 100644
--- a/src/components/icon-text/icon-text.js
+++ b/src/components/icon-text/icon-text.js
@@ -18,6 +18,7 @@ class IconText extends React.Component {
   render() {
     const { props } = this;
     const spacer = props.gap === 'small' ? '3' : '6';
+    const inline = props.inline ? '-inline' : '';
 
     const before = !props.iconBefore ? null : (
       <span className={`flex-child mr${spacer}`}>
@@ -32,7 +33,7 @@ class IconText extends React.Component {
     );
 
     return (
-      <span className="flex-parent flex-parent--center-cross">
+      <span className={`flex-parent${inline} flex-parent--center-cross`}>
         {before}
         <span className="flex-child">{props.children}</span>
         {after}
@@ -61,11 +62,17 @@ IconText.propTypes = {
   /**
    * An icon to place after the text. See details documented for `iconBefore`.
    */
-  iconAfter: PropTypes.oneOfType([PropTypes.node, PropTypes.string])
+  iconAfter: PropTypes.oneOfType([PropTypes.node, PropTypes.string]),
+  /**
+   * A boolean indicating whether the component's wrapper element should be
+   * 'flex-parent' (false) or 'flex-parent-inline' (true).
+   */
+  inline: PropTypes.bool
 };
 
 IconText.defaultProps = {
-  gap: 'small'
+  gap: 'small',
+  inline: false
 };
 
 export default IconText;
diff --git a/src/components/tab-list/tab-list.js b/src/components/tab-list/tab-list.js
index b8b8e11..91794c7 100644
--- a/src/components/tab-list/tab-list.js
+++ b/src/components/tab-list/tab-list.js
@@ -202,7 +202,7 @@ export default class TabList extends React.PureComponent {
       <div className="clearfix">
         {alwaysRenderedItems}
         {sometimesHiddenItems}
-        {moreButton}
+        {truncateBy < items.length && moreButton}
       </div>
     );
   }


Test patch:
diff --git a/src/components/button/__tests__/__snapshots__/button.test.js.snap b/src/components/button/__tests__/__snapshots__/button.test.js.snap
index e753bcc..812bc1a 100644
--- a/src/components/button/__tests__/__snapshots__/button.test.js.snap
+++ b/src/components/button/__tests__/__snapshots__/button.test.js.snap
@@ -92,6 +92,7 @@ exports[`Link with outline and icon text, extra padding renders as expected 1`]
   <IconText
     gap="small"
     iconBefore="bug"
+    inline={false}
   >
     Go do things
   </IconText>
diff --git a/src/components/control-file/__tests__/__snapshots__/control-file.test.js.snap b/src/components/control-file/__tests__/__snapshots__/control-file.test.js.snap
index e25683b..3115283 100644
--- a/src/components/control-file/__tests__/__snapshots__/control-file.test.js.snap
+++ b/src/components/control-file/__tests__/__snapshots__/control-file.test.js.snap
@@ -36,6 +36,7 @@ exports[`ControlFile all options renders 1`] = `
           <IconText
             gap="small"
             iconBefore="harddrive"
+            inline={false}
           >
             Select an image
           </IconText>
@@ -98,6 +99,7 @@ exports[`ControlFile basic receives an array of one file as value, displaying it
           <IconText
             gap="small"
             iconBefore="harddrive"
+            inline={false}
           >
             bon jovi.mp3
           </IconText>
@@ -187,6 +189,7 @@ exports[`ControlFile basic renders 1`] = `
           <IconText
             gap="small"
             iconBefore="harddrive"
+            inline={false}
           >
             Select a file
           </IconText>
@@ -245,6 +248,7 @@ exports[`ControlFile disabled renders 1`] = `
           <IconText
             gap="small"
             iconBefore="harddrive"
+            inline={false}
           >
             Select a file
           </IconText>
@@ -303,6 +307,7 @@ exports[`ControlFile multiple receives an array of files as value, displaying th
           <IconText
             gap="small"
             iconBefore="harddrive"
+            inline={false}
           >
             bon jovi.mp3, aerosmith.mp3
           </IconText>
diff --git a/src/components/icon-text/__tests__/__snapshots__/icon-text.test.js.snap b/src/components/icon-text/__tests__/__snapshots__/icon-text.test.js.snap
index 8d75206..8effcaa 100644
--- a/src/components/icon-text/__tests__/__snapshots__/icon-text.test.js.snap
+++ b/src/components/icon-text/__tests__/__snapshots__/icon-text.test.js.snap
@@ -76,6 +76,28 @@ exports[`icons on both sides, including a non-string one renders as expected 1`]
 </span>
 `;
 
+exports[`inline flex parent renders as expected 1`] = `
+<span
+  className="flex-parent-inline flex-parent--center-cross"
+>
+  <span
+    className="flex-child mr3"
+  >
+    <Icon
+      inline={false}
+      name="polyline"
+      passthroughProps={Object {}}
+      size={18}
+    />
+  </span>
+  <span
+    className="flex-child"
+  >
+    Inline!
+  </span>
+</span>
+`;
+
 exports[`large gap renders as expected 1`] = `
 <span
   className="flex-parent flex-parent--center-cross"
diff --git a/src/components/icon-text/__tests__/icon-text-test-cases.js b/src/components/icon-text/__tests__/icon-text-test-cases.js
index 1144e58..0234a82 100644
--- a/src/components/icon-text/__tests__/icon-text-test-cases.js
+++ b/src/components/icon-text/__tests__/icon-text-test-cases.js
@@ -45,10 +45,24 @@ testCases.bothIcons = {
 testCases.buttonyDisplay = {
   description: 'a buttony look',
   element: (
-    <button aria-label="Duplicate" type="button" className="link txt-bold txt-s block">
+    <button
+      aria-label="Duplicate"
+      type="button"
+      className="link txt-bold txt-s block"
+    >
       <IconText iconBefore="duplicate">Duplicate</IconText>
     </button>
   )
 };
 
+testCases.flexParentInline = {
+  description: 'inline flex parent',
+  component: IconText,
+  props: {
+    children: 'Inline!',
+    iconBefore: 'polyline',
+    inline: true
+  }
+};
+
 export { testCases };
diff --git a/src/components/icon-text/__tests__/icon-text.test.js b/src/components/icon-text/__tests__/icon-text.test.js
index 3b3ab5d..1a31d0e 100644
--- a/src/components/icon-text/__tests__/icon-text.test.js
+++ b/src/components/icon-text/__tests__/icon-text.test.js
@@ -48,3 +48,14 @@ describe(testCases.bothIcons.description, () => {
     expect(wrapper).toMatchSnapshot();
   });
 });
+
+describe(testCases.flexParentInline.description, () => {
+  beforeEach(() => {
+    testCase = testCases.flexParentInline;
+    wrapper = shallow(React.createElement(testCase.component, testCase.props));
+  });
+
+  test('renders as expected', () => {
+    expect(wrapper).toMatchSnapshot();
+  });
+});
diff --git a/src/components/tab-list/__tests__/__snapshots__/tab-list.test.js.snap b/src/components/tab-list/__tests__/__snapshots__/tab-list.test.js.snap
index b2fd067..12011a7 100644
--- a/src/components/tab-list/__tests__/__snapshots__/tab-list.test.js.snap
+++ b/src/components/tab-list/__tests__/__snapshots__/tab-list.test.js.snap
@@ -389,6 +389,43 @@ exports[`TabList links renders 1`] = `
 </div>
 `;
 
+exports[`TabList only show the more + button if there are more items not currently visible renders 1`] = `
+<div
+  className="clearfix"
+>
+  <div
+    className="fl"
+    key="0"
+  >
+    <button
+      aria-label="one"
+      className="px6 mr24 fl-mm align-center-mm align-l py12 border-b-mm border-b--2 inline-block border-b border--blue txt-bold "
+      data-test="one"
+      key="one"
+      onClick={[Function]}
+      type="button"
+    >
+      Label one
+    </button>
+  </div>
+  <div
+    className="fl"
+    key="1"
+  >
+    <button
+      aria-label="two"
+      className="px6 mr24 fl-mm align-center-mm align-l py12 border-b-mm border-b--2 inline-block border-b border--transparent "
+      data-test="two"
+      key="two"
+      onClick={[Function]}
+      type="button"
+    >
+      Label two
+    </button>
+  </div>
+</div>
+`;
+
 exports[`TabList truncate all renders 1`] = `
 <div
   className="clearfix"
diff --git a/src/components/tab-list/__tests__/tab-list-test-cases.js b/src/components/tab-list/__tests__/tab-list-test-cases.js
index 58d62b2..a9120e8 100644
--- a/src/components/tab-list/__tests__/tab-list-test-cases.js
+++ b/src/components/tab-list/__tests__/tab-list-test-cases.js
@@ -49,6 +49,20 @@ testCases.truncateAll = {
   }
 };
 
+testCases.moreButtonOnlyIfNeeded = {
+  description:
+    'only show the more + button if there are more items not currently visible',
+  component: TabList,
+  props: {
+    activeItem: 'one',
+    truncateBy: 2,
+    items: [
+      { id: 'one', label: 'Label one' },
+      { id: 'two', label: 'Label two' }
+    ]
+  }
+};
+
 testCases.labelNode = {
   description: 'a label contains a node',
   component: TabList,
diff --git a/src/components/tab-list/__tests__/tab-list.test.js b/src/components/tab-list/__tests__/tab-list.test.js
index 67c106f..77aee0e 100644
--- a/src/components/tab-list/__tests__/tab-list.test.js
+++ b/src/components/tab-list/__tests__/tab-list.test.js
@@ -32,6 +32,7 @@ describe('TabList', () => {
     test('activeItem prop updates active item', () => {
       wrapper.setProps({ activeItem: 'one' });
       wrapper.update();
+      expect(wrapper.find('.border--blue.txt-bold').length).toBe(1);
     });
   });
   describe(testCases.links.description, () => {
@@ -43,14 +44,13 @@ describe('TabList', () => {
     });
 
     test('renders', () => {
-      expect(
-        toJson(shallow(React.createElement(testCase.component, testCase.props)))
-      ).toMatchSnapshot();
+      expect(toJson(wrapper)).toMatchSnapshot();
     });
 
     test('activeItem prop updates active item', () => {
       wrapper.setProps({ activeItem: 'one' });
       wrapper.update();
+      expect(wrapper.find({ 'data-test': 'one' }).length).toBe(1);
     });
   });
   describe(testCases.truncateAll.description, () => {
@@ -62,14 +62,31 @@ describe('TabList', () => {
     });
 
     test('renders', () => {
-      expect(
-        toJson(shallow(React.createElement(testCase.component, testCase.props)))
-      ).toMatchSnapshot();
+      expect(toJson(wrapper)).toMatchSnapshot();
+    });
+
+    test('activeItem prop updates active item', () => {
+      wrapper.setProps({ activeItem: 'one' });
+      wrapper.update();
+      expect(wrapper.find({ 'data-test': 'one' }).length).toBe(1);
+    });
+  });
+  describe(testCases.moreButtonOnlyIfNeeded.description, () => {
+    beforeEach(() => {
+      testCase = testCases.moreButtonOnlyIfNeeded;
+      wrapper = shallow(
+        React.createElement(testCase.component, testCase.props)
+      );
+    });
+
+    test('renders', () => {
+      expect(toJson(wrapper)).toMatchSnapshot();
     });
 
     test('activeItem prop updates active item', () => {
       wrapper.setProps({ activeItem: 'one' });
       wrapper.update();
+      expect(wrapper.find({ 'data-test': 'one' }).length).toBe(1);
     });
   });
   describe(testCases.labelNode.description, () => {
@@ -97,6 +114,7 @@ describe('TabList', () => {
     test('activeItem prop updates active item', () => {
       wrapper.setProps({ activeItem: 'one' });
       wrapper.update();
+      expect(wrapper.find({ 'data-test': 'one' }).length).toBe(1);
     });
   });
 });