Skip to content

Commit

Permalink
feat(ForgeLayout): add button action (#1548)
Browse files Browse the repository at this point in the history
## πŸ“ Changes

- Adds a button type for the actions list

## βœ… Checklist

Easy UI has certain UX standards that must be met. In general,
non-trivial changes should meet the following criteria:

- [x] Visuals match Design Specs in Figma
- [x] Stories accompany any component changes
- [x] Code is in accordance with our style guide
- [x] Design tokens are utilized
- [x] Unit tests accompany any component changes
- [ ] ~TSDoc is written for any API surface area~
- [ ] ~Specs are up-to-date
- [x] Console is free from warnings
- [x] No accessibility violations are reported
- [x] Cross-browser check is performed (Chrome, Safari, Firefox)
- [x] Changeset is added

~Strikethrough~ any items that are not applicable to this pull request.
  • Loading branch information
stephenjwatkins authored Dec 19, 2024
1 parent 9c352b6 commit 5e76576
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/long-bananas-push.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@easypost/easy-ui": minor
---

feat(ForgeLayout): add button action
4 changes: 2 additions & 2 deletions easy-ui-react/src/ForgeLayout/ForgeLayout.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@ const Template = (args: Partial<ForgeLayoutProps>) => {
accessibilityLabel="Action 3"
iconSymbol={AccountCircleIcon}
/>
<ForgeLayout.LinkAction
href="/5"
<ForgeLayout.ButtonAction
accessibilityLabel="Action 4"
iconSymbol={DoorOpenIcon}
onPress={action("Button clicked!")}
/>
</ForgeLayout.Actions>
</ForgeLayout.Header>
Expand Down
5 changes: 5 additions & 0 deletions easy-ui-react/src/ForgeLayout/ForgeLayout.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ function createForgeLayout(
accessibilityLabel="Action 3"
iconSymbol={Icon}
/>
<ForgeLayout.ButtonAction
accessibilityLabel="Action 4"
iconSymbol={Icon}
onPress={() => {}}
/>
</ForgeLayout.Actions>
</ForgeLayout.Header>
<ForgeLayout.Content>{content}</ForgeLayout.Content>
Expand Down
6 changes: 6 additions & 0 deletions easy-ui-react/src/ForgeLayout/ForgeLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { classNames, variationName } from "../utilities/css";
import {
ForgeLayoutActionBadge,
ForgeLayoutActions,
ForgeLayoutButtonAction,
ForgeLayoutLinkAction,
ForgeLayoutMenuAction,
} from "./ForgeLayoutActions";
Expand Down Expand Up @@ -262,6 +263,11 @@ ForgeLayout.MenuAction = ForgeLayoutMenuAction;
*/
ForgeLayout.LinkAction = ForgeLayoutLinkAction;

/**
* Represents a secondary button action of a `<ForgeLayout />`.
*/
ForgeLayout.ButtonAction = ForgeLayoutButtonAction;

/**
* Represents the main content of a `<ForgeLayout />`.
*/
Expand Down
6 changes: 4 additions & 2 deletions easy-ui-react/src/ForgeLayout/ForgeLayoutActions.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@
outline: none;
}

.focused {
.focused,
[data-focus-visible="true"] {
@include native-focus-ring;
}

.hovered {
.hovered,
[data-hovered="true"] {
background-color: design-token("color.neutral.050");
}

Expand Down
43 changes: 43 additions & 0 deletions easy-ui-react/src/ForgeLayout/ForgeLayoutActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
useLink,
usePress,
} from "react-aria";
import { Button } from "react-aria-components";
import { ButtonProps } from "../Button";
import { HorizontalStack } from "../HorizontalStack";
import { Icon } from "../Icon";
import { Menu } from "../Menu";
Expand Down Expand Up @@ -62,6 +64,21 @@ export type ForgeLayoutLinkActionProps = {
renderBadge?: () => ReactNode;
} & AriaLinkOptions;

export type ForgeLayoutButtonActionProps = {
/** Optional custom accessibility label describing the menu action. */
accessibilityLabel?: string;

/** Action button icon symbol. */
iconSymbol: IconSymbol;

/** Whether or not action button is selected. */
isSelected?: boolean;

/** Badge for the action. */
renderBadge?: () => ReactNode;
} & ButtonProps &
React.RefAttributes<HTMLButtonElement>;

export function ForgeLayoutActions(props: ForgeLayoutActionsProps) {
const { children } = props;
return (
Expand Down Expand Up @@ -142,6 +159,32 @@ export function ForgeLayoutLinkAction(props: ForgeLayoutLinkActionProps) {
);
}

export function ForgeLayoutButtonAction(props: ForgeLayoutButtonActionProps) {
const {
accessibilityLabel = "Actions",
iconSymbol,
renderBadge,
isSelected,
...buttonProps
} = props;
const ref = useRef(null);
const className = classNames(styles.button, isSelected && styles.selected);
return (
<Button
ref={ref}
className={className}
aria-current={isSelected ? "true" : undefined}
{...buttonProps}
>
<Text visuallyHidden>{accessibilityLabel}</Text>
<Icon symbol={iconSymbol} />
{renderBadge && (
<div className={styles.badgeContainer}>{renderBadge()}</div>
)}
</Button>
);
}

export function ForgeLayoutActionBadge(props: ForgeLayoutActionBadgeProps) {
const { children } = props;
return <div className={styles.badge}>{children}</div>;
Expand Down

0 comments on commit 5e76576

Please sign in to comment.