Skip to content

Commit 78eaee3

Browse files
Fercas123origami-z
andauthored
promote dialog and overlay headers to core (#4655)
Co-authored-by: Zhihao Cui <[email protected]>
1 parent 097f463 commit 78eaee3

32 files changed

+501
-809
lines changed

.changeset/fresh-tomatoes-mate.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
"@salt-ds/core": minor
3+
---
4+
5+
Promote updated `DialogHeader` component to core. `DialogHeader`'s update follows our standardized header for container components and app regions, and it can be added to provide a structured header for dialog. The header includes a title and actions that follows our Header Block pattern.
6+
7+
- Fixed default `initialFocus` to close button (if used) as per accessibility guidance.
8+
- Updated bottom padding of DialogHeader from `--salt-spacing-300` to `--salt-spacing-100`
9+
- Updated close button position in `DialogHeader` to horizontally align with header icon using the new `actions` prop.
10+
- Updated overflow border to be above and below `DialogContent` instead of below `DialogHeader` to make scrolling area more evident.
11+
- Added `description` to `DialogHeader`. the description text is displayed just below the header.
12+
13+
```typescript
14+
<Dialog open={open} onOpenChange={onOpenChange}>
15+
<DialogHeader
16+
header="Terms and conditions"
17+
actions={
18+
<Button
19+
aria-label="Close overlay"
20+
appearance="transparent"
21+
sentiment="neutral"
22+
>
23+
<CloseIcon aria-hidden />
24+
</Button>
25+
}
26+
/>
27+
<DialogContent>
28+
Only Chase Cards that we determine are eligible can be added to the wallet.
29+
</DialogContent>
30+
</Dialog>;
31+
```
32+
33+
Prompted `OverlayHeader` component to core.
34+
35+
- Fixed default `initialFocus` to close button (if used) as per accessibility guidance.
36+
- Updated close button position in `OverlayHeader` using the new `actions` prop.
37+
- Added `description` to `OverlayHeader`. the description text is displayed just below the header.
38+
39+
```tsx
40+
<Overlay {...args}>
41+
<OverlayTrigger>
42+
<Button>Show Overlay</Button>
43+
</OverlayTrigger>
44+
<OverlayPanel aria-labelledby={id}>
45+
<OverlayHeader
46+
id={id}
47+
header="Title"
48+
actions={
49+
<Button
50+
aria-label="Close overlay"
51+
appearance="transparent"
52+
sentiment="neutral"
53+
>
54+
<CloseIcon aria-hidden />
55+
</Button>
56+
}
57+
/>
58+
<OverlayPanelContent>Content of Overlay</OverlayPanelContent>
59+
</OverlayPanel>
60+
</Overlay>
61+
```

.changeset/unlucky-bobcats-sort.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@salt-ds/lab": minor
3+
---
4+
5+
- Removed `DialogHeader` from labs and promoted to core.
6+
- Removed `OverlayHeader` from labs and promoted to core.

packages/core/src/__tests__/__e2e__/dialog/Dialog.cy.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ describe("GIVEN a Dialog", () => {
6565

6666
cy.findByRole("button", { name: "Open dialog" }).realClick();
6767

68-
cy.get(".saltDialogHeader-preheader").should("be.visible");
68+
cy.get(".saltDialogHeader-header").contains("I am a preheader");
6969
});
7070
});
7171

@@ -165,20 +165,20 @@ describe("GIVEN a Dialog", () => {
165165
cy.mount(<Default />);
166166
cy.findByRole("button", { name: "Open dialog" }).realClick();
167167
cy.findByRole("dialog").should("be.visible");
168+
cy.findAllByRole("button", { name: "Close dialog" }).should("be.focused");
169+
cy.realPress("Tab");
168170
cy.findAllByRole("button", { name: "Cancel" }).should("be.focused");
169171
cy.realPress("Tab");
170172
cy.findAllByRole("button", { name: "Previous" }).should("be.focused");
171173
cy.realPress("Tab");
172174
cy.findAllByRole("button", { name: "Next" }).should("be.focused");
173175
cy.realPress("Tab");
174-
cy.findAllByRole("button", { name: "Close dialog" }).should("be.focused");
175-
cy.realPress("Tab");
176176
//back to the first button
177-
cy.findAllByRole("button", { name: "Cancel" }).should("be.focused");
177+
cy.findAllByRole("button", { name: "Close dialog" }).should("be.focused");
178178
});
179179

180180
it("THEN should support initialFocus being set", () => {
181-
cy.mount(<Default initialFocus={2} />);
181+
cy.mount(<Default initialFocus={3} />);
182182
cy.findByRole("button", { name: "Open dialog" }).realClick();
183183
cy.findByRole("dialog").should("be.visible");
184184
cy.findByRole("button", { name: "Next" }).should("be.focused");

packages/core/src/dialog/DialogContent.css

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77
margin-left: var(--salt-spacing-200);
88
margin-right: var(--salt-spacing-300);
99
padding-left: var(--salt-spacing-100);
10-
border-bottom: var(--salt-size-border) var(--salt-separable-borderStyle) transparent;
11-
box-shadow: none;
1210
/* auto is needed for support on Safari, due to webkit bugs with flex */
1311
flex: 1 1 auto;
1412
}
@@ -17,6 +15,7 @@
1715
}
1816

1917
.saltDialogContent-scroll {
18+
margin-left: var(--salt-spacing-300);
19+
margin-right: var(--salt-spacing-300);
2020
border-bottom: var(--salt-size-border) var(--salt-separable-borderStyle) var(--salt-separable-tertiary-borderColor);
21-
box-shadow: var(--salt-overlayable-shadow-scroll);
2221
}

packages/core/src/dialog/DialogContent.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,24 @@ export interface DialogContentProps extends HTMLAttributes<HTMLDivElement> {
3030
export const DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(
3131
function DialogContent(props, ref) {
3232
const { children, className, ...rest } = props;
33-
const [scrolled, setScrolled] = useState(false);
33+
const [scrolledTop, setScrolledTop] = useState(false);
34+
const [scrolledBottom, setScrolledBottom] = useState(true);
3435
const [isOverflowing, setIsOverflowing] = useState(false);
3536

3637
const divRef = useRef<HTMLDivElement>(null);
3738
const containerRef = useForkRef(divRef, ref);
3839

3940
const handleScroll = () => {
4041
targetWindow?.requestAnimationFrame(() => {
41-
if (!divRef.current) return;
42-
setScrolled(divRef.current.scrollTop > 0);
42+
const container = divRef.current;
43+
if (!container) return;
44+
setScrolledTop(container.scrollTop > 0);
45+
setScrolledBottom(
46+
container.scrollHeight -
47+
container.scrollTop -
48+
container.clientHeight !==
49+
0,
50+
);
4351
});
4452
};
4553

@@ -65,7 +73,11 @@ export const DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(
6573

6674
return (
6775
<>
68-
<div className={clsx({ [withBaseName("scroll")]: scrolled })} />
76+
<div
77+
className={clsx({
78+
[withBaseName("scroll")]: isOverflowing && scrolledTop,
79+
})}
80+
/>
6981
<div
7082
className={clsx(
7183
withBaseName(),
@@ -80,6 +92,11 @@ export const DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(
8092
>
8193
{children}
8294
</div>
95+
<div
96+
className={clsx({
97+
[withBaseName("scroll")]: isOverflowing && scrolledBottom,
98+
})}
99+
/>
83100
</>
84101
);
85102
},

packages/core/src/dialog/DialogHeader.css

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
/* Styles applied to the root element */
22
.saltDialogHeader {
3-
padding-bottom: var(--salt-spacing-100);
3+
padding-bottom: var(--salt-spacing-300);
44
padding-left: var(--salt-spacing-300);
55
padding-right: var(--salt-spacing-300);
6-
align-items: center;
76
display: flex;
87
flex-direction: row;
98
gap: var(--salt-spacing-100);
109
box-sizing: border-box;
1110
}
12-
1311
.saltDialogHeader-header {
1412
margin: 0;
1513
}
1614

15+
.saltDialogHeader-container {
16+
flex-grow: 1;
17+
margin: 0;
18+
display: flex;
19+
flex-direction: column;
20+
gap: var(--salt-spacing-50);
21+
padding-top: calc((var(--salt-size-base) - var(--salt-text-h2-lineHeight)) / 2);
22+
align-items: flex-start;
23+
}
24+
25+
.saltDialogHeader-header > .saltText {
26+
margin: 0;
27+
}
28+
29+
.saltDialogHeader-actionsContainer {
30+
align-self: flex-start;
31+
}
32+
1733
/* Styles applied to the status indicator icon overriding its default size */
1834
.saltDialogHeader .saltStatusIndicator.saltIcon {
1935
--icon-size: var(--salt-text-h2-lineHeight);
36+
padding-top: calc((var(--salt-size-base) - var(--salt-text-h2-lineHeight)) / 2);
2037
}
2138

2239
/* Styles applied to DialogHeader when accent={true} */
@@ -29,23 +46,7 @@
2946
position: absolute;
3047
top: 0;
3148
left: 0;
32-
bottom: var(--salt-spacing-100);
49+
bottom: var(--salt-spacing-300);
3350
width: var(--salt-size-bar);
3451
background: var(--salt-accent-background);
3552
}
36-
37-
.saltDialogHeader-error::before {
38-
background: var(--salt-status-error-borderColor);
39-
}
40-
41-
.saltDialogHeader-info::before {
42-
background: var(--salt-status-info-borderColor);
43-
}
44-
45-
.saltDialogHeader-success::before {
46-
background: var(--salt-status-success-borderColor);
47-
}
48-
49-
.saltDialogHeader-warning::before {
50-
background: var(--salt-status-warning-borderColor);
51-
}

packages/core/src/dialog/DialogHeader.tsx

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
1+
import {
2+
H2,
3+
StatusIndicator,
4+
Text,
5+
type ValidationStatus,
6+
} from "@salt-ds/core";
17
import { useComponentCssInjection } from "@salt-ds/styles";
28
import { useWindow } from "@salt-ds/window";
3-
import clsx from "clsx";
9+
import { clsx } from "clsx";
410
import {
511
type ComponentPropsWithoutRef,
612
type ReactNode,
713
forwardRef,
814
} from "react";
9-
import { StatusIndicator, type ValidationStatus } from "../status-indicator";
10-
import { H2, Text } from "../text";
11-
import { makePrefixer } from "../utils";
1215
import { useDialogContext } from "./DialogContext";
16+
17+
import { makePrefixer } from "../utils";
1318
import dialogHeaderCss from "./DialogHeader.css";
1419

1520
const withBaseName = makePrefixer("saltDialogHeader");
@@ -30,15 +35,25 @@ export interface DialogHeaderProps extends ComponentPropsWithoutRef<"div"> {
3035
* Displays the preheader just above the header
3136
**/
3237
preheader?: ReactNode;
38+
/**
39+
* Description text is displayed just below the header
40+
**/
41+
description?: ReactNode;
42+
/**
43+
* Actions to be displayed in header
44+
*/
45+
actions?: ReactNode;
3346
}
3447

3548
export const DialogHeader = forwardRef<HTMLDivElement, DialogHeaderProps>(
3649
function DialogHeader(props, ref) {
3750
const {
3851
className,
52+
description,
53+
disableAccent,
54+
actions,
3955
header,
4056
preheader,
41-
disableAccent,
4257
status: statusProp,
4358
...rest
4459
} = props;
@@ -68,14 +83,20 @@ export const DialogHeader = forwardRef<HTMLDivElement, DialogHeaderProps>(
6883
{...rest}
6984
>
7085
{status && <StatusIndicator status={status} />}
71-
<H2 className={withBaseName("header")}>
72-
{preheader && (
73-
<Text variant="secondary" className={withBaseName("preheader")}>
74-
{preheader}
86+
<div className={withBaseName("container")}>
87+
<H2 className={withBaseName("header")}>
88+
{preheader && <Text color="primary">{preheader}</Text>}
89+
{header}
90+
</H2>
91+
{description && (
92+
<Text color="secondary" className={withBaseName("description")}>
93+
{description}
7594
</Text>
7695
)}
77-
<div>{header}</div>
78-
</H2>
96+
</div>
97+
{actions && (
98+
<div className={withBaseName("actionsContainer")}>{actions}</div>
99+
)}
79100
</div>
80101
);
81102
},

packages/lab/src/overlay/OverlayHeader.css renamed to packages/core/src/overlay/OverlayHeader.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
display: flex;
1515
flex-direction: column;
1616
gap: var(--salt-spacing-50);
17+
padding-top: calc((var(--salt-size-base) - var(--salt-text-h4-lineHeight)) / 2);
18+
align-items: flex-start;
1719
}
1820

1921
.saltOverlayHeader-header {

packages/lab/src/overlay/OverlayHeader.tsx renamed to packages/core/src/overlay/OverlayHeader.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { H2, Text, makePrefixer } from "@salt-ds/core";
1+
import { H2, Text } from "@salt-ds/core";
22
import { useComponentCssInjection } from "@salt-ds/styles";
33
import { useWindow } from "@salt-ds/window";
44
import { clsx } from "clsx";
@@ -7,6 +7,8 @@ import {
77
type ReactNode,
88
forwardRef,
99
} from "react";
10+
import { makePrefixer } from "../utils";
11+
1012
import overlayHeaderCss from "./OverlayHeader.css";
1113

1214
const withBaseName = makePrefixer("saltOverlayHeader");

packages/core/src/overlay/OverlayPanelCloseButton.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
right: 0;
1010
top: 0;
1111
}
12-
1312
.saltOverlayPanelCloseButton ~ .saltOverlayPanelContent {
1413
padding-right: var(--salt-spacing-400);
1514
}

0 commit comments

Comments
 (0)