Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/en/components/alert.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { GlButton } from "gitlab-ui-react/button";
<GlAlert dismissible={false}>
<GlAlertDescription>Your preferences were updated.</GlAlertDescription>
<GlAlertActions>
<GlButton>Review changes</GlButton>
<GlButton size="small">Review changes</GlButton>
</GlAlertActions>
</GlAlert>
```
Expand Down Expand Up @@ -51,7 +51,7 @@ Choose a variant for the message's meaning, not merely its visual emphasis. Use

## Titles and actions

Add a short title when it helps users identify what triggered the alert. Put up to two explicit recovery or continuation controls in `GlAlertActions`; use a confirm primary action and a default secondary action.
Add a short title when it helps users identify what triggered the alert. Put up to two explicit recovery or continuation controls in `GlAlertActions`; use small buttons with a confirm primary action and a default secondary action. If a wrapper component renders `GlAlertActions`, set `hasActions` on `GlAlert` so the alert preserves the action spacing.

<DocsExample
filename="alert/AlertActionsExample.tsx"
Expand All @@ -78,6 +78,7 @@ These are the component-specific props. `GlAlert`, `GlAlertDescription`, and `Gl
| `variant` | Sets the semantic style to `danger`, `warning`, `success`, `info`, or `tip`. | `"info"` |
| `title` | Adds a short heading before the alert content. | — |
| `headerLevel` | Sets the title heading level from `1` through `6`. | `2` |
| `hasActions` | Overrides automatic action detection. Set it when a wrapper component renders `GlAlertActions`. | — |
| `dismissible` | Shows the dismiss button; visibility remains controlled by the parent. | `true` |
| `dismissLabel` | Sets the dismiss button's accessible label. | `"Dismiss"` |
| `onDismiss` | Runs when the dismiss button is activated. | — |
Expand Down
5 changes: 3 additions & 2 deletions docs/zh/components/alert.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { GlButton } from "gitlab-ui-react/button";
<GlAlert dismissible={false}>
<GlAlertDescription>Your preferences were updated.</GlAlertDescription>
<GlAlertActions>
<GlButton>Review changes</GlButton>
<GlButton size="small">Review changes</GlButton>
</GlAlertActions>
</GlAlert>
```
Expand Down Expand Up @@ -51,7 +51,7 @@ import { GlButton } from "gitlab-ui-react/button";

## 标题与操作

当标题有助于用户识别触发警报的事件时,可以添加一个简短标题。在 `GlAlertActions` 中最多放置两个明确的恢复或继续操作;主要操作使用 confirm 变体,次要操作使用 default 变体。
当标题有助于用户识别触发警报的事件时,可以添加一个简短标题。在 `GlAlertActions` 中最多放置两个明确的恢复或继续操作;使用小尺寸按钮,主要操作使用 confirm 变体,次要操作使用 default 变体。如果由包装组件渲染 `GlAlertActions`,请在 `GlAlert` 上设置 `hasActions`,以保留操作区域的间距。

<DocsExample
filename="alert/AlertActionsExample.tsx"
Expand All @@ -78,6 +78,7 @@ import { GlButton } from "gitlab-ui-react/button";
| `variant` | 将语义样式设置为 `danger`、`warning`、`success`、`info` 或 `tip`。 | `"info"` |
| `title` | 在警报内容之前添加简短标题。 | — |
| `headerLevel` | 将标题层级设置为 `1` 到 `6`。 | `2` |
| `hasActions` | 覆盖自动操作检测。当包装组件内部渲染 `GlAlertActions` 时设置此属性。 | — |
| `dismissible` | 显示关闭按钮;可见性仍由父组件控制。 | `true` |
| `dismissLabel` | 设置关闭按钮的无障碍标签。 | `"Dismiss"` |
| `onDismiss` | 关闭按钮被激活时调用。 | — |
Expand Down
8 changes: 6 additions & 2 deletions examples/alert/AlertActionsExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ export default function AlertActionsExample() {
Check the pipeline configuration, then try again.
</GlAlertDescription>
<GlAlertActions>
<GlButton variant="confirm">Retry</GlButton>
<GlButton category="secondary">View details</GlButton>
<GlButton size="small" variant="confirm">
Retry
</GlButton>
<GlButton category="secondary" size="small">
View details
</GlButton>
</GlAlertActions>
</GlAlert>
);
Expand Down
40 changes: 19 additions & 21 deletions packages/styles/dist/gitlab-ui.css
Original file line number Diff line number Diff line change
Expand Up @@ -4441,43 +4441,44 @@
}
}
.gl-alert {
padding-block: var(--gl-spacing-scale-5);
padding-block: var(--gl-spacing-scale-3);
font-size: var(--gl-font-size-base);
border-radius: var(--gl-alert-border-radius);
padding-inline: calc(3rem + var(--gl-alert-padding-x, 0px));
padding-inline: calc(2rem + var(--gl-alert-padding-x, 0px));
position: relative;
border: 1px solid;
}
.gl-alert-not-dismissible {
padding-right: calc(1rem + var(--gl-alert-padding-x, 0px));
padding-right: calc(0.75rem + var(--gl-alert-padding-x, 0px));
}
.gl-alert .gl-link:not(.gl-label-link) {
text-decoration-line: underline;
}
.gl-alert.gl-alert-has-actions {
padding-bottom: var(--gl-spacing-scale-4);
}
.gl-alert-sticky {
position: sticky;
top: 0;
}
.gl-alert-title {
font-weight: var(--gl-heading-scale-400-font-weight);
margin-top: var(--gl-heading-scale-400-margin-top);
font-size: var(--gl-heading-scale-400-font-size);
letter-spacing: var(--gl-heading-scale-400-letter-spacing);
line-height: var(--gl-heading-scale-400-line-height);
color: var(--gl-heading-scale-400-color);
margin-top: calc(var(--gl-spacing-scale-1) * -1);
font-weight: var(--gl-heading-scale-300-font-weight);
margin-top: var(--gl-heading-scale-300-margin-top);
font-size: var(--gl-heading-scale-300-font-size);
letter-spacing: var(--gl-heading-scale-300-letter-spacing);
line-height: var(--gl-heading-scale-300-line-height);
color: var(--gl-heading-scale-300-color);
margin-bottom: var(--gl-spacing-scale-2);
--tw-leading: var(--gl-line-height-20);
line-height: var(--gl-line-height-20);
}
.gl-alert-icon-container {
position: absolute;
top: 1rem;
left: calc(1rem + var(--gl-alert-padding-x, 0px));
top: var(--gl-spacing-scale-3);
left: calc(0.5rem + var(--gl-alert-padding-x, 0px));
display: flex;
align-items: center;
height: 1.5rem;
}
.gl-alert-has-title .gl-alert-icon-container {
top: 0.75rem;
height: 1.25rem;
}
.gl-alert-body {
--tw-leading: var(--gl-line-height-20);
Expand Down Expand Up @@ -4518,11 +4519,8 @@
}
.gl-dismiss-btn {
position: absolute;
top: 0.75rem;
right: calc(0.75rem + var(--gl-alert-padding-x, 0px));
}
.gl-alert-has-title .gl-dismiss-btn {
top: 0.75rem;
top: calc(0.5rem - (1.5rem - 1.25rem) / 2);
right: calc(0.25rem + var(--gl-alert-padding-x, 0px));
}
.gl-alert-danger {
background-color: var(--gl-alert-danger-background-color);
Expand Down
45 changes: 23 additions & 22 deletions packages/ui/src/base/alert/alert.css
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
* packages/gitlab-ui/src/components/base/alert/alert.scss
*
* Sass constants are expanded to match the upstream compiled output:
* `$gl-spacing-scale-9` is `3rem`, `$gl-spacing-scale-5` is `1rem`,
* `$gl-spacing-scale-4` is `.75rem`, `$gl-spacing-scale-1` is `.25rem`,
* and `$gl-line-height-24` is `1.5rem`. The `gl-container-width-up(sm)`
* mixin is written out as the established local `@container`/`@media` pair.
* `$gl-spacing-scale-7` is `2rem`, `$gl-spacing-scale-4` is `.75rem`,
* `$gl-spacing-scale-3` is `.5rem`, `$gl-spacing-scale-2` is `.25rem`,
* `$gl-line-height-20` is `1.25rem`, and `$gl-button-small-size` is `1.5rem`.
* The `gl-container-width-up(sm)` mixin is written out as the established
* local `@container`/`@media` pair.
* Compound action controls receive the upstream `gl-alert-action` styles
* through direct-child selectors instead of an internally applied class.
*/
Expand All @@ -19,20 +20,24 @@
* in GitLab.
*/
.gl-alert {
@apply gl-py-5;
@apply gl-py-3;
@apply gl-text-base;
border-radius: var(--gl-alert-border-radius);
padding-inline: calc(3rem + var(--gl-alert-padding-x, 0px));
padding-inline: calc(2rem + var(--gl-alert-padding-x, 0px));
position: relative;
border: 1px solid;

&-not-dismissible {
padding-right: calc(1rem + var(--gl-alert-padding-x, 0px));
padding-right: calc(0.75rem + var(--gl-alert-padding-x, 0px));
}

.gl-link:not(.gl-label-link) {
@apply gl-underline;
}

&.gl-alert-has-actions {
@apply gl-pb-4;
}
}

.gl-alert-sticky {
Expand All @@ -41,20 +46,19 @@
}

.gl-alert-title {
@apply gl-heading-scale-400 -gl-mt-1 gl-mb-2;
@apply gl-heading-scale-300;
@apply gl-mb-2;
@apply gl-leading-20;
}

.gl-alert-icon-container {
position: absolute;
top: 1rem;
left: calc(1rem + var(--gl-alert-padding-x, 0px));
@apply gl-absolute;
@apply gl-top-3;
left: calc(0.5rem + var(--gl-alert-padding-x, 0px));
@apply gl-flex;
@apply gl-items-center;
height: 1.5rem;

.gl-alert-has-title & {
top: 0.75rem;
}
/* Match line-height on first line of title or slot text content */
height: 1.25rem;
}

.gl-alert-body {
Expand Down Expand Up @@ -96,12 +100,9 @@
/* This is the replacement for .gl-alert-dismiss */
.gl-dismiss-btn {
position: absolute;
top: 0.75rem;
right: calc(0.75rem + var(--gl-alert-padding-x, 0px));

.gl-alert-has-title & {
top: 0.75rem;
}
/* Pull the button up by half its overhang so it aligns with first line title or slot text content */
top: calc(0.5rem - (1.5rem - 1.25rem) / 2);
right: calc(0.25rem + var(--gl-alert-padding-x, 0px));
}

/* Variants */
Expand Down
32 changes: 24 additions & 8 deletions packages/ui/src/base/alert/alert.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ const variants = [
const onPrimaryAction = fn();
const onSecondaryAction = fn((event: MouseEvent<HTMLElement>) => event.preventDefault());

function WrappedCustomActions() {
return (
<GlAlertActions>
<button type="button">Custom action</button>
</GlAlertActions>
);
}

const meta = {
title: "UI/Base/Alert",
component: GlAlert,
Expand Down Expand Up @@ -94,13 +102,15 @@ export const UndismissibleDangerWithActions: Story = {
<GlButton
category="primary"
onClick={onPrimaryAction}
size="small"
variant="confirm">
Primary action
</GlButton>
<GlButton
category="secondary"
href="#"
onClick={onSecondaryAction}
size="small"
variant="default">
Secondary action
</GlButton>
Expand All @@ -113,13 +123,20 @@ export const UndismissibleDangerWithActions: Story = {

const alert = canvas.getByRole("alert");

await expect(alert).toHaveClass("gl-alert-danger", "gl-alert-not-dismissible");
await expect(alert).toHaveClass(
"gl-alert-danger",
"gl-alert-has-actions",
"gl-alert-not-dismissible",
);
await expect(within(alert).queryByRole("button", { name: "Dismiss" })).toBeNull();

await userEvent.click(canvas.getByRole("button", { name: "Primary action" }));
const primary = canvas.getByRole("button", { name: "Primary action" });
await expect(primary).toHaveClass("btn-sm");
await userEvent.click(primary);
await expect(onPrimaryAction).toHaveBeenCalledTimes(1);

const secondary = canvas.getByRole("button", { name: "Secondary action" });
await expect(secondary).toHaveClass("btn-sm");
await expect(secondary).toHaveAttribute("href", "#");
await userEvent.click(secondary);
await expect(onSecondaryAction).toHaveBeenCalledTimes(1);
Expand All @@ -128,14 +145,13 @@ export const UndismissibleDangerWithActions: Story = {

export const CustomActions: Story = {
render: (args) => (
<GlAlert {...args}>
<GlAlert {...args} hasActions>
<GlAlertDescription>Lorem ipsum dolor sit amet</GlAlertDescription>
<GlAlertActions>
<button type="button">Custom action</button>
</GlAlertActions>
<WrappedCustomActions />
</GlAlert>
),
play: async ({ canvas }) => {
await expect(canvas.getByRole("status")).toHaveClass("gl-alert-has-actions");
await expect(canvas.getByRole("button", { name: "Custom action" })).toBeInTheDocument();
},
};
Expand All @@ -159,8 +175,8 @@ export const Variants: Story = {
<span className="gl-capitalize">{variant}</span> lorem ipsum dolor sit amet
</GlAlertDescription>
<GlAlertActions>
<GlButton category="primary" variant="confirm">Primary</GlButton>
<GlButton category="secondary" variant="default">Secondary</GlButton>
<GlButton category="primary" size="small" variant="confirm">Primary</GlButton>
<GlButton category="secondary" size="small" variant="default">Secondary</GlButton>
</GlAlertActions>
</GlAlert>
))}
Expand Down
43 changes: 38 additions & 5 deletions packages/ui/src/base/alert/alert.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ describe("GlAlert", () => {
});

it("does not render any actions", () => {
expect(renderAlert()).not.toContain("gl-alert-actions");
const markup = renderAlert();

expect(markup).not.toContain("gl-alert-actions");
expect(markup).not.toContain("gl-alert-has-actions");
});

it("renders the description content", () => {
Expand Down Expand Up @@ -144,7 +147,9 @@ describe("GlAlert", () => {
<>
<GlAlertDescription><p>Alert message</p></GlAlertDescription>
<GlAlertActions>
<GlButton category="primary" variant="confirm">Primary action</GlButton>
<GlButton category="primary" size="small" variant="confirm">
Primary action
</GlButton>
</GlAlertActions>
</>
));
Expand All @@ -153,15 +158,17 @@ describe("GlAlert", () => {
"<div class=\"gl-alert-body\"><p>Alert message</p></div>",
);
expect(markup).toContain("<div class=\"gl-alert-actions\">");
expect(markup).toContain("gl-alert-has-actions");
expect(markup).toContain("btn-confirm");
expect(markup).toContain("btn-sm");
expect(markup).toContain("Primary action");
});

it("renders composed button and link actions", () => {
const markup = renderAlert({}, (
<GlAlertActions>
<GlButton category="primary" variant="confirm">Retry</GlButton>
<GlButton href="#cancel" variant="default">Cancel</GlButton>
<GlButton category="primary" size="small" variant="confirm">Retry</GlButton>
<GlButton href="#cancel" size="small" variant="default">Cancel</GlButton>
</GlAlertActions>
));

Expand Down Expand Up @@ -196,6 +203,30 @@ describe("GlAlert", () => {
expect(markup).toContain("Description");
expect(markup).toContain("Actions");
expect(markup).not.toContain("Hidden actions");
expect(markup).toContain("gl-alert-has-actions");
});

it("supports an explicit hasActions override for wrapper components", () => {
function WrappedActions() {
return <GlAlertActions>Wrapped actions</GlAlertActions>;
}

const automaticMarkup = renderAlert({}, <WrappedActions />);
const declaredMarkup = renderAlert({ hasActions: true }, <WrappedActions />);

expect(automaticMarkup).toContain("gl-alert-actions");
expect(automaticMarkup).not.toContain("gl-alert-has-actions");
expect(declaredMarkup).toContain("gl-alert-has-actions");
});

it("lets an explicit false hasActions override automatic detection", () => {
const markup = renderAlert(
{ hasActions: false },
<GlAlertActions>Actions</GlAlertActions>,
);

expect(markup).toContain("gl-alert-actions");
expect(markup).not.toContain("gl-alert-has-actions");
});

it("accepts ordinary, wrapped, and repeated content", () => {
Expand Down Expand Up @@ -246,7 +277,9 @@ describe("GlAlert", () => {
</GlAlert>,
);

expect(markup).toContain("gl-alert gl-alert-info custom-alert");
expect(markup).toContain(
"gl-alert gl-alert-info gl-alert-has-actions custom-alert",
);
expect(markup).toContain("id=\"system-alert\"");
expect(markup).toContain("lang=\"en\"");
expect(markup).toContain("gl-alert-body custom-description");
Expand Down
Loading
Loading