lbh-frontend 4.0.0 → 4.1.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ Republish on npm as `latest`. The earlier `3.7.0` tarball was published from the
|
|
|
6
6
|
|
|
7
7
|
* fix Release Please tag format (`include-component-in-tag: false`) so future releases use `vX.Y.Z` tags
|
|
8
8
|
|
|
9
|
+
## [4.1.0](https://github.com/LBHackney-IT/lbh-frontend/compare/v4.0.0...v4.1.0) (2026-07-02)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* export Dialog with optional action footer ([#257](https://github.com/LBHackney-IT/lbh-frontend/issues/257)) ([a90ffe3](https://github.com/LBHackney-IT/lbh-frontend/commit/a90ffe37d6b23db551e3514dd16c208f11c17288))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Bug Fixes
|
|
18
|
+
|
|
19
|
+
* dialog BEM selectors with radix modifier ([#254](https://github.com/LBHackney-IT/lbh-frontend/issues/254)) ([7ef1b81](https://github.com/LBHackney-IT/lbh-frontend/commit/7ef1b81db4a39d02db0c81055e400ceadd1da0e9))
|
|
20
|
+
* import Dialog directly in docs example for SSG ([#259](https://github.com/LBHackney-IT/lbh-frontend/issues/259)) ([49f0b16](https://github.com/LBHackney-IT/lbh-frontend/commit/49f0b16b5d3347202d3f1fa30e7a142d804ab220))
|
|
21
|
+
|
|
9
22
|
## [4.0.0](https://github.com/LBHackney-IT/lbh-frontend/compare/v3.7.1...v4.0.0) (2026-06-12)
|
|
10
23
|
|
|
11
24
|
|
package/lbh/all.js
CHANGED
|
@@ -11,6 +11,7 @@ import Map from "./components/lbh-contact-block/contact-block";
|
|
|
11
11
|
import Radios from "./components/lbh-radios/radios";
|
|
12
12
|
import Tabs from "./components/lbh-tabs/tabs";
|
|
13
13
|
import Collapsible from "./components/lbh-collapsible/collapsible";
|
|
14
|
+
import Dialog from "./components/lbh-dialog/index";
|
|
14
15
|
|
|
15
16
|
function nodeListForEach(nodes, callback) {
|
|
16
17
|
if (window.NodeList.prototype.forEach) {
|
|
@@ -63,6 +64,7 @@ export {
|
|
|
63
64
|
Checkboxes,
|
|
64
65
|
CookieBanner,
|
|
65
66
|
Details,
|
|
67
|
+
Dialog,
|
|
66
68
|
ErrorSummary,
|
|
67
69
|
Map,
|
|
68
70
|
Radios,
|
|
@@ -24,11 +24,13 @@
|
|
|
24
24
|
background: lbh-colour("lbh-white");
|
|
25
25
|
border: 4px solid lbh-colour("lbh-text");
|
|
26
26
|
|
|
27
|
-
&
|
|
27
|
+
// Use full element class names — &__ would compile to .lbh-dialog--radix__*
|
|
28
|
+
// when the parent selector includes a BEM modifier on the same element.
|
|
29
|
+
.lbh-dialog__title {
|
|
28
30
|
margin-top: 0;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
.lbh-dialog__close {
|
|
32
34
|
position: absolute;
|
|
33
35
|
right: 7px;
|
|
34
36
|
top: 7px;
|
|
@@ -52,7 +54,7 @@
|
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
57
|
+
.lbh-dialog__actions {
|
|
56
58
|
display: flex;
|
|
57
59
|
flex-direction: column;
|
|
58
60
|
align-items: center;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { fireEvent, render, screen, within } from "@testing-library/react"
|
|
3
|
+
import Dialog from "./index"
|
|
4
|
+
|
|
5
|
+
describe("Dialog", () => {
|
|
6
|
+
it("renders title, children, and close button", () => {
|
|
7
|
+
render(
|
|
8
|
+
<Dialog isOpen title="Delete record" onDismiss={() => {}}>
|
|
9
|
+
<p>Are you sure?</p>
|
|
10
|
+
</Dialog>
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
expect(screen.getByRole("heading", { name: "Delete record" })).toBeTruthy()
|
|
14
|
+
expect(screen.getByText("Are you sure?")).toBeTruthy()
|
|
15
|
+
expect(screen.getByRole("button", { name: "Close" })).toBeTruthy()
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
it("does not render actions when no action props are provided", () => {
|
|
19
|
+
render(
|
|
20
|
+
<Dialog isOpen title="Information" onDismiss={() => {}}>
|
|
21
|
+
<p>Details only</p>
|
|
22
|
+
</Dialog>
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
expect(document.body.querySelector(".lbh-dialog__actions")).toBeNull()
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("renders confirm and cancel actions when provided", () => {
|
|
29
|
+
render(
|
|
30
|
+
<Dialog
|
|
31
|
+
isOpen
|
|
32
|
+
title="Delete record"
|
|
33
|
+
onDismiss={() => {}}
|
|
34
|
+
onConfirm={() => {}}
|
|
35
|
+
confirmText="Yes, delete"
|
|
36
|
+
onCancel={() => {}}
|
|
37
|
+
cancelText="No, cancel"
|
|
38
|
+
>
|
|
39
|
+
<p>Content</p>
|
|
40
|
+
</Dialog>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
expect(screen.getByRole("button", { name: "Yes, delete" })).toBeTruthy()
|
|
44
|
+
expect(screen.getByRole("button", { name: "No, cancel" })).toBeTruthy()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it("renders only confirm when onCancel is omitted", () => {
|
|
48
|
+
render(
|
|
49
|
+
<Dialog
|
|
50
|
+
isOpen
|
|
51
|
+
title="Sign out"
|
|
52
|
+
onDismiss={() => {}}
|
|
53
|
+
onConfirm={() => {}}
|
|
54
|
+
confirmText="Stay logged in"
|
|
55
|
+
>
|
|
56
|
+
<p>You will be signed out.</p>
|
|
57
|
+
</Dialog>
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
expect(
|
|
61
|
+
screen.getByRole("button", { name: "Stay logged in" })
|
|
62
|
+
).toBeTruthy()
|
|
63
|
+
expect(screen.queryByRole("button", { name: "No, cancel" })).toBeNull()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it("renders only cancel when onConfirm is omitted", () => {
|
|
67
|
+
render(
|
|
68
|
+
<Dialog
|
|
69
|
+
isOpen
|
|
70
|
+
title="Address"
|
|
71
|
+
onDismiss={() => {}}
|
|
72
|
+
onCancel={() => {}}
|
|
73
|
+
cancelText="Close"
|
|
74
|
+
>
|
|
75
|
+
<p>Address details</p>
|
|
76
|
+
</Dialog>
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
const actions = document.body.querySelector(".lbh-dialog__actions")
|
|
80
|
+
expect(actions).toBeTruthy()
|
|
81
|
+
expect(within(actions as HTMLElement).getByRole("button", { name: "Close" })).toBeTruthy()
|
|
82
|
+
expect(screen.queryByRole("button", { name: "Yes" })).toBeNull()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it("calls onDismiss when the close button is clicked", () => {
|
|
86
|
+
const onDismiss = jest.fn()
|
|
87
|
+
|
|
88
|
+
render(
|
|
89
|
+
<Dialog isOpen title="Close me" onDismiss={onDismiss}>
|
|
90
|
+
<p>Content</p>
|
|
91
|
+
</Dialog>
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
fireEvent.click(screen.getByRole("button", { name: "Close" }))
|
|
95
|
+
expect(onDismiss).toHaveBeenCalledTimes(1)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it("calls onConfirm and onCancel when action buttons are clicked", () => {
|
|
99
|
+
const onConfirm = jest.fn()
|
|
100
|
+
const onCancel = jest.fn()
|
|
101
|
+
|
|
102
|
+
render(
|
|
103
|
+
<Dialog
|
|
104
|
+
isOpen
|
|
105
|
+
title="Confirm"
|
|
106
|
+
onDismiss={() => {}}
|
|
107
|
+
onConfirm={onConfirm}
|
|
108
|
+
onCancel={onCancel}
|
|
109
|
+
confirmButtonTestId="confirm-action"
|
|
110
|
+
>
|
|
111
|
+
<p>Content</p>
|
|
112
|
+
</Dialog>
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
fireEvent.click(screen.getByTestId("confirm-action"))
|
|
116
|
+
fireEvent.click(screen.getByRole("button", { name: "No, cancel" }))
|
|
117
|
+
|
|
118
|
+
expect(onConfirm).toHaveBeenCalledTimes(1)
|
|
119
|
+
expect(onCancel).toHaveBeenCalledTimes(1)
|
|
120
|
+
})
|
|
121
|
+
})
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import React from "react"
|
|
2
2
|
import * as RadixDialog from "@radix-ui/react-dialog"
|
|
3
3
|
|
|
4
|
-
interface
|
|
4
|
+
export interface DialogProps
|
|
5
|
+
extends Omit<
|
|
6
|
+
React.ComponentPropsWithoutRef<typeof RadixDialog.Content>,
|
|
7
|
+
"title"
|
|
8
|
+
> {
|
|
5
9
|
isOpen: boolean
|
|
6
10
|
onDismiss: () => void
|
|
7
|
-
children: React.ReactNode
|
|
8
11
|
title: string
|
|
12
|
+
children: React.ReactNode
|
|
13
|
+
onConfirm?: () => void
|
|
14
|
+
confirmText?: string
|
|
15
|
+
confirmButtonTestId?: string
|
|
16
|
+
onCancel?: () => void
|
|
17
|
+
cancelText?: string
|
|
9
18
|
}
|
|
10
19
|
|
|
11
20
|
const Dialog = ({
|
|
@@ -13,40 +22,76 @@ const Dialog = ({
|
|
|
13
22
|
onDismiss,
|
|
14
23
|
children,
|
|
15
24
|
title,
|
|
25
|
+
onConfirm,
|
|
26
|
+
confirmText = "Yes",
|
|
27
|
+
confirmButtonTestId,
|
|
28
|
+
onCancel,
|
|
29
|
+
cancelText = "No, cancel",
|
|
16
30
|
...props
|
|
17
|
-
}:
|
|
18
|
-
|
|
19
|
-
open={isOpen}
|
|
20
|
-
onOpenChange={(open) => {
|
|
21
|
-
if (!open) onDismiss()
|
|
22
|
-
}}
|
|
23
|
-
>
|
|
24
|
-
<RadixDialog.Portal>
|
|
25
|
-
<RadixDialog.Overlay className="lbh-dialog__overlay" />
|
|
26
|
-
<RadixDialog.Content className="lbh-dialog lbh-dialog--radix" {...props}>
|
|
27
|
-
<RadixDialog.Title asChild>
|
|
28
|
-
<h2 className="lbh-heading-h2 lbh-dialog__title">{title}</h2>
|
|
29
|
-
</RadixDialog.Title>
|
|
30
|
-
{children}
|
|
31
|
-
<RadixDialog.Close asChild>
|
|
32
|
-
<button type="button" className="lbh-dialog__close">
|
|
33
|
-
<span className="govuk-visually-hidden">Close</span>
|
|
31
|
+
}: DialogProps): React.ReactElement => {
|
|
32
|
+
const showActions = Boolean(onConfirm || onCancel)
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
34
|
+
return (
|
|
35
|
+
<RadixDialog.Root
|
|
36
|
+
open={isOpen}
|
|
37
|
+
onOpenChange={(open) => {
|
|
38
|
+
if (!open) onDismiss()
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
<RadixDialog.Portal>
|
|
42
|
+
<RadixDialog.Overlay className="lbh-dialog__overlay" />
|
|
43
|
+
<RadixDialog.Content
|
|
44
|
+
className="lbh-dialog lbh-dialog--radix"
|
|
45
|
+
aria-describedby={undefined}
|
|
46
|
+
{...props}
|
|
47
|
+
>
|
|
48
|
+
<RadixDialog.Title asChild>
|
|
49
|
+
<h2 className="lbh-heading-h2 lbh-dialog__title">{title}</h2>
|
|
50
|
+
</RadixDialog.Title>
|
|
51
|
+
{children}
|
|
52
|
+
<RadixDialog.Close asChild>
|
|
53
|
+
<button type="button" className="lbh-dialog__close">
|
|
54
|
+
<span className="govuk-visually-hidden">Close</span>
|
|
55
|
+
|
|
56
|
+
<svg width="18" height="18" viewBox="0 0 13 13" fill="none">
|
|
57
|
+
<path
|
|
58
|
+
d="M-0.0501709 1.36379L1.36404 -0.050415L12.6778 11.2633L11.2635 12.6775L-0.0501709 1.36379Z"
|
|
59
|
+
fill="#0B0C0C"
|
|
60
|
+
/>
|
|
61
|
+
<path
|
|
62
|
+
d="M11.2635 -0.050293L12.6778 1.36392L1.36404 12.6776L-0.0501709 11.2634L11.2635 -0.050293Z"
|
|
63
|
+
fill="#0B0C0C"
|
|
64
|
+
/>
|
|
65
|
+
</svg>
|
|
66
|
+
</button>
|
|
67
|
+
</RadixDialog.Close>
|
|
68
|
+
{showActions && (
|
|
69
|
+
<div className="lbh-dialog__actions">
|
|
70
|
+
{onConfirm && (
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
className="govuk-button lbh-button"
|
|
74
|
+
onClick={onConfirm}
|
|
75
|
+
data-testid={confirmButtonTestId}
|
|
76
|
+
>
|
|
77
|
+
{confirmText}
|
|
78
|
+
</button>
|
|
79
|
+
)}
|
|
80
|
+
{onCancel && (
|
|
81
|
+
<button
|
|
82
|
+
type="button"
|
|
83
|
+
className="lbh-link lbh-link--no-visited-state"
|
|
84
|
+
onClick={onCancel}
|
|
85
|
+
>
|
|
86
|
+
{cancelText}
|
|
87
|
+
</button>
|
|
88
|
+
)}
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
</RadixDialog.Content>
|
|
92
|
+
</RadixDialog.Portal>
|
|
93
|
+
</RadixDialog.Root>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
51
96
|
|
|
52
97
|
export default Dialog
|
package/lbh/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { default as Checkboxes } from "./components/lbh-checkboxes/checkboxes";
|
|
|
6
6
|
import { default as Collapsible } from "./components/lbh-collapsible/collapsible";
|
|
7
7
|
import { default as CookieBanner } from "./components/lbh-cookie-banner/cookie-banner";
|
|
8
8
|
import { default as Details } from "./components/lbh-details/details";
|
|
9
|
+
import { default as Dialog } from "./components/lbh-dialog/index";
|
|
9
10
|
import { default as ErrorSummary } from "./components/lbh-error-summary/error-summary";
|
|
10
11
|
import { default as Map } from "./components/lbh-contact-block/contact-block";
|
|
11
12
|
import { default as Radios } from "./components/lbh-radios/radios";
|
|
@@ -26,12 +27,15 @@ export {
|
|
|
26
27
|
Collapsible,
|
|
27
28
|
CookieBanner,
|
|
28
29
|
Details,
|
|
30
|
+
Dialog,
|
|
29
31
|
ErrorSummary,
|
|
30
32
|
Map,
|
|
31
33
|
Radios,
|
|
32
34
|
Tabs,
|
|
33
35
|
};
|
|
34
36
|
|
|
37
|
+
export type { DialogProps } from "./components/lbh-dialog/index";
|
|
38
|
+
|
|
35
39
|
declare global {
|
|
36
40
|
interface Window {
|
|
37
41
|
LBHFrontend: {
|