Skip to content

Commit

Permalink
Alert: add actions input (#122)
Browse files Browse the repository at this point in the history
* add actions

* comments

* tweak comment

* use container query
  • Loading branch information
Kerry authored Nov 1, 2023
1 parent 4edbed2 commit 1cdf6be
Show file tree
Hide file tree
Showing 5 changed files with 378 additions and 32 deletions.
34 changes: 34 additions & 0 deletions src/components/Alert/Alert.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
/* TODO: Review entire file for semantic token definiton */

.alert {
container-type: inline-size;
container-name: alert;
display: flex;
align-items: start;
justify-content: start;
Expand All @@ -43,6 +45,13 @@ limitations under the License.

.content {
flex: 1;
display: flex;
flex-direction: row;
gap: var(--cpd-space-3x);
}

.text-content {
flex: 1 1 0;
}

[data-type="success"] :is(.title, .icon) {
Expand All @@ -60,3 +69,28 @@ limitations under the License.
.alert p {
margin: 0;
}

.actions {
flex: 0;
display: flex;
flex-direction: row;
gap: var(--cpd-space-1x);
align-self: center;
}

.icon {
flex-shrink: 0;
}

/* @TODO 600px break should be a token */

/* wrap actions into a stacked layout when the alert is <=600px */
@container alert (max-width: 600px) {
.content {
flex-wrap: wrap;
}

.text-content {
flex: 1 0 100%;
}
}
18 changes: 18 additions & 0 deletions src/components/Alert/Alert.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import React from "react";
import { Meta } from "@storybook/react";
import { Button } from "../Button/Button";

import { Alert as AlertComponent } from "./Alert";

Expand Down Expand Up @@ -61,6 +63,22 @@ export const Info = {
},
};

export const WithActions = {
args: {
type: "info",
title:
"Long title. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
children:
"Actions are vertically centered against alert content. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.",
actions: (
<>
<Button>Yes</Button>
<Button>No</Button>
</>
),
},
};

export const WithoutClose = {
...Success,
args: {
Expand Down
58 changes: 42 additions & 16 deletions src/components/Alert/Alert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,35 +23,55 @@ import {
waitFor,
} from "@testing-library/react";
import React from "react";
import { composeStory } from "@storybook/react";

import { Alert } from "./Alert";
import Meta, {
Success,
Critical,
Info,
WithActions,
WithoutClose,
} from "./Alert.stories";

describe("Alert", () => {
it("renders", () => {
const { asFragment } = render(
<Alert title="Title" type="success">
Success!
</Alert>,
);
it("renders success", () => {
const Component = composeStory(Success, Meta);
const { asFragment } = render(<Component />);
expect(asFragment()).toMatchSnapshot();
});

it("renders critical", () => {
const Component = composeStory(Critical, Meta);
const { asFragment } = render(<Component />);
expect(asFragment()).toMatchSnapshot();
});

it("renders info", () => {
const Component = composeStory(Info, Meta);
const { asFragment } = render(<Component />);
expect(asFragment()).toMatchSnapshot();
});

it("has no close button by default", () => {
render(
<Alert title="Title" type="info">
Click me!
</Alert>,
);
const Component = composeStory(WithoutClose, Meta);
render(<Component />);

expect(screen.queryByLabelText("Close")).not.toBeInTheDocument();
});

it("can have a close button", async () => {
const spy = vi.fn();
const { container } = render(
<Alert title="Title" type="critical" onClose={spy}>
Click me!
</Alert>,
const Component = composeStory(
{
...Success,
args: {
...Success.args,
onClose: spy,
},
},
Meta,
);
const { container } = render(<Component />);

await waitFor(() =>
expect(getByLabelText(container, "Close")).toBeInTheDocument(),
Expand All @@ -61,4 +81,10 @@ describe("Alert", () => {

expect(spy).toHaveBeenCalledTimes(1);
});

it("renders actions", () => {
const Component = composeStory(WithActions, Meta);
const { asFragment } = render(<Component />);
expect(asFragment()).toMatchSnapshot();
});
});
28 changes: 22 additions & 6 deletions src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ type AlertProps = {
* The CSS class name.
*/
className?: string;
/**
* Actions that will be displayed to the right of the content
* Wraps and stacks actions under content when alert's size is <=600px
* eg
* ```
* <Alert
* title='Title'
* actions={<Button onClick={doSomething}>Yes</Button>}
* />
* ```
*/
actions?: React.ReactNode;
/**
* Event callback when dismissing the alert. Determines the display of the
* "close" button at the top right of the alert.
Expand All @@ -56,6 +68,7 @@ export const Alert: React.FC<PropsWithChildren<AlertProps>> = ({
title,
children,
className,
actions,
onClose,
...props
}: PropsWithChildren<AlertProps>) => {
Expand Down Expand Up @@ -84,12 +97,15 @@ export const Alert: React.FC<PropsWithChildren<AlertProps>> = ({
"aria-hidden": true,
})}
<div className={styles.content}>
<Text size="md" weight="semibold">
{title}
</Text>
<Text size="sm" weight="regular">
{children}
</Text>
<div className={styles["text-content"]}>
<Text size="md" weight="semibold">
{title}
</Text>
<Text size="sm" weight="regular">
{children}
</Text>
</div>
{actions && <div className={styles.actions}>{actions}</div>}
</div>
{/* TODO: Setup an i18n function for the aria label below */}
{onClose && (
Expand Down
Loading

0 comments on commit 1cdf6be

Please sign in to comment.