✨(react) add Alert
Here is the Alert component based on recent delivered sketches. There is a main component that based on props renders sub alert components.
This commit is contained in:
5
.changeset/spotty-hotels-study.md
Normal file
5
.changeset/spotty-hotels-study.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@openfun/cunningham-react": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
add Alert
|
||||||
26
packages/react/src/components/Alert/AlertAdditional.tsx
Normal file
26
packages/react/src/components/Alert/AlertAdditional.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { AlertProps } from ":/components/Alert/index";
|
||||||
|
import {
|
||||||
|
AlertButtons,
|
||||||
|
AlertClose,
|
||||||
|
AlertIcon,
|
||||||
|
AlertWrapper,
|
||||||
|
} from ":/components/Alert/Utils";
|
||||||
|
|
||||||
|
export const AlertAdditional = (props: AlertProps) => {
|
||||||
|
return (
|
||||||
|
<AlertWrapper {...props}>
|
||||||
|
<div className="c__alert__content">
|
||||||
|
<div className="c__alert__content__left">
|
||||||
|
{props.children}
|
||||||
|
<AlertIcon {...props} />
|
||||||
|
</div>
|
||||||
|
<AlertClose {...props} />
|
||||||
|
</div>
|
||||||
|
<div className="c__alert__additional">{props.additional}</div>
|
||||||
|
<div className="c__alert-additional__buttons">
|
||||||
|
<AlertButtons {...props} />
|
||||||
|
</div>
|
||||||
|
</AlertWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { useControllableState } from ":/hooks/useControllableState";
|
||||||
|
import { Button } from ":/components/Button";
|
||||||
|
import { AlertProps } from ":/components/Alert/index";
|
||||||
|
import { AlertAdditional } from ":/components/Alert/AlertAdditional";
|
||||||
|
import { AlertOneLine } from ":/components/Alert/AlertOneLine";
|
||||||
|
import { useCunningham } from ":/components/Provider";
|
||||||
|
|
||||||
|
export const AlertAdditionalExpandable = (props: AlertProps) => {
|
||||||
|
const { t } = useCunningham();
|
||||||
|
const [expanded, onExpand] = useControllableState(
|
||||||
|
false,
|
||||||
|
props.expanded,
|
||||||
|
props.onExpand,
|
||||||
|
);
|
||||||
|
|
||||||
|
const iconButton = (
|
||||||
|
<Button
|
||||||
|
color="tertiary"
|
||||||
|
size="nano"
|
||||||
|
aria-label={
|
||||||
|
expanded
|
||||||
|
? t("components.alert.shrink_aria_label")
|
||||||
|
: t("components.alert.expand_aria_label")
|
||||||
|
}
|
||||||
|
icon={
|
||||||
|
<span className="material-icons">{expanded ? "remove" : "add"}</span>
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
onExpand(!expanded);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const customProps = {
|
||||||
|
...props,
|
||||||
|
icon: iconButton,
|
||||||
|
className: "c__alert--expandable",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (expanded) {
|
||||||
|
return <AlertAdditional {...customProps} />;
|
||||||
|
}
|
||||||
|
return <AlertOneLine {...customProps} />;
|
||||||
|
};
|
||||||
32
packages/react/src/components/Alert/AlertOneLine.tsx
Normal file
32
packages/react/src/components/Alert/AlertOneLine.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { AlertProps } from ":/components/Alert/index";
|
||||||
|
import {
|
||||||
|
AlertButtons,
|
||||||
|
AlertClose,
|
||||||
|
AlertIcon,
|
||||||
|
AlertWrapper,
|
||||||
|
} from ":/components/Alert/Utils";
|
||||||
|
|
||||||
|
export const AlertOneLine = (props: AlertProps) => {
|
||||||
|
const hasActions =
|
||||||
|
props.canClose ||
|
||||||
|
props.primaryLabel ||
|
||||||
|
props.tertiaryLabel ||
|
||||||
|
props.buttons;
|
||||||
|
return (
|
||||||
|
<AlertWrapper {...props}>
|
||||||
|
<div className="c__alert__content">
|
||||||
|
<div className="c__alert__content__left">
|
||||||
|
{props.children}
|
||||||
|
<AlertIcon {...props} />
|
||||||
|
</div>
|
||||||
|
{hasActions && (
|
||||||
|
<div className="c__alert__actions">
|
||||||
|
<AlertButtons {...props} />
|
||||||
|
<AlertClose {...props} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AlertWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
94
packages/react/src/components/Alert/Utils.tsx
Normal file
94
packages/react/src/components/Alert/Utils.tsx
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import React, { useMemo } from "react";
|
||||||
|
import classNames from "classnames";
|
||||||
|
import { Button } from ":/components/Button";
|
||||||
|
import { AlertProps, AlertType } from ":/components/Alert/index";
|
||||||
|
import { useCunningham } from ":/components/Provider";
|
||||||
|
|
||||||
|
export const AlertWrapper = (props: AlertProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
"c__alert",
|
||||||
|
"c__alert--" + props.type,
|
||||||
|
props.className,
|
||||||
|
{
|
||||||
|
"c__alert--hide": props.hide,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AlertIcon = ({ type, ...props }: AlertProps) => {
|
||||||
|
const icon = useMemo(() => {
|
||||||
|
switch (type) {
|
||||||
|
case AlertType.INFO:
|
||||||
|
return "info";
|
||||||
|
case AlertType.SUCCESS:
|
||||||
|
return "task_alt";
|
||||||
|
case AlertType.WARNING:
|
||||||
|
return "warning";
|
||||||
|
case AlertType.ERROR:
|
||||||
|
return "cancel";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}, [type]);
|
||||||
|
if (props.icon) {
|
||||||
|
return props.icon;
|
||||||
|
}
|
||||||
|
if (!icon) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="c__alert__icon">
|
||||||
|
<span className="material-icons">{icon}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AlertClose = (props: AlertProps) => {
|
||||||
|
const { t } = useCunningham();
|
||||||
|
return (
|
||||||
|
props.canClose && (
|
||||||
|
<Button
|
||||||
|
className="ml-st"
|
||||||
|
color="tertiary"
|
||||||
|
size="small"
|
||||||
|
icon={<span className="material-icons">close</span>}
|
||||||
|
aria-label={t("components.alert.close_aria_label")}
|
||||||
|
onClick={() => {
|
||||||
|
props.onClose?.(true);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AlertButtons = (props: AlertProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{props.tertiaryLabel && (
|
||||||
|
<Button
|
||||||
|
color="tertiary-text"
|
||||||
|
onClick={props.tertiaryOnClick}
|
||||||
|
{...props.tertiaryProps}
|
||||||
|
>
|
||||||
|
{props.tertiaryLabel}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{props.primaryLabel && (
|
||||||
|
<Button
|
||||||
|
color="primary-text"
|
||||||
|
onClick={props.primaryOnClick}
|
||||||
|
{...props.primaryProps}
|
||||||
|
>
|
||||||
|
{props.primaryLabel}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{props.buttons}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
102
packages/react/src/components/Alert/_index.scss
Normal file
102
packages/react/src/components/Alert/_index.scss
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
.c__alert {
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background-color: var(--c--components--alert--background-color);
|
||||||
|
border-radius: var(--c--components--alert--border-radius);
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 1px;
|
||||||
|
border-left-width: 3px;
|
||||||
|
font-weight: var(--c--components--alert--font-weight);
|
||||||
|
color: var(--c--components--alert--color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-color: var(--c--theme--colors--greyscale-300);
|
||||||
|
border-left-color: var(--c--theme--colors--greyscale-600);
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: var(--c--components--alert--icon-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
// We want to maintain the same height as when a button is present.
|
||||||
|
min-height: 2.25rem;
|
||||||
|
|
||||||
|
&__left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
// For accessibility reasons, we want to make sure that the text is the first thing that is read.
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-additional {
|
||||||
|
|
||||||
|
&__buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.c__alert__additional {
|
||||||
|
font-weight: var(--c--components--alert--additional-font-weight);
|
||||||
|
color: var(--c--components--alert--additional-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(.c__alert--neutral), &.c__alert--expandable {
|
||||||
|
.c__alert__additional {
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--info {
|
||||||
|
border-color: var(--c--theme--colors--primary-300);
|
||||||
|
border-left-color: var(--c--theme--colors--primary-600);
|
||||||
|
|
||||||
|
.c__alert__icon {
|
||||||
|
color: var(--c--theme--colors--primary-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--success {
|
||||||
|
border-color: var(--c--theme--colors--success-300);
|
||||||
|
border-left-color: var(--c--theme--colors--success-600);
|
||||||
|
|
||||||
|
.c__alert__icon {
|
||||||
|
color: var(--c--theme--colors--success-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--warning {
|
||||||
|
border-color: var(--c--theme--colors--warning-300);
|
||||||
|
border-left-color: var(--c--theme--colors--warning-600);
|
||||||
|
|
||||||
|
.c__alert__icon {
|
||||||
|
color: var(--c--theme--colors--warning-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--error {
|
||||||
|
border-color: var(--c--theme--colors--danger-300);
|
||||||
|
border-left-color: var(--c--theme--colors--danger-600);
|
||||||
|
|
||||||
|
.c__alert__icon {
|
||||||
|
color: var(--c--theme--colors--danger-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
251
packages/react/src/components/Alert/index.spec.tsx
Normal file
251
packages/react/src/components/Alert/index.spec.tsx
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
import { render, screen } from "@testing-library/react";
|
||||||
|
import React from "react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { Alert, AlertType } from ":/components/Alert/index";
|
||||||
|
import { Button } from ":/components/Button";
|
||||||
|
import { CunninghamProvider } from ":/components/Provider";
|
||||||
|
|
||||||
|
describe("<Alert/>", () => {
|
||||||
|
it.each([
|
||||||
|
[AlertType.INFO, "info"],
|
||||||
|
[AlertType.SUCCESS, "task_alt"],
|
||||||
|
[AlertType.WARNING, "warning"],
|
||||||
|
[AlertType.ERROR, "cancel"],
|
||||||
|
[AlertType.NEUTRAL, undefined],
|
||||||
|
])("renders % alert with according icon", (type, icon) => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert type={type}>Alert component</Alert>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
const $icon = document.querySelector(".c__alert__icon");
|
||||||
|
if (icon) {
|
||||||
|
expect($icon).toHaveTextContent(icon!);
|
||||||
|
} else {
|
||||||
|
expect($icon).toBeNull();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
it("renders additional information", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert type={AlertType.INFO} additional="Additional information">
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
expect(screen.getByText("Additional information")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("renders primary button when primaryLabel is provided", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert type={AlertType.INFO} primaryLabel="Primary">
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
screen.getByRole("button", { name: "Primary" });
|
||||||
|
});
|
||||||
|
it("renders tertiary button when tertiaryLabel is provided", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert type={AlertType.INFO} tertiaryLabel="Tertiary">
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
screen.getByRole("button", { name: "Tertiary" });
|
||||||
|
});
|
||||||
|
it("renders both buttons labels are provided", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert
|
||||||
|
type={AlertType.INFO}
|
||||||
|
primaryLabel="Primary"
|
||||||
|
tertiaryLabel="Tertiary"
|
||||||
|
>
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
screen.getByRole("button", { name: "Primary" });
|
||||||
|
screen.getByRole("button", { name: "Tertiary" });
|
||||||
|
});
|
||||||
|
it("renders custom buttons via buttons props", () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert
|
||||||
|
type={AlertType.INFO}
|
||||||
|
buttons={
|
||||||
|
<>
|
||||||
|
<Button color="primary">Primary Custom</Button>
|
||||||
|
<Button color="secondary">Secondary Custom</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
screen.getByRole("button", { name: "Primary Custom" });
|
||||||
|
screen.getByRole("button", { name: "Secondary Custom" });
|
||||||
|
});
|
||||||
|
it("can close the alert non controlled", async () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert type={AlertType.INFO} canClose={true}>
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
screen.getByText("Alert component");
|
||||||
|
expect(document.querySelector(".c__alert")).toBeInTheDocument();
|
||||||
|
|
||||||
|
const $close = screen.getByRole("button", { name: "Delete alert" });
|
||||||
|
await userEvent.click($close);
|
||||||
|
|
||||||
|
expect(screen.queryByText("Alert component")).not.toBeInTheDocument();
|
||||||
|
expect(document.querySelector(".c__alert")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("can close the alert controlled", async () => {
|
||||||
|
const Wrapper = () => {
|
||||||
|
const [closed, setClosed] = React.useState(false);
|
||||||
|
return (
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert
|
||||||
|
type={AlertType.INFO}
|
||||||
|
canClose={true}
|
||||||
|
closed={closed}
|
||||||
|
onClose={(flag) => setClosed(flag)}
|
||||||
|
>
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
<div>Closed: {closed ? "true" : "false"}</div>
|
||||||
|
<Button onClick={() => setClosed(true)}>Close</Button>
|
||||||
|
<Button onClick={() => setClosed(false)}>Open</Button>
|
||||||
|
</CunninghamProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(<Wrapper />);
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
screen.getByText("Closed: false");
|
||||||
|
|
||||||
|
expect(document.querySelector(".c__alert")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Close from button.
|
||||||
|
const $closeButton = screen.getByRole("button", { name: "Close" });
|
||||||
|
await user.click($closeButton);
|
||||||
|
|
||||||
|
expect(document.querySelector(".c__alert")).not.toBeInTheDocument();
|
||||||
|
screen.getByText("Closed: true");
|
||||||
|
|
||||||
|
// Open from button.
|
||||||
|
const $openButton = screen.getByRole("button", { name: "Open" });
|
||||||
|
await user.click($openButton);
|
||||||
|
|
||||||
|
expect(document.querySelector(".c__alert")).toBeInTheDocument();
|
||||||
|
screen.getByText("Closed: false");
|
||||||
|
|
||||||
|
// Close from alert.
|
||||||
|
const $close = screen.getByRole("button", { name: "Delete alert" });
|
||||||
|
await userEvent.click($close);
|
||||||
|
|
||||||
|
screen.getByText("Closed: true");
|
||||||
|
});
|
||||||
|
it("can expand the alert non controlled", async () => {
|
||||||
|
render(
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert
|
||||||
|
type={AlertType.INFO}
|
||||||
|
additional="Additional information"
|
||||||
|
expandable={true}
|
||||||
|
>
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
</CunninghamProvider>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
screen.getByText("Alert component");
|
||||||
|
expect(
|
||||||
|
screen.queryByText("Additional information"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
const $expandButton = screen.getByRole("button", { name: "Expand alert" });
|
||||||
|
await user.click($expandButton);
|
||||||
|
|
||||||
|
expect(screen.queryByText("Additional information")).toBeInTheDocument();
|
||||||
|
|
||||||
|
const $shrinkButton = screen.getByRole("button", { name: "Shrink alert" });
|
||||||
|
await user.click($shrinkButton);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByText("Additional information"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
it("can expand the alert controlled", async () => {
|
||||||
|
const Wrapper = () => {
|
||||||
|
const [expanded, setExpanded] = React.useState(false);
|
||||||
|
return (
|
||||||
|
<CunninghamProvider>
|
||||||
|
<Alert
|
||||||
|
type={AlertType.INFO}
|
||||||
|
additional="Additional information"
|
||||||
|
expandable={true}
|
||||||
|
expanded={expanded}
|
||||||
|
onExpand={(flag) => setExpanded(flag)}
|
||||||
|
>
|
||||||
|
Alert component
|
||||||
|
</Alert>
|
||||||
|
<div>Expanded: {expanded ? "true" : "false"}</div>
|
||||||
|
<Button onClick={() => setExpanded(true)}>Expand</Button>
|
||||||
|
<Button onClick={() => setExpanded(false)}>Shrink</Button>
|
||||||
|
</CunninghamProvider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
render(<Wrapper />);
|
||||||
|
|
||||||
|
const user = userEvent.setup();
|
||||||
|
|
||||||
|
screen.getByText("Expanded: false");
|
||||||
|
|
||||||
|
// Expand from button.
|
||||||
|
const $expandButton = screen.getByRole("button", { name: "Expand" });
|
||||||
|
await user.click($expandButton);
|
||||||
|
|
||||||
|
screen.getByText("Expanded: true");
|
||||||
|
expect(screen.queryByText("Additional information")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Shrink from button.
|
||||||
|
const $shrinkButton = screen.getByRole("button", { name: "Shrink" });
|
||||||
|
await user.click($shrinkButton);
|
||||||
|
|
||||||
|
screen.getByText("Expanded: false");
|
||||||
|
expect(
|
||||||
|
screen.queryByText("Additional information"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
// Expand from alert.
|
||||||
|
const $expandAlertButton = screen.getByRole("button", {
|
||||||
|
name: "Expand alert",
|
||||||
|
});
|
||||||
|
await user.click($expandAlertButton);
|
||||||
|
|
||||||
|
screen.getByText("Expanded: true");
|
||||||
|
expect(screen.queryByText("Additional information")).toBeInTheDocument();
|
||||||
|
|
||||||
|
// Expand from alert.
|
||||||
|
const $shrinkAlertButton = screen.getByRole("button", {
|
||||||
|
name: "Shrink alert",
|
||||||
|
});
|
||||||
|
await user.click($shrinkAlertButton);
|
||||||
|
|
||||||
|
screen.getByText("Expanded: false");
|
||||||
|
expect(
|
||||||
|
screen.queryByText("Additional information"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
60
packages/react/src/components/Alert/index.tsx
Normal file
60
packages/react/src/components/Alert/index.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import React, { PropsWithChildren, ReactNode } from "react";
|
||||||
|
import { ButtonProps } from ":/components/Button";
|
||||||
|
import { useControllableState } from ":/hooks/useControllableState";
|
||||||
|
import { AlertAdditionalExpandable } from ":/components/Alert/AlertAdditionalExpandable";
|
||||||
|
import { AlertAdditional } from ":/components/Alert/AlertAdditional";
|
||||||
|
import { AlertOneLine } from ":/components/Alert/AlertOneLine";
|
||||||
|
|
||||||
|
export enum AlertType {
|
||||||
|
INFO = "info",
|
||||||
|
SUCCESS = "success",
|
||||||
|
WARNING = "warning",
|
||||||
|
ERROR = "error",
|
||||||
|
NEUTRAL = "neutral",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AlertProps extends PropsWithChildren {
|
||||||
|
additional?: React.ReactNode;
|
||||||
|
buttons?: React.ReactNode;
|
||||||
|
canClose?: boolean;
|
||||||
|
className?: string;
|
||||||
|
closed?: boolean;
|
||||||
|
expandable?: boolean;
|
||||||
|
expanded?: boolean;
|
||||||
|
hide?: boolean;
|
||||||
|
icon?: ReactNode;
|
||||||
|
onClose?: (value: boolean) => void;
|
||||||
|
onExpand?: (value: boolean) => void;
|
||||||
|
primaryLabel?: string;
|
||||||
|
primaryOnClick?: ButtonProps["onClick"];
|
||||||
|
primaryProps?: ButtonProps;
|
||||||
|
tertiaryLabel?: string;
|
||||||
|
tertiaryOnClick?: ButtonProps["onClick"];
|
||||||
|
tertiaryProps?: ButtonProps;
|
||||||
|
type?: AlertType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Alert = (props: AlertProps) => {
|
||||||
|
const [closed, onClose] = useControllableState(
|
||||||
|
false,
|
||||||
|
props.closed,
|
||||||
|
props.onClose,
|
||||||
|
);
|
||||||
|
|
||||||
|
const propsWithDefault = {
|
||||||
|
type: AlertType.INFO,
|
||||||
|
...props,
|
||||||
|
onClose,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (closed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (props.additional) {
|
||||||
|
if (props.expandable) {
|
||||||
|
return <AlertAdditionalExpandable {...propsWithDefault} />;
|
||||||
|
}
|
||||||
|
return <AlertAdditional {...propsWithDefault} />;
|
||||||
|
}
|
||||||
|
return <AlertOneLine {...propsWithDefault} />;
|
||||||
|
};
|
||||||
13
packages/react/src/components/Alert/tokens.ts
Normal file
13
packages/react/src/components/Alert/tokens.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { DefaultTokens } from "@openfun/cunningham-tokens";
|
||||||
|
|
||||||
|
export const tokens = (defaults: DefaultTokens) => {
|
||||||
|
return {
|
||||||
|
"background-color": defaults.theme.colors["greyscale-100"],
|
||||||
|
"border-radius": "4px",
|
||||||
|
"font-weight": defaults.theme.font.weights.medium,
|
||||||
|
color: defaults.theme.colors["greyscale-900"],
|
||||||
|
"icon-size": defaults.theme.font.sizes.l,
|
||||||
|
"additional-font-weight": defaults.theme.font.weights.regular,
|
||||||
|
"additional-color": defaults.theme.colors["greyscale-700"],
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
@use "@openfun/cunningham-tokens/default-tokens";
|
@use "@openfun/cunningham-tokens/default-tokens";
|
||||||
|
|
||||||
@use "utils/accessibility";
|
@use "utils/accessibility";
|
||||||
|
@use "./components/Alert";
|
||||||
@use "./components/Button";
|
@use "./components/Button";
|
||||||
@use "./components/DataGrid";
|
@use "./components/DataGrid";
|
||||||
@use "./components/Forms/Checkbox";
|
@use "./components/Forms/Checkbox";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import "./index.scss";
|
|||||||
import { PartialExtendableNested, PartialNested } from ":/types";
|
import { PartialExtendableNested, PartialNested } from ":/types";
|
||||||
import { tokens } from "./cunningham-tokens";
|
import { tokens } from "./cunningham-tokens";
|
||||||
|
|
||||||
|
export * from "./components/Alert";
|
||||||
export * from "./components/Button";
|
export * from "./components/Button";
|
||||||
export * from "./components/DataGrid";
|
export * from "./components/DataGrid";
|
||||||
export * from "./components/DataGrid/DataList";
|
export * from "./components/DataGrid/DataList";
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"components": {
|
"components": {
|
||||||
|
"alert": {
|
||||||
|
"close_aria_label": "Delete alert",
|
||||||
|
"expand_aria_label": "Expand alert",
|
||||||
|
"shrink_aria_label": "Shrink alert"
|
||||||
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"goto_label": "Go to page",
|
"goto_label": "Go to page",
|
||||||
"goto_label_aria": "Go to any page",
|
"goto_label_aria": "Go to any page",
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"components": {
|
"components": {
|
||||||
|
"alert": {
|
||||||
|
"close_aria_label": "Supprimer l'alerte",
|
||||||
|
"expand_aria_label": "Ouvrir l'alerte",
|
||||||
|
"shrink_aria_label": "Fermer l'alerte"
|
||||||
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"goto_label": "Aller à",
|
"goto_label": "Aller à",
|
||||||
"goto_label_aria": "Aller à la page",
|
"goto_label_aria": "Aller à la page",
|
||||||
|
|||||||
Reference in New Issue
Block a user