Skip to content

Commit

Permalink
T80804 KebabButton (#1509)
Browse files Browse the repository at this point in the history
## 📝 Changes

Please provide a brief summary of the changes made and why they were
made.

Include any notes, screenshots, or videos that may be helpful for
developers reviewing this pull request.

## ✅ 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
- [x] 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.

---------

Co-authored-by: Kevin Liu <kliu@easypost.com>
  • Loading branch information
kevinalexliu and Kevin Liu authored Dec 4, 2024
1 parent c7fd4f5 commit 6a9ea9c
Show file tree
Hide file tree
Showing 12 changed files with 132 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/quiet-ways-laugh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@easypost/easy-ui": minor
---

feat: KebabButton
10 changes: 0 additions & 10 deletions easy-ui-react/src/DataGrid/ActionsCellContent.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,3 @@
display: inline-flex;
gap: design-token("space.1.5");
}

.MenuButton {
display: inline-flex;
border-radius: design-token("shape.border_radius.sm");
}

.open {
background: design-token("color.primary.700");
color: design-token("color.neutral.000");
}
14 changes: 6 additions & 8 deletions easy-ui-react/src/DataGrid/ActionsCellContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import MoreVertIcon from "@easypost/easy-ui-icons/MoreVert";
import React, { useCallback, useState } from "react";
import { Icon } from "../Icon";
import { Menu } from "../Menu";
import { Text } from "../Text";
import { classNames } from "../utilities/css";
import { UnstyledPressButton } from "./UnstyledPressButton";
import { KebabButton } from "../KebabButton";
import { UnstyledPressButton } from "../UnstyledButton/UnstyledPressButton";
import { useDataGridRow } from "./context";
import {
ActionRowAction as ActionRowActionType,
Expand Down Expand Up @@ -45,14 +44,13 @@ function MenuRowAction({ rowAction }: { rowAction: MenuRowActionType }) {
setIsOpen(isOpen);
}, []);

const className = classNames(styles.MenuButton, isOpen && styles.open);
return (
<Menu isOpen={isOpen} onOpenChange={handleOpenChange}>
<Menu.Trigger>
<UnstyledPressButton onPress={handleClick} className={className}>
<Text visuallyHidden>{accessibilityLabel}</Text>
<Icon symbol={MoreVertIcon} />
</UnstyledPressButton>
<KebabButton
onPress={handleClick}
accessibilityLabel={accessibilityLabel}
/>
</Menu.Trigger>
{rowAction.renderMenuOverlay()}
</Menu>
Expand Down
2 changes: 1 addition & 1 deletion easy-ui-react/src/DataGrid/ExpandCellContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ArrowForwardIos from "@easypost/easy-ui-icons/ArrowForwardIos";
import React, { useCallback } from "react";
import { Icon } from "../Icon";
import { classNames } from "../utilities/css";
import { UnstyledPressButton } from "./UnstyledPressButton";
import { UnstyledPressButton } from "../UnstyledButton/UnstyledPressButton";
import { Text } from "../Text";

import styles from "./ExpandCellContent.module.scss";
Expand Down
20 changes: 20 additions & 0 deletions easy-ui-react/src/KebabButton/KebabButton.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from "react";
import { Canvas, Meta, ArgTypes } from "@storybook/blocks";
import { KebabButton } from "./KebabButton";
import * as KebabButtonStories from "./KebabButton.stories";

<Meta of={KebabButtonStories} />

# KebabButton

A `<KebabButton />` is a kebab menu icon button. The icon represents an overflow menu that reveals a list of options or actions related to the current context when clicked.

## Use with Menu

A `<KebabButton />` is designed to used as trigger for `<Menu />`.

<Canvas of={KebabButtonStories.WithMenu} />

## Properties

<ArgTypes of={KebabButtonStories} />
11 changes: 11 additions & 0 deletions easy-ui-react/src/KebabButton/KebabButton.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
@use "../styles/common" as *;

.KebabButton {
display: inline-flex;
border-radius: design-token("shape.border_radius.md");
}

.KebabButton[aria-expanded="true"] {
background-color: design-token("color.primary.700");
color: design-token("color.neutral.000");
}
38 changes: 38 additions & 0 deletions easy-ui-react/src/KebabButton/KebabButton.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { action } from "@storybook/addon-actions";
import { Meta, StoryObj } from "@storybook/react";
import React from "react";
import { OverlayLayoutDecorator } from "../utilities/storybook";
import { Menu } from "../Menu";
import { KebabButton } from "./KebabButton";

type Story = StoryObj<typeof KebabButton>;

const meta: Meta<typeof KebabButton> = {
title: "Components/Button/KebabButton",
component: KebabButton,
argTypes: {
accessibilityLabel: {
control: "text",
description: "Optional custom accessibility label describing the action.",
},
},
args: { accessibilityLabel: "Actions" },
};

export default meta;

export const WithMenu: Story = {
render: () => (
<Menu>
<Menu.Trigger>
<KebabButton onPress={action("Clicked")} />
</Menu.Trigger>
<Menu.Overlay onAction={action("Select")}>
<Menu.Item key="copy">Copy</Menu.Item>
<Menu.Item key="cut">Cut</Menu.Item>
<Menu.Item key="paste">Paste</Menu.Item>
</Menu.Overlay>
</Menu>
),
decorators: [OverlayLayoutDecorator],
};
11 changes: 11 additions & 0 deletions easy-ui-react/src/KebabButton/KebabButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react";
import { render, screen } from "@testing-library/react";
import { KebabButton } from "./KebabButton";

describe("<KebabButton />", () => {
it("should render a button with icon", () => {
render(<KebabButton />);
expect(screen.getByRole("button")).toBeInTheDocument();
expect(screen.getByRole("img", { hidden: true })).toBeInTheDocument();
});
});
39 changes: 39 additions & 0 deletions easy-ui-react/src/KebabButton/KebabButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import MoreVertIcon from "@easypost/easy-ui-icons/MoreVert";
import { AriaButtonProps } from "react-aria";
import { UnstyledPressButton } from "../UnstyledButton/UnstyledPressButton";
import { Text } from "../Text";
import { Icon } from "../Icon";
import styles from "./KebabButton.module.scss";

export type KebabButtonProps = AriaButtonProps & {
/** Optional custom accessibility label describing the action. */
accessibilityLabel?: string;
};

/**
* Typically used as a trigger to display a set of options
* for the user to choose from.
*
* @remarks
* Can be used alongside Easy UI's `<Menu />` as the trigger
* element.
*/
export const KebabButton = React.forwardRef<null, KebabButtonProps>(
(props, inRef) => {
const { accessibilityLabel = "Actions", ...restProps } = props;

return (
<UnstyledPressButton
ref={inRef}
className={styles.KebabButton}
{...restProps}
>
<Text visuallyHidden>{accessibilityLabel}</Text>
<Icon symbol={MoreVertIcon} />
</UnstyledPressButton>
);
},
);

KebabButton.displayName = "KebabButton";
1 change: 1 addition & 0 deletions easy-ui-react/src/KebabButton/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./KebabButton";

0 comments on commit 6a9ea9c

Please sign in to comment.