-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #349 from rebeccaalpert/terms
feat(TermsOfUse): Add Terms of Use modal
- Loading branch information
Showing
9 changed files
with
637 additions
and
0 deletions.
There are no files selected for viewing
148 changes: 148 additions & 0 deletions
148
...fly-docs/content/extensions/chatbot/examples/UI/PF-TermsAndConditionsHeader.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
147 changes: 147 additions & 0 deletions
147
packages/module/patternfly-docs/content/extensions/chatbot/examples/UI/TermsOfUse.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import React from 'react'; | ||
import { Button, Checkbox, FormGroup, Radio, SkipToContent } from '@patternfly/react-core'; | ||
import TermsOfUse from '@patternfly/chatbot/dist/dynamic/TermsOfUse'; | ||
import Chatbot, { ChatbotDisplayMode } from '@patternfly/chatbot/dist/dynamic/Chatbot'; | ||
import termsAndConditionsHeader from './PF-TermsAndConditionsHeader.svg'; | ||
|
||
export const TermsOfUseExample: React.FunctionComponent = () => { | ||
const [isModalOpen, setIsModalOpen] = React.useState(true); | ||
const [displayMode, setDisplayMode] = React.useState(ChatbotDisplayMode.default); | ||
const [hasImage, setHasImage] = React.useState(true); | ||
const chatbotRef = React.useRef<HTMLDivElement>(null); | ||
const termsRef = React.useRef<HTMLDivElement>(null); | ||
|
||
const handleSkipToContent = (e) => { | ||
e.preventDefault(); | ||
if (!isModalOpen && chatbotRef.current) { | ||
chatbotRef.current.focus(); | ||
} | ||
if (isModalOpen && termsRef.current) { | ||
termsRef.current.focus(); | ||
} | ||
}; | ||
|
||
const handleModalToggle = (_event: React.MouseEvent | MouseEvent | KeyboardEvent) => { | ||
setIsModalOpen(!isModalOpen); | ||
}; | ||
|
||
const onPrimaryAction = () => { | ||
// eslint-disable-next-line no-console | ||
console.log('Clicked primary action'); | ||
}; | ||
|
||
const onSecondaryAction = () => { | ||
// eslint-disable-next-line no-console | ||
console.log('Clicked secondary action'); | ||
}; | ||
|
||
const introduction = ( | ||
<> | ||
<p> | ||
Welcome to PatternFly! These terms and conditions outline the rules and regulations for the use of PatternFly's | ||
website, located at <a href="https://patternfly.org">www.patternfly.org.</a> | ||
</p> | ||
<p> | ||
By accessing this website, you are agreeing with our terms and conditions. If you do not agree to all of these | ||
terms and conditions, do not continue to use PatternFly. | ||
</p> | ||
</> | ||
); | ||
|
||
const terminology = ( | ||
<> | ||
<p> | ||
The following terminology applies to these Terms and Conditions, Privacy Statement, Disclaimer Notice, and all | ||
Agreements: | ||
</p> | ||
<ul> | ||
<li> | ||
"Client", "You", and "Your" refer to you, the person using this website who is compliant with the Company's | ||
terms and conditions. | ||
</li> | ||
<li> | ||
"The Company", "Ourselves", "We", "Our", and "Us", refer to our Company. "Party", "Parties", or "Us", refers | ||
to both the Client and ourselves. | ||
</li> | ||
</ul> | ||
</> | ||
); | ||
|
||
const body = ( | ||
<> | ||
<h2>Introduction</h2> | ||
{introduction} | ||
<h2>Terminology</h2> | ||
{terminology} | ||
</> | ||
); | ||
|
||
return ( | ||
<> | ||
<SkipToContent style={{ zIndex: '999' }} onClick={handleSkipToContent} href="#"> | ||
Skip to chatbot | ||
</SkipToContent> | ||
<div | ||
style={{ | ||
position: 'fixed', | ||
padding: 'var(--pf-t--global--spacer--lg)', | ||
zIndex: '601', | ||
boxShadow: 'var(--pf-t--global--box-shadow--lg)' | ||
}} | ||
> | ||
<FormGroup role="radiogroup" isInline fieldId="basic-form-radio-group" label="Display mode"> | ||
<Radio | ||
isChecked={displayMode === ChatbotDisplayMode.default} | ||
onChange={() => setDisplayMode(ChatbotDisplayMode.default)} | ||
name="basic-inline-radio" | ||
label="Default" | ||
id="default" | ||
/> | ||
<Radio | ||
isChecked={displayMode === ChatbotDisplayMode.docked} | ||
onChange={() => setDisplayMode(ChatbotDisplayMode.docked)} | ||
name="basic-inline-radio" | ||
label="Docked" | ||
id="docked" | ||
/> | ||
<Radio | ||
isChecked={displayMode === ChatbotDisplayMode.fullscreen} | ||
onChange={() => setDisplayMode(ChatbotDisplayMode.fullscreen)} | ||
name="basic-inline-radio" | ||
label="Fullscreen" | ||
id="fullscreen" | ||
/> | ||
<Radio | ||
isChecked={displayMode === ChatbotDisplayMode.embedded} | ||
onChange={() => setDisplayMode(ChatbotDisplayMode.embedded)} | ||
name="basic-inline-radio" | ||
label="Embedded" | ||
id="embedded" | ||
/> | ||
</FormGroup> | ||
<Checkbox | ||
isChecked={hasImage} | ||
aria-label="Toggle whether terms and conditions has a header image" | ||
id="toggle-header-image" | ||
name="toggle-header-image" | ||
label="Has image in header" | ||
onChange={(_event, checked) => setHasImage(checked)} | ||
></Checkbox> | ||
<Button onClick={handleModalToggle}>Launch modal</Button> | ||
</div> | ||
<Chatbot ref={chatbotRef} displayMode={displayMode} isVisible></Chatbot> | ||
<TermsOfUse | ||
ref={termsRef} | ||
displayMode={displayMode} | ||
isModalOpen={isModalOpen} | ||
handleModalToggle={handleModalToggle} | ||
onPrimaryAction={onPrimaryAction} | ||
onSecondaryAction={onSecondaryAction} | ||
image={hasImage ? termsAndConditionsHeader : undefined} | ||
altText={hasImage ? 'Open book' : undefined} | ||
> | ||
{body} | ||
</TermsOfUse> | ||
</> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
.pf-chatbot__terms-of-use-modal { | ||
.pf-v6-c-content { | ||
font-size: var(--pf-t--global--font--size--body--lg); | ||
|
||
h2 { | ||
font-size: var(--pf-t--global--icon--size--font--heading--h2); | ||
font-family: var(--pf-t--global--font--family--heading); | ||
margin-bottom: var(--pf-t--global--spacer--md); | ||
margin-top: var(--pf-t--global--spacer--md); | ||
font-weight: var(--pf-t--global--font--weight--heading--default); | ||
} | ||
h2:first-of-type { | ||
margin-top: 0; | ||
} | ||
} | ||
|
||
.pf-chatbot__terms-of-use--header { | ||
display: flex; | ||
align-items: center; | ||
justify-content: center; | ||
flex-direction: column; | ||
gap: var(--pf-t--global--spacer--xl); | ||
margin-block-start: var(--pf-t--global--spacer--xl); | ||
} | ||
|
||
.pf-chatbot__terms-of-use--title { | ||
font-size: var(--pf-t--global--font--size--heading--h1); | ||
font-family: var(--pf-t--global--font--family--heading); | ||
font-weight: var(--pf-t--global--font--weight--heading--bold); | ||
} | ||
|
||
.pf-chatbot__terms-of-use--footer { | ||
margin-block-start: var(--pf-t--global--spacer--md); | ||
} | ||
|
||
.pf-chatbot__terms-of-use--section { | ||
display: flex; | ||
flex-direction: column; | ||
height: 100%; | ||
width: 100%; | ||
} | ||
|
||
// for handling zoom conditions; zoom to 125% or higher to see this | ||
@media screen and (max-height: 620px) { | ||
.pf-v6-c-modal-box__body { | ||
--pf-v6-c-modal-box__body--MinHeight: auto; | ||
overflow: visible; | ||
} | ||
} | ||
} | ||
|
||
.pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--fullscreen.pf-chatbot__terms-of-use-modal.pf-chatbot__terms-of-use-modal--fullscreen, | ||
.pf-chatbot__chatbot-modal.pf-chatbot__chatbot-modal--embedded.pf-chatbot__terms-of-use-modal.pf-chatbot__terms-of-use-modal--embedded { | ||
// override parent modal style | ||
height: inherit; | ||
|
||
.pf-v6-c-content { | ||
h2 { | ||
font-size: var(--pf-t--global--icon--size--font--heading--h1); | ||
} | ||
} | ||
|
||
.pf-chatbot__terms-of-use--title { | ||
font-size: var(--pf-t--global--font--size--heading--2xl); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import '@testing-library/jest-dom'; | ||
import userEvent from '@testing-library/user-event'; | ||
import TermsOfUse from './TermsOfUse'; | ||
import { Content } from '@patternfly/react-core'; | ||
|
||
const handleModalToggle = jest.fn(); | ||
const onPrimaryAction = jest.fn(); | ||
const onSecondaryAction = jest.fn(); | ||
|
||
const body = ( | ||
<Content> | ||
<h1>Heading 1</h1> | ||
<p>Legal text</p> | ||
</Content> | ||
); | ||
describe('TermsOfUse', () => { | ||
afterEach(() => { | ||
jest.clearAllMocks(); | ||
}); | ||
it('should render modal correctly', () => { | ||
render( | ||
<TermsOfUse | ||
isModalOpen | ||
onSecondaryAction={onSecondaryAction} | ||
handleModalToggle={handleModalToggle} | ||
ouiaId="Terms" | ||
> | ||
{body} | ||
</TermsOfUse> | ||
); | ||
expect(screen.getByRole('heading', { name: /Terms of use/i })).toBeTruthy(); | ||
expect(screen.getByRole('button', { name: /Accept/i })).toBeTruthy(); | ||
expect(screen.getByRole('button', { name: /Decline/i })).toBeTruthy(); | ||
expect(screen.getByRole('heading', { name: /Heading 1/i })).toBeTruthy(); | ||
expect(screen.getByText(/Legal text/i)).toBeTruthy(); | ||
expect(screen.getByRole('dialog')).toHaveClass('pf-chatbot__terms-of-use-modal'); | ||
expect(screen.getByRole('dialog')).toHaveClass('pf-chatbot__terms-of-use-modal--default'); | ||
}); | ||
it('should handle image and altText props', () => { | ||
render( | ||
<TermsOfUse | ||
isModalOpen | ||
onSecondaryAction={onSecondaryAction} | ||
handleModalToggle={handleModalToggle} | ||
image="./image.png" | ||
altText="Test image" | ||
> | ||
{body} | ||
</TermsOfUse> | ||
); | ||
expect(screen.getByRole('img')).toBeTruthy(); | ||
expect(screen.getByRole('img')).toHaveAttribute('alt', 'Test image'); | ||
}); | ||
it('should handle className prop', () => { | ||
render( | ||
<TermsOfUse | ||
isModalOpen | ||
onSecondaryAction={onSecondaryAction} | ||
handleModalToggle={handleModalToggle} | ||
className="test" | ||
> | ||
{body} | ||
</TermsOfUse> | ||
); | ||
expect(screen.getByRole('dialog')).toHaveClass('pf-chatbot__terms-of-use-modal'); | ||
expect(screen.getByRole('dialog')).toHaveClass('pf-chatbot__terms-of-use-modal--default'); | ||
expect(screen.getByRole('dialog')).toHaveClass('test'); | ||
}); | ||
it('should handle title prop', () => { | ||
render( | ||
<TermsOfUse | ||
isModalOpen | ||
onSecondaryAction={onSecondaryAction} | ||
handleModalToggle={handleModalToggle} | ||
title="Updated title" | ||
> | ||
{body} | ||
</TermsOfUse> | ||
); | ||
expect(screen.getByRole('heading', { name: /Updated title/i })).toBeTruthy(); | ||
expect(screen.queryByRole('heading', { name: /Terms of use/i })).toBeFalsy(); | ||
}); | ||
it('should handle primary button prop', () => { | ||
render( | ||
<TermsOfUse | ||
isModalOpen | ||
onSecondaryAction={onSecondaryAction} | ||
handleModalToggle={handleModalToggle} | ||
primaryActionBtn="First" | ||
> | ||
{body} | ||
</TermsOfUse> | ||
); | ||
expect(screen.getByRole('button', { name: /First/i })).toBeTruthy(); | ||
expect(screen.queryByRole('button', { name: /Accept/i })).toBeFalsy(); | ||
}); | ||
it('should handle secondary button prop', () => { | ||
render( | ||
<TermsOfUse | ||
isModalOpen | ||
onSecondaryAction={onSecondaryAction} | ||
handleModalToggle={handleModalToggle} | ||
secondaryActionBtn="Second" | ||
> | ||
{body} | ||
</TermsOfUse> | ||
); | ||
expect(screen.getByRole('button', { name: /Second/i })).toBeTruthy(); | ||
expect(screen.queryByRole('button', { name: /Deny/i })).toBeFalsy(); | ||
}); | ||
it('should handle primary button click', async () => { | ||
render( | ||
<TermsOfUse | ||
isModalOpen | ||
onPrimaryAction={onPrimaryAction} | ||
onSecondaryAction={onSecondaryAction} | ||
handleModalToggle={handleModalToggle} | ||
> | ||
{body} | ||
</TermsOfUse> | ||
); | ||
await userEvent.click(screen.getByRole('button', { name: /Accept/i })); | ||
expect(onPrimaryAction).toHaveBeenCalledTimes(1); | ||
expect(handleModalToggle).toHaveBeenCalledTimes(1); | ||
}); | ||
it('should handle secondary button click', async () => { | ||
render( | ||
<TermsOfUse isModalOpen onSecondaryAction={onSecondaryAction} handleModalToggle={handleModalToggle}> | ||
{body} | ||
</TermsOfUse> | ||
); | ||
await userEvent.click(screen.getByRole('button', { name: /Decline/i })); | ||
expect(onSecondaryAction).toHaveBeenCalledTimes(1); | ||
expect(handleModalToggle).not.toHaveBeenCalled(); | ||
}); | ||
}); |
Oops, something went wrong.