✨(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:
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 "utils/accessibility";
|
||||
@use "./components/Alert";
|
||||
@use "./components/Button";
|
||||
@use "./components/DataGrid";
|
||||
@use "./components/Forms/Checkbox";
|
||||
|
||||
@@ -2,6 +2,7 @@ import "./index.scss";
|
||||
import { PartialExtendableNested, PartialNested } from ":/types";
|
||||
import { tokens } from "./cunningham-tokens";
|
||||
|
||||
export * from "./components/Alert";
|
||||
export * from "./components/Button";
|
||||
export * from "./components/DataGrid";
|
||||
export * from "./components/DataGrid/DataList";
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"components": {
|
||||
"alert": {
|
||||
"close_aria_label": "Delete alert",
|
||||
"expand_aria_label": "Expand alert",
|
||||
"shrink_aria_label": "Shrink alert"
|
||||
},
|
||||
"pagination": {
|
||||
"goto_label": "Go to page",
|
||||
"goto_label_aria": "Go to any page",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
{
|
||||
"components": {
|
||||
"alert": {
|
||||
"close_aria_label": "Supprimer l'alerte",
|
||||
"expand_aria_label": "Ouvrir l'alerte",
|
||||
"shrink_aria_label": "Fermer l'alerte"
|
||||
},
|
||||
"pagination": {
|
||||
"goto_label": "Aller à",
|
||||
"goto_label_aria": "Aller à la page",
|
||||
|
||||
Reference in New Issue
Block a user