Skip to content

USWDS - Button: Add basic support for usa-icon in buttons #5398

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 17 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/usa-button/src/styles/_usa-button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ $button-stroke: inset 0 0 0 units($theme-button-stroke-width);
@include button-disabled;
}

// @TODO: Use `:has()` selector once browser support improves.
// https://developer.mozilla.org/en-US/docs/Web/CSS/:has#browser_compatibility
.usa-icon {
margin-top: -0.1em; // Similar pattern to _usa-breadcrumb.scss:14.
vertical-align: middle;
}

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about using flexbox for alignment on usa-button elements instead of creating a rule with negative margins here? I’d have to do some testing to confirm, but a quick experiment showed that it created more consistently centered elements at varying font and icon sizes, as well as allowed for buttons to be the same height when placed next to each other. It also didn't seem to impact visual display if a single button had no icon added.

For example, in this PR buttons in a segmented button group do not have a consistent height if icons are added to only some buttons in a row.

image

Additionally, I was looking at the open PR for button group wrap alignment, and it got me thinking that display: inline-flex and align-items: stretch might offer the answer to both scenarios.

I just did some quick poking and it looks like adding the style rules below would result in the following. Curious to hear what you think.

image

.usa-button {
 align-items: center;
  justify-content: center;
  display: inline-flex;
  flex-direction: row;
 }

 .usa-button-group{
   align-items: stretch;
}

.usa-button-group__item {
  display: inline-flex;
}

.usa-icon {
  min-width: $icon-inline-size-magic-number;
}

For reference, the HTML I used:

<ul class="usa-button-group usa-button-group--segmented">
    <li class="usa-button-group__item">
        <button type="button" class="usa-button"><svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
                <use xlink:href="./img/sprite.svg#navigate_before"></use>
            </svg>Previous</button>
    </li>
    <li class="usa-button-group__item">
        <button type="button" class="usa-button usa-button--outline">Current</button>
    </li>
    <li class="usa-button-group__item">
        <button type="button" class="usa-button usa-button--outline">Next<svg class="usa-icon" aria-hidden="true"
                focusable="false" role="img">
                <use xlink:href="./img/sprite.svg#navigate_next"></use>
            </svg></button>
    </li>
</ul><!-- start segmented styles -->
<ul class="usa-button-group usa-button-group--segmented">
    <li class="usa-button-group__item">
        <button type="button" class="usa-button"><svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
                <use xlink:href="./img/sprite.svg#navigate_before"></use>
            </svg>Previous</button>
    </li>
    <li class="usa-button-group__item">
        <button type="button" class="usa-button usa-button--outline">Current</button>
    </li>
    <li class="usa-button-group__item">
        <button type="button" class="usa-button usa-button--outline">Next with wrapping text<svg class="usa-icon"
                aria-hidden="true" focusable="false" role="img">
                <use xlink:href="./img/sprite.svg#navigate_next"></use>
            </svg></button>
    </li>
</ul>
<ul class="usa-button-group usa-button-group--segmented">
    <li class="usa-button-group__item">
        <button type="button" class="usa-button font-body-xl">Map<svg class="usa-icon usa-icon--size-3"
                aria-hidden="true" focusable="false" role="img">
                <use xlink:href="./img/sprite.svg#navigate_next"></use>
            </svg></button>
    </li>
    <li class="usa-button-group__item">
        <button type="button" class="usa-button usa-button--outline">Hybrid</button>
    </li>
    <li class="usa-button-group__item">
        <button type="button" class="usa-button usa-button--outline">Satellite<svg class="usa-icon usa-icon--size-4"
                aria-hidden="true" focusable="false" role="img">
                <use xlink:href="./img/sprite.svg#navigate_next"></use>
            </svg></button>
    </li>
</ul>

<ul class="usa-button-group usa-button-group--segmented">
    <li class="usa-button-group__item">
        <button type="button" class="usa-button ">Map</button>
    </li>
    <li class="usa-button-group__item">
        <button type="button" class="usa-button ">Hybrid</button>
    </li>
    <li class="usa-button-group__item">
        <button type="button" class="usa-button ">Satellite</button>
    </li>
</ul>

Copy link
Contributor Author

@mejiaj mejiaj Sep 8, 2023

Choose a reason for hiding this comment

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

@amyleadem

Re: display: inline-flex. Because this is such a widely used button ― I wanted to minimize any potentially breaking changes as much as possible.

Since we already specify inline-block, I don't think the switch to inline-flex is as drastic as I imagined. Double-checking MDN display - CSS: Cascading Style Sheets | MDN and there should be very little display difference at all.

This addition improves the experience, but might require a new theme setting for the column gap. I'll play with this.


I've added flex box and a new column gap setting in 03082ea. For now, items are centered. I'd like to keep the button group changes separate, if possible. This would keep the scope of our testing more manageable.

Example with icons on both sides (for testing only)
image

Small screens
image

@media (forced-colors: active) {
&:not(.usa-button--unstyled) {
border: $border-high-contrast;
Expand Down
26 changes: 26 additions & 0 deletions packages/usa-button/src/usa-button.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ import {
UnstyledContent,
} from "./content";

import { icons } from "../../usa-icon/src/usa-icon.json";

const iconItems = icons.items;
const iconNames = iconItems.map((item) => item.name);

export default {
title: "Components/Button",
argTypes: {
Expand All @@ -31,6 +36,20 @@ export default {
options: ["button", "reset", "submit"],
control: { type: "radio" },
},
icon_included: {
name: "Icon included",
defaultValue: false,
type: "boolean",
},
icon_name: {
name: "Icon name",
control: {
type: "select",
options: iconNames,
defaultValue: "add_circle_outline",
},
if: { arg: "icon_included" },
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! Love a conditional Storybook control. Cool find.

},
},
};

Expand All @@ -51,6 +70,13 @@ Base.args = BaseContent;
export const Big = Template.bind({});
Big.args = BigContent;

export const Icon = Template.bind({});
Icon.args = {
...DefaultContent,
icon_included: true,
icon_name: "add_circle_outline",
};

export const Outline = Template.bind({});
Outline.args = OutlineContent;

Expand Down
82 changes: 71 additions & 11 deletions packages/usa-button/src/usa-button.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,80 @@
<div class="bg-base-darkest padding-1" style="max-width: fit-content">
{% endif %}

{% set icon %}
<!-- @TODO: Allow size to be passed as a argument. -->
<!-- usa-icon--size-{3-9} -->
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
<use xlink:href="./img/sprite.svg#{{ icon_name | default("add_circle_outline") }}"></use>
</svg>
{% endset %}

{% if is_demo and 'usa-button--big' not in modifier %}
<button type="{{ type }}" class="usa-button {{ modifier }}">Default</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-button--hover">Hover</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-button--active">Active</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-focus">Focus</button>
<button type="{{ type }}" class="usa-button {{ modifier }}" disabled>Disabled</button>
<button type="{{ type }}" class="usa-button {{ modifier }}" aria-disabled="true">aria-disabled</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-button--unstyled">Unstyled button</button>
<button type="{{ type }}" class="usa-button {{ modifier }}">
Default
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-button--hover">
Hover
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-button--active">
Active
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-focus">
Focus
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
<button type="{{ type }}" class="usa-button {{ modifier }}" disabled>
Disabled
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
<button type="{{ type }}" class="usa-button {{ modifier }}" aria-disabled="true">
aria-disabled
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-button--unstyled">
Unstyled button
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
{% else %}
<button type="{{ type }}" class="usa-button {{ modifier }}">{{ text }}</button>
<button type="{{ type }}" class="usa-button {{ modifier }}">
{{ text }}
</button>
{% if is_demo and 'usa-button--big' in modifier %}
<button type="{{ type }}" class="usa-button {{ modifier }}" disabled>Disabled</button>
<button type="{{ type }}" class="usa-button {{ modifier }}" aria-disabled="true">aria-disabled</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-button--unstyled">Unstyled button</button>
<button type="{{ type }}" class="usa-button {{ modifier }}" disabled>
Disabled
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
<button type="{{ type }}" class="usa-button {{ modifier }}" aria-disabled="true">
aria-disabled
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
<button type="{{ type }}" class="usa-button {{ modifier }} usa-button--unstyled">
Unstyled button
{% if icon_included %}
{{ icon }}
{% endif %}
</button>
{% endif %}
{% endif %}

Expand Down