Skip to content

Commit

Permalink
chore(ui): export deprecated Panel and undo some chnages
Browse files Browse the repository at this point in the history
  • Loading branch information
guoda-puidokaite committed Nov 25, 2024
1 parent d70aa22 commit 6e33c99
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,6 @@ export const Panel: FC<PanelProps> = ({
</div>
<div className={`juno-panel-content-wrapper ${contentWrapperStyles}`}>{children}</div>
</div>,
portalContainer ?? document.body
portalContainer ? portalContainer : document.body
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@

import React, { ReactNode, HTMLAttributes } from "react"

const panelBodyBaseClasses = `
jn-bg-white
jn-rounded-md
jn-shadow
`

const bodyContentClasses = `
const bodyContentStyles = `
jn-px-8
jn-py-4
`
Expand Down Expand Up @@ -41,9 +35,9 @@ export interface PanelBodyProps extends HTMLAttributes<HTMLDivElement> {
*/
export const PanelBody: React.FC<PanelBodyProps> = ({ className = "", footer, children, ...props }) => {
return (
<div className={`juno-panel-body ${panelBodyBaseClasses} ${className}`} {...props}>
<div className={`juno-panel-body-content ${bodyContentClasses}`}>{children}</div>
{footer ? <div className="juno-panel-footer">{footer}</div> : null}
<div className={`juno-panel-body ${className}`} {...props}>
<div className={`juno-panel-body-content ${bodyContentStyles}`}>{children}</div>
{footer}
</div>
)
}
146 changes: 146 additions & 0 deletions packages/ui-components/src/deprecated_js/Panel/Panel.component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useEffect } from "react"
import { createPortal } from "react-dom"
import PropTypes from "prop-types"
import { Icon } from "../../deprecated_js/Icon/Icon.component"
import { usePortalRef } from "../../deprecated_js/PortalProvider/PortalProvider.component"

const panelClasses = (isOpen, isTransitioning, size) => {
return `
jn-fixed
jn-right-0
jn-transition-transform
jn-ease-out
jn-duration-300
jn-inset-y-0
jn-z-[9989]
jn-grid
jn-grid-rows-[auto_1fr]
jn-bg-theme-panel
jn-backdrop-blur
jn-backdrop-saturate-150
jn-shadow-md
${
size === "large"
? `
jn-w-[90%]
xl:jn-w-[80%]
2xl:jn-w-[1228px]`
: `
jn-w-[75%]
xl:jn-w-[55%]
2xl:jn-w-[844px]`
}
${!isOpen ? `jn-translate-x-[100%]` : ""}
${!isOpen && !isTransitioning ? `jn-invisible` : ""}
`
.replace(/\n/g, " ")
.replace(/\s+/g, " ")
}

const contentWrapperClasses = `
jn-overflow-auto
`

const panelHeaderClasses = `
jn-flex
jn-items-center
jn-py-4
jn-px-8
`

const panelTitleClasses = `
jn-text-theme-high
jn-text-lg
jn-font-bold
`

/** A slide-in panel for the Content Area. */
export const Panel = ({
heading = "",
size,
className = "",
opened = false,
closeable = true,
onClose,
children,
...props
}) => {
const [isOpen, setIsOpen] = useState(opened)
const [isCloseable, setIsCloseable] = useState(closeable)
const [isTransitioning, setIsTransitioning] = useState(false)

// ensure we notice if the opened parameter is changed from the outside
useEffect(() => {
setIsOpen(opened)
}, [opened])

// ensure we notice if the cloeseable parameter is changed from the outside
useEffect(() => {
setIsCloseable(closeable)
}, [closeable])

// ----- Timeout stuff -------
// necessary because we want to set the panel to invisible only after the closing transition has finished
// the invisible panel is to ensure that the panel can't be tab targeted when closed
const timeoutRef = React.useRef(null)

React.useEffect(() => {
return () => clearTimeout(timeoutRef.current) // clear when component is unmounted
}, [])

// if isOpen state changes to false set the transitioning state to true for 500ms
useEffect(() => {
if (!isOpen) {
setIsTransitioning(true)
clearTimeout(timeoutRef.current)
timeoutRef.current = setTimeout(() => setIsTransitioning(false), 500)
}
}, [isOpen])

const handleClose = (event) => {
setIsOpen(false)
onClose && onClose(event)
}

const portalContainer = usePortalRef()

return createPortal(
<div
className={`juno-panel ${panelClasses(isOpen, isTransitioning, size)} ${className}`}
role="dialog"
aria-labelledby="juno-panel-title"
{...props}
>
<div className={`juno-panel-header ${panelHeaderClasses}`}>
<div className={`juno-panel-title ${panelTitleClasses}`} id="juno-panel-title">
{heading}
</div>
{isCloseable && <Icon icon="close" onClick={handleClose} className="juno-panel-close jn-ml-auto" />}
</div>
<div className={`juno-panel-content-wrapper ${contentWrapperClasses}`}>{children}</div>
</div>,
portalContainer ? portalContainer : document.body
)
}

Panel.propTypes = {
/** Pass a Panel heading/title. */
heading: PropTypes.node,
/** Size of the opened panel. If unspecified, default size is used. */
size: PropTypes.oneOf(["default", "large"]),
/** Pass open state */
opened: PropTypes.bool,
/** Pass whether panel should be closeable via a close button or not. If false, the close button will not be rendered. The panel can still be closed by setting "opened" to false. */
closeable: PropTypes.bool,
/** Pass a handler that will be called when the close button is clicked */
onClose: PropTypes.func,
/** Pass an optional className */
className: PropTypes.string,
/** Pass child nodes to be rendered in the main body of the Panel */
children: PropTypes.node,
}
80 changes: 80 additions & 0 deletions packages/ui-components/src/deprecated_js/Panel/Panel.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import * as React from "react"
import { render, screen, waitFor } from "@testing-library/react"
import userEvent from "@testing-library/user-event"
import { Panel } from "./index"

const closedClass = "jn-translate-x-[100%]"

describe("Panel", () => {
test("renders a panel", async () => {
await waitFor(() => render(<Panel />))
expect(screen.getByRole("dialog")).toBeInTheDocument()
expect(screen.getByRole("dialog")).toHaveClass("juno-panel")
})

test("renders a closed panel by default", async () => {
await waitFor(() => render(<Panel />))
expect(screen.getByRole("dialog")).toBeInTheDocument()
expect(screen.getByRole("dialog")).toHaveClass(closedClass)
})

test("renders an opened panel", async () => {
await waitFor(() => render(<Panel opened />))
expect(screen.getByRole("dialog")).toBeInTheDocument()
expect(screen.getByRole("dialog")).not.toHaveClass(closedClass)
})

test("renders a panel with heading", async () => {
await waitFor(() => render(<Panel heading="My heading" opened />))
expect(screen.getByRole("dialog")).toBeInTheDocument()
expect(screen.getByRole("dialog")).toHaveTextContent("My heading")
})

test("renders a panel with close button by default", async () => {
await waitFor(() => render(<Panel />))
expect(screen.getByRole("dialog")).toBeInTheDocument()
expect(screen.getByRole("button")).toBeInTheDocument()
expect(screen.getByLabelText("close")).toBeInTheDocument()
expect(screen.getByRole("button")).toHaveAttribute("aria-label", "close")
expect(screen.getByRole("img")).toHaveAttribute("alt", "close")
})

test("renders a panel without a close button", async () => {
await waitFor(() => render(<Panel closeable={false} />))
expect(screen.getByRole("dialog")).toBeInTheDocument()
expect(screen.queryByRole("button")).not.toBeInTheDocument()
expect(screen.queryByLabelText("close")).not.toBeInTheDocument()
})

test("renders a custom classname", async () => {
await waitFor(() => render(<Panel className="my-custom-classname" />))
expect(screen.getByRole("dialog")).toBeInTheDocument()
expect(screen.getByRole("dialog")).toHaveClass("my-custom-classname")
})

test("renders all props as passed", async () => {
await waitFor(() => render(<Panel data-xyz={true} />))
expect(screen.getByRole("dialog")).toBeInTheDocument()
expect(screen.getByRole("dialog")).toHaveAttribute("data-xyz")
})

// EVENTS

test("on click on close button closes panel", async () => {
await waitFor(() => render(<Panel />))
await waitFor(() => userEvent.click(screen.getByRole("button")))
expect(screen.getByRole("dialog")).toHaveClass(closedClass)
})

test("on click on close button fires onClose handler as passed", async () => {
const handleClose = jest.fn()
await waitFor(() => render(<Panel onClose={handleClose} />))
await waitFor(() => userEvent.click(screen.getByRole("button")))
expect(handleClose).toHaveBeenCalledTimes(1)
})
})
6 changes: 6 additions & 0 deletions packages/ui-components/src/deprecated_js/Panel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { Panel } from "./Panel.component"
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from "react"
import PropTypes from "prop-types"

const panelBodyClasses = `
`

const bodyContentClasses = `
jn-px-8
jn-py-4
`

/**
* The panel body component. The main (form) content for the panel goes here.
*/
export const PanelBody = ({ className = "", footer, children, ...props }) => {
return (
<div className={`juno-panel-body ${panelBodyClasses} ${className}`} {...props}>
<div className={`juno-panel-body-content ${bodyContentClasses}`}>{children}</div>

{footer}
</div>
)
}

PanelBody.propTypes = {
/** Add custom class name */
className: PropTypes.string,
children: PropTypes.any,
/** optional footer component */
footer: PropTypes.element,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import * as React from "react"
import { render, screen } from "@testing-library/react"
import { PanelBody } from "./index"
import { PanelFooter } from "../PanelFooter/index"

describe("PanelBody", () => {
test("renders a panel body", async () => {
render(<PanelBody data-testid="panel-body" />)
expect(screen.getByTestId("panel-body")).toBeInTheDocument()
expect(screen.getByTestId("panel-body")).toHaveClass("juno-panel-body")
})

test("renders children as passed", async () => {
render(
<PanelBody data-testid="panel-body">
<button></button>
</PanelBody>
)
expect(screen.getByTestId("panel-body")).toBeInTheDocument()
expect(screen.getByRole("button")).toBeInTheDocument()
})

test("renders footer as passed", async () => {
render(<PanelBody data-testid="panel-body" footer={<PanelFooter>This is the footer</PanelFooter>}></PanelBody>)
expect(screen.getByTestId("panel-body")).toBeInTheDocument()
expect(screen.getByTestId("panel-body")).toHaveTextContent("This is the footer")
})

test("renders a custom className", async () => {
render(<PanelBody data-testid="panel-body" className="my-custom-classname" />)
expect(screen.getByTestId("panel-body")).toBeInTheDocument()
expect(screen.getByTestId("panel-body")).toHaveClass("my-custom-classname")
})

test("renders all props", async () => {
render(<PanelBody data-testid="panel-body" data-lolol="some-prop" />)
expect(screen.getByTestId("panel-body")).toBeInTheDocument()
expect(screen.getByTestId("panel-body")).toHaveAttribute("data-lolol", "some-prop")
})
})
6 changes: 6 additions & 0 deletions packages/ui-components/src/deprecated_js/PanelBody/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

export { PanelBody } from "./PanelBody.component"
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and Juno contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from "react"
import PropTypes from "prop-types"

const panelFooterClasses = `
jn-border-t
jn-border-t-theme-background-lvl-2
jn-px-8
jn-py-4
jn-flex
jn-items-center
jn-justify-end
jn-gap-3
jn-w-full
`

/**
* The panel footer component. You can drop buttons in here and they will automatically be aligned correctly to the right.
*/
export const PanelFooter = ({ className = "", children, ...props }) => {
return (
<div className={`juno-panel-footer ${panelFooterClasses} ${className}`} {...props}>
{children}
</div>
)
}

PanelFooter.propTypes = {
/** Add custom class name */
className: PropTypes.string,
children: PropTypes.any,
}
Loading

0 comments on commit 6e33c99

Please sign in to comment.