From 1cdf6bef3f78f3afa15b1ae8d8f00c19a7248fb1 Mon Sep 17 00:00:00 2001 From: Kerry Date: Thu, 2 Nov 2023 09:49:27 +1300 Subject: [PATCH] Alert: add actions input (#122) * add actions * comments * tweak comment * use container query --- src/components/Alert/Alert.module.css | 34 +++ src/components/Alert/Alert.stories.tsx | 18 ++ src/components/Alert/Alert.test.tsx | 58 ++-- src/components/Alert/Alert.tsx | 28 +- .../Alert/__snapshots__/Alert.test.tsx.snap | 272 +++++++++++++++++- 5 files changed, 378 insertions(+), 32 deletions(-) diff --git a/src/components/Alert/Alert.module.css b/src/components/Alert/Alert.module.css index 77001dee..bc6d1c5d 100644 --- a/src/components/Alert/Alert.module.css +++ b/src/components/Alert/Alert.module.css @@ -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; @@ -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) { @@ -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%; + } +} diff --git a/src/components/Alert/Alert.stories.tsx b/src/components/Alert/Alert.stories.tsx index f7be9e7f..42a3d6f1 100644 --- a/src/components/Alert/Alert.stories.tsx +++ b/src/components/Alert/Alert.stories.tsx @@ -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"; @@ -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: ( + <> + + + + ), + }, +}; + export const WithoutClose = { ...Success, args: { diff --git a/src/components/Alert/Alert.test.tsx b/src/components/Alert/Alert.test.tsx index 860edb8a..013879f3 100644 --- a/src/components/Alert/Alert.test.tsx +++ b/src/components/Alert/Alert.test.tsx @@ -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( - - Success! - , - ); + it("renders success", () => { + const Component = composeStory(Success, Meta); + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("renders critical", () => { + const Component = composeStory(Critical, Meta); + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); + + it("renders info", () => { + const Component = composeStory(Info, Meta); + const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); }); it("has no close button by default", () => { - render( - - Click me! - , - ); + const Component = composeStory(WithoutClose, Meta); + render(); + expect(screen.queryByLabelText("Close")).not.toBeInTheDocument(); }); it("can have a close button", async () => { const spy = vi.fn(); - const { container } = render( - - Click me! - , + const Component = composeStory( + { + ...Success, + args: { + ...Success.args, + onClose: spy, + }, + }, + Meta, ); + const { container } = render(); await waitFor(() => expect(getByLabelText(container, "Close")).toBeInTheDocument(), @@ -61,4 +81,10 @@ describe("Alert", () => { expect(spy).toHaveBeenCalledTimes(1); }); + + it("renders actions", () => { + const Component = composeStory(WithActions, Meta); + const { asFragment } = render(); + expect(asFragment()).toMatchSnapshot(); + }); }); diff --git a/src/components/Alert/Alert.tsx b/src/components/Alert/Alert.tsx index 65a144fd..39261532 100644 --- a/src/components/Alert/Alert.tsx +++ b/src/components/Alert/Alert.tsx @@ -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 + * ``` + * Yes} + * /> + * ``` + */ + actions?: React.ReactNode; /** * Event callback when dismissing the alert. Determines the display of the * "close" button at the top right of the alert. @@ -56,6 +68,7 @@ export const Alert: React.FC> = ({ title, children, className, + actions, onClose, ...props }: PropsWithChildren) => { @@ -84,12 +97,15 @@ export const Alert: React.FC> = ({ "aria-hidden": true, })}
- - {title} - - - {children} - +
+ + {title} + + + {children} + +
+ {actions &&
{actions}
}
{/* TODO: Setup an i18n function for the aria label below */} {onClose && ( diff --git a/src/components/Alert/__snapshots__/Alert.test.tsx.snap b/src/components/Alert/__snapshots__/Alert.test.tsx.snap index dc676b5a..02eac2e9 100644 --- a/src/components/Alert/__snapshots__/Alert.test.tsx.snap +++ b/src/components/Alert/__snapshots__/Alert.test.tsx.snap @@ -1,6 +1,234 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Alert > renders 1`] = ` +exports[`Alert > renders actions 1`] = ` + +
+ +
+
+

+ 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. +

+

+ 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. +

+
+
+ + +
+
+ +
+
+`; + +exports[`Alert > renders critical 1`] = ` + +
+ +
+
+

+ Title +

+

+ Description +

+
+
+ +
+
+`; + +exports[`Alert > renders info 1`] = ` + +
+ +
+
+

+ Title +

+

+ Description +

+
+
+ +
+
+`; + +exports[`Alert > renders success 1`] = `
renders 1`] = `
-

- Title -

-

- Success! -

+

+ Title +

+

+ Description +

+
+
`;