lbh-frontend 4.0.1-next.1 → 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,12 +6,18 @@ 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.0.1](https://github.com/LBHackney-IT/lbh-frontend/compare/v4.0.0...v4.0.1) (2026-07-02)
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))
10
15
 
11
16
 
12
17
  ### Bug Fixes
13
18
 
14
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))
15
21
 
16
22
  ## [4.0.0](https://github.com/LBHackney-IT/lbh-frontend/compare/v3.7.1...v4.0.0) (2026-06-12)
17
23
 
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,
@@ -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 Props {
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
- }: Props): React.ReactElement => (
18
- <RadixDialog.Root
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
- <svg width="18" height="18" viewBox="0 0 13 13" fill="none">
36
- <path
37
- d="M-0.0501709 1.36379L1.36404 -0.050415L12.6778 11.2633L11.2635 12.6775L-0.0501709 1.36379Z"
38
- fill="#0B0C0C"
39
- />
40
- <path
41
- d="M11.2635 -0.050293L12.6778 1.36392L1.36404 12.6776L-0.0501709 11.2634L11.2635 -0.050293Z"
42
- fill="#0B0C0C"
43
- />
44
- </svg>
45
- </button>
46
- </RadixDialog.Close>
47
- </RadixDialog.Content>
48
- </RadixDialog.Portal>
49
- </RadixDialog.Root>
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: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lbh-frontend",
3
- "version": "4.0.1-next.1",
3
+ "version": "4.1.0-next.1",
4
4
  "description": "Everything you need to make digital services for Hackney",
5
5
  "license": "MIT",
6
6
  "homepage": "https://design-system.hackney.gov.uk",