Skip to content

Button - allow caller to specify icon for kind in theme and enhance kind padding #6297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 26, 2022
Merged

Conversation

taysea
Copy link
Collaborator

@taysea taysea commented Aug 23, 2022

What does this PR do?

Adds theme.button[kind].icon, theme.button.hover[kind].icon, theme.button.active[kind].icon, theme.button.disabled[kind].icon to allow theme to include a specific icon in a button kind.

Additionally, enhances theme.button.size to accept kind specific padding. This allows certain button kinds to take on different padding than other button kinds.

Also, in addition to adding relevant tests for this functionality, I fixed a size test that was written incorrectly. The snapshot was not showing the desired styling --> this PR updates the test and I have verified the snapshot changes now render the desired styling.

Also, adding string as an accepted button size -- based on need from upcoming grommet-theme-hpe changes.

Where should the reviewer start?

What testing has been done on this PR?

Locally in storybook by adjusted Themed Button TS story:

import React from 'react';

import { Box, Button, Grommet, Heading, Paragraph, Text } from 'grommet';
import { Hpe } from 'grommet-icons';
import { hpe } from 'grommet-theme-hpe';
import { deepMerge } from '../../../../utils';

const kindButtonTheme = deepMerge(hpe, {
  button: {
    size: {
      small: {
        border: {
          radius: '4px',
        },
        pad: {
          vertical: '4px',
          horizontal: '8px',
        },
        'cta-primary': {
          pad: {
            vertical: '3px',
            horizontal: '12px',
          },
        },
        'cta-alternate': {
          pad: {
            vertical: '3px',
            horizontal: '12px',
          },
        },
      },
      medium: {
        border: {
          radius: '4px',
        },
        pad: {
          vertical: '6px',
          horizontal: '12px',
        },
        'cta-primary': {
          pad: {
            vertical: '6px',
            horizontal: '16px',
          },
        },
        'cta-alternate': {
          pad: {
            vertical: '6px',
            horizontal: '16px',
          },
        },
      },
      large: {
        border: {
          radius: '6px',
        },
        pad: {
          vertical: '8px',
          horizontal: '16px',
        },
        'cta-primary': {
          pad: {
            vertical: '8px',
            horizontal: '20px',
          },
        },
        'cta-alternate': {
          pad: {
            vertical: '8px',
            horizontal: '20px',
          },
        },
      },
      xlarge: {
        border: {
          radius: '6px',
        },
        pad: {
          vertical: '10px',
          horizontal: '20px',
        },
        'cta-primary': {
          pad: {
            vertical: '10px',
            horizontal: '28px',
          },
        },
        'cta-alternate': {
          pad: {
            vertical: '10px',
            horizontal: '28px',
          },
        },
      },
    },
    extend: ({ sizeProp }) => {
      let fontSize = '19px';
      let lineHeight = '24px';
      if (sizeProp === 'small') {
        fontSize = '16px';
        lineHeight = '22px';
      } else if (sizeProp === 'large') {
        fontSize = '20px';
        lineHeight = '26px';
      } else if (sizeProp === 'xlarge') {
        fontSize = '22px';
        lineHeight = '28px';
      }
      return `
    font-size: ${fontSize};
    line-height: ${lineHeight}
    `;
    },
    'cta-primary': {
      background: { color: 'brand' },
      border: {
        radius: '100px',
      },
      color: 'white',
      font: { weight: 'bold' },
      icon: <Hpe />,
      reverse: true,
      size: {
        small: {
          pad: {
            vertical: '3px',
            horizontal: '12px',
          },
        },
      },
    },
    'cta-alternate': {
      background: 'background-contrast',
      border: {
        radius: '100px',
      },
      color: 'text-strong',
      font: {
        weight: 'bold',
      },
      icon: <Hpe color="brand" />,
      reverse: true,
    },
    active: {
      'cta-alternate': {
        icon: <Hpe color="brand" />,
      },
    },
    disabled: {
      'cta-alternate': {
        border: {
          width: '2px',
          color: 'text-xweak',
        },
      },
      'cta-primary': {
        border: {
          width: '2px',
          color: 'text-xweak',
        },
      },
    },
    secondary: {
      border: {
        color: 'brand',
      },
    },
  },
});

const customTheme = {
  global: {
    font: {
      family: 'Arial',
    },
  },
  button: {
    border: {
      radius: undefined,
      color: '#2196f3',
    },
    disabled: {
      color: 'orange',
      border: {
        color: 'orange',
      },
      extend: `border: 10px dashed red;`,
    },
    padding: {
      vertical: '12px',
      horizontal: '24px',
    },
    primary: {
      color: '#2196f3',
      active: {
        border: {
          color: 'red',
        },
        extend: `background: cadetblue;`,
      },
      extend: `background: skyblue; border: 5px dotted green;`,
    },
    extend: (props) => {
      let extraStyles = '';
      if (props.primary) {
        extraStyles = `
            text-transform: uppercase;
          `;
      }
      return `
          font-size: 12px;
          font-weight: bold;
          ${extraStyles}
        `;
    },
  },
};

const coloredButton = {
  button: {
    border: {
      color: 'accent-1',
    },
    color: { dark: 'accent-1', light: 'dark-2' },
    primary: {
      color: 'neutral-2',
    },
  },
};

const PrimaryButtons = () => (
  <Box gap="medium">
    <Button
      alignSelf="start"
      label="Primary small"
      onClick={() => {}}
      color="brand"
      primary
      size="small"
    />
    <Button
      alignSelf="start"
      label="Primary"
      onClick={() => {}}
      primary
      color="brand"
    />
    <Button
      alignSelf="start"
      label="Primary large"
      onClick={() => {}}
      primary
      color="brand"
      size="large"
    />
    <Button
      alignSelf="start"
      label="Primary xlarge"
      onClick={() => {}}
      primary
      color="brand"
      size="xlarge"
    />
    <Button
      alignSelf="start"
      label="Disabled Primary"
      onClick={() => {}}
      primary
      disabled
    />
    <Button
      alignSelf="start"
      label="Active Primary"
      onClick={() => {}}
      primary
      active
    />
  </Box>
);

const DefaultButtons = () => (
  <Box gap="medium">
    <Button
      alignSelf="start"
      label="Default small"
      onClick={() => {}}
      size="small"
    />
    <Button alignSelf="start" label="Default" onClick={() => {}} />
    <Button
      alignSelf="start"
      label="Default large"
      onClick={() => {}}
      size="large"
    />
    <Button label="Default xlarge" onClick={() => {}} size="xlarge" />
    <Button
      alignSelf="start"
      label="Disabled Default"
      onClick={() => {}}
      disabled
    />
    <Button
      alignSelf="start"
      label="Active Default"
      onClick={() => {}}
      active
    />
  </Box>
);

const SecondaryButtons = () => (
  <Box gap="medium">
    <Button
      alignSelf="start"
      label="Secondary small"
      onClick={() => {}}
      secondary
      size="small"
    />
    <Button alignSelf="start" label="Secondary" onClick={() => {}} secondary />
    <Button
      alignSelf="start"
      label="Secondary large"
      onClick={() => {}}
      size="large"
      secondary
    />
    <Button
      label="Secondary xlarge"
      onClick={() => {}}
      size="xlarge"
      secondary
    />
    <Button
      alignSelf="start"
      label="Disabled Secondary"
      onClick={() => {}}
      disabled
      secondary
    />
    <Button
      alignSelf="start"
      label="Active Secondary"
      onClick={() => {}}
      active
      secondary
    />
  </Box>
);

const CTAPrimary = () => (
  <Box gap="medium">
    <Button
      alignSelf="start"
      label="CTA Primary small"
      onClick={() => {}}
      kind="cta-primary"
      size="small"
    />
    <Button
      alignSelf="start"
      label="CTA Primary"
      onClick={() => {}}
      kind="cta-primary"
    />
    <Button
      alignSelf="start"
      label="CTA Primary large"
      onClick={() => {}}
      size="large"
      kind="cta-primary"
    />
    <Button
      label="CTA Primary xlarge"
      onClick={() => {}}
      size="xlarge"
      kind="cta-primary"
    />
    <Button
      alignSelf="start"
      label="Disabled CTA Primary"
      kind="cta-primary"
      onClick={() => {}}
      disabled
    />
    <Button
      alignSelf="start"
      label="Active CTA Primary"
      kind="cta-primary"
      onClick={() => {}}
      active
    />
  </Box>
);

const CTAAlternate = () => (
  <Box gap="medium">
    <Button
      alignSelf="start"
      label="CTA Alternate small"
      onClick={() => {}}
      size="small"
      kind="cta-alternate"
    />
    <Button
      alignSelf="start"
      label="CTA Alternate"
      onClick={() => {}}
      kind="cta-alternate"
    />
    <Button
      alignSelf="start"
      label="CTA Alternate large"
      onClick={() => {}}
      size="large"
      kind="cta-alternate"
    />
    <Button
      label="CTA Alternate xlarge"
      onClick={() => {}}
      size="xlarge"
      kind="cta-alternate"
    />
    <Button
      alignSelf="start"
      label="Disabled CTA Alternate"
      kind="cta-alternate"
      onClick={() => {}}
      disabled
    />
    <Button
      alignSelf="start"
      label="Active CTA Alternate"
      kind="cta-alternate"
      onClick={() => {}}
      active
    />
  </Box>
);

export const ThemedButtons = () => (
    <Grommet theme={kindButtonTheme}>
        <Box direction="row" gap="large">
          <DefaultButtons />
          <SecondaryButtons />
          <PrimaryButtons />
          <CTAPrimary />
          <CTAAlternate />
        </Box>
    </Grommet>
);

ThemedButtons.storyName = 'Themed Buttons';

export default {
  title: 'Controls/Button/Custom Themed/Themed Buttons',
};

How should this be manually tested?

In storybook. Can

Do Jest tests follow these best practices?

  • screen is used for querying.
  • The correct query is used. (Refer to this list of queries)
  • userEvent is used in place of fireEvent.
  • asFragment() is used for snapshot testing.

Any background context you want to provide?

What are the relevant issues?

Closes #6271

Screenshots (if appropriate)

Do the grommet docs need to be updated?

Yes.

Should this PR be mentioned in the release notes?

Yes.

Is this change backwards compatible or is it a breaking change?

Backwards compatible.

Copy link
Collaborator

@MikeKingdom MikeKingdom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Just a minor typo in a comment

@taysea taysea requested a review from halocline August 24, 2022 23:14
Copy link
Collaborator

@halocline halocline left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems odd to me that we would need to set if I don't want to modify it from its base:

    active: {
      'cta-alternate': {
        icon: <Hpe color="brand" />,
      },
    },

My expectation would be that if this is undefined, the active state would inherit the kind's base icon color.

Thoughts? Investigate getIconColor logic?

@taysea
Copy link
Collaborator Author

taysea commented Aug 25, 2022

It seems odd to me that we would need to set if I don't want to modify it from its base:

    active: {
      'cta-alternate': {
        icon: <Hpe color="brand" />,
      },
    },

My expectation would be that if this is undefined, the active state would inherit the kind's base icon color.

Thoughts? Investigate getIconColor logic?

I would agree if it doesn't deviate from its base then I don't think we should have to define it again. Let me investigate the getIconColor logic.

EDIT: This should be resolved in the latest commit.

@taysea taysea requested a review from halocline August 25, 2022 23:25
Copy link
Collaborator

@halocline halocline left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functionality and implementation are looking good. Small types / theme question.

@@ -68,7 +68,7 @@ if (process.env.NODE_ENV !== 'production') {
primary: PropTypes.bool,
reverse: PropTypes.bool,
secondary: PropTypes.bool,
size: PropTypes.oneOf(['small', 'medium', 'large']),
size: PropTypes.oneOf(['small', 'medium', 'large', 'xlarge']),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also noticing, that kind is not in propTypes definition, nor is it documented. Is that still purposeful?

cc: @jcfilben

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have it in typescript, so it feels like adding it here would be reasonable but maybe for sake of progressing this PR we can handle that in a small standalone PR (since it's an existing thing)/

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was not intending it for this PR, rather a point of awareness and stimulate discussion.

Copy link
Collaborator

@jcfilben jcfilben Aug 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also noticing, that kind is not in propTypes definition, nor is it documented. Is that still purposeful?

Sorry for slow response, I must have missed this comment when I was originally tagged. Anyway I'm looking into this... It also looks like the TS definition for kind is 'string' but looking at the code I think it could be a 'string' or 'object'. I'm not sure why we aren't documenting this, I can check with Shimi and maybe get more context.

taysea and others added 6 commits August 25, 2022 19:00
Co-authored-by: Eric Soderberg <eric.soderberg@hpe.com>
Co-authored-by: Matthew Glissmann <mdglissmann@gmail.com>
Co-authored-by: Matthew Glissmann <mdglissmann@gmail.com>
@taysea taysea requested a review from halocline August 26, 2022 17:37
Copy link
Collaborator

@halocline halocline left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @taysea.

@taysea taysea requested a review from ericsoderberghp August 26, 2022 18:05
@ericsoderberghp ericsoderberghp merged commit 6961c7e into grommet:master Aug 26, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Button theme should support specification of icon for kind
5 participants