Skip to content

Commit a4dec46

Browse files
fix title for ReactNode
1 parent 6b3da42 commit a4dec46

4 files changed

Lines changed: 46 additions & 26 deletions

File tree

.changeset/warm-news-remain.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudoperators/juno-ui-components": patch
3+
---
4+
5+
Change `title` and `heading` type to `ReactNode` and fix alignement for `Modal`.

packages/ui-components/src/components/Modal/Modal.component.tsx

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import React, { useState, useEffect, useRef, useId } from "react"
6+
import React, { useState, useEffect, useRef, useId, ReactNode } from "react"
77
import { createPortal } from "react-dom"
88
import { FocusTrap } from "focus-trap-react"
99
import { ModalFooter } from "../ModalFooter/index"
@@ -46,8 +46,8 @@ const headerstyles = `
4646
`
4747

4848
const titlestyles = `
49-
jn:text-xl
50-
jn:font-bold
49+
jn:text-xl
50+
jn:font-bold
5151
`
5252

5353
const contentstyles = `
@@ -161,9 +161,9 @@ export const Modal: React.FC<ModalProps> = ({
161161

162162
const modalRef = useRef<HTMLDivElement | null>(null)
163163

164-
const theTitle = title || heading
165-
166-
const modalTitleId = uniqueId()
164+
const modalTitle = title || heading
165+
const hasTitle = Boolean(modalTitle)
166+
const modalTitleId = hasTitle ? uniqueId() : undefined
167167

168168
return (
169169
<>
@@ -176,7 +176,7 @@ export const Modal: React.FC<ModalProps> = ({
176176
clickOutsideDeactivates: isCloseabelOnBackdropClick,
177177
fallbackFocus: () => modalRef.current!,
178178
allowOutsideClick: true,
179-
escapeDeactivates: (e) => {
179+
escapeDeactivates: (e: KeyboardEvent) => {
180180
handleEsc(e)
181181
return false
182182
},
@@ -187,20 +187,18 @@ export const Modal: React.FC<ModalProps> = ({
187187
role="dialog"
188188
ref={modalRef}
189189
{...props}
190-
aria-labelledby={theTitle && theTitle.length ? modalTitleId : undefined}
190+
aria-labelledby={modalTitleId}
191191
aria-label={ariaLabel}
192192
>
193-
<div
194-
className={`juno-modal-header ${headerstyles} ${theTitle && theTitle.length ? `jn:justify-between` : `jn:justify-end`}`}
195-
>
193+
<div className={`juno-modal-header ${headerstyles} jn:justify-between`}>
196194
{title || heading ? (
197195
<h1 className={`juno-modal-title ${titlestyles}`} id={modalTitleId}>
198196
{title || heading}
199197
</h1>
200198
) : (
201199
""
202200
)}
203-
{isCloseable ? <Icon icon="close" onClick={handleCancelClick} /> : ""}
201+
{isCloseable ? <Icon icon="close" onClick={handleCancelClick} aria-label="close" /> : ""}
204202
</div>
205203
<div className={`juno-modal-content ${contentstyles} ${unpad ? "" : contentpaddingstyles}`}>
206204
{children}
@@ -232,11 +230,11 @@ export const Modal: React.FC<ModalProps> = ({
232230

233231
type ModalSize = "small" | "large" | "xl" | "2xl"
234232

235-
export interface ModalProps extends Omit<React.HTMLProps<HTMLDivElement>, "size"> {
233+
export interface ModalProps extends Omit<React.HTMLProps<HTMLDivElement>, "size" | "title"> {
236234
/** The title of the modal. This will be rendering as the heading of the modal, and the modal's `arial-labelledby` attribute will reference the title/heading element. If the modal does not have `title` or `heading`, use `ariaLabel` to provide an accessible name for the modal. */
237-
title?: string
238-
/** Also the title of the modal, just for API flexibility. If both `title` and `heading` are passed, `title` will win. */
239-
heading?: string
235+
title?: ReactNode
236+
/** Also the title of the modal, just for API flexibility. If both `title` and `heading` are passed, `title` will take precedence. */
237+
heading?: ReactNode
240238
/** The aria-label of the modal. Use only if the modal does NOT have a `title` or `heading`. */
241239
ariaLabel?: string
242240
/** By default, the first element in the tab order of the Modal content will be focussed. To specify an element to be focussed when the modal opens, pass an element, DOM node, or selector string. */

packages/ui-components/src/components/Modal/Modal.stories.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ export const TestSelectInModal: Story = {
289289
export const TestComboBoxInModal: Story = {
290290
render: Template,
291291
args: {
292-
title: "Modal with CombBox inside",
292+
title: <p>Hello</p>,
293293
size: "small",
294294
children: (
295295
<>

packages/ui-components/src/components/Modal/Modal.test.tsx

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,33 @@ describe("Modal", () => {
3131
expect(screen.getByRole("dialog")).toHaveClass("juno-modal")
3232
})
3333

34-
test("renders a title as passed", async () => {
35-
await waitFor(() =>
36-
render(
37-
<PortalProvider>
38-
<Modal title="My Modal" open />
39-
</PortalProvider>
40-
)
34+
test("renders a string title correctly", () => {
35+
render(
36+
<PortalProvider>
37+
<Modal open title="String Title" />
38+
</PortalProvider>
4139
)
42-
expect(screen.getByRole("dialog")).toBeInTheDocument()
43-
expect(screen.getByRole("dialog")).toHaveTextContent("My Modal")
40+
const dialog = screen.getByRole("dialog")
41+
const titleElement = screen.getByText("String Title")
42+
expect(dialog).toBeInTheDocument()
43+
expect(titleElement).toBeInTheDocument()
44+
expect(titleElement).toHaveAttribute("id")
45+
expect(dialog).toHaveAttribute("aria-labelledby", titleElement.getAttribute("id"))
46+
})
47+
48+
test("renders a ReactNode title correctly", () => {
49+
render(
50+
<PortalProvider>
51+
<Modal open title={<div>Node Title</div>} />
52+
</PortalProvider>
53+
)
54+
55+
const dialog = screen.getByRole("dialog")
56+
const titleWrapper = screen.getByText("Node Title").closest("h1")
57+
58+
expect(dialog).toBeInTheDocument()
59+
expect(titleWrapper).toBeInTheDocument()
60+
expect(dialog).toHaveAttribute("aria-labelledby", titleWrapper?.id)
4461
})
4562

4663
test("renders a title when a 'heading' prop is passed", async () => {

0 commit comments

Comments
 (0)