Skip to content

Commit c839dbc

Browse files
author
Luke Bowerman
authored
Prompt clean-up & improvements (#891)
* Prompt should clear value when it's closed * Prompt should clear value when it's closed * Remove <form> within Prompt to avoid conflict with other onSubmit handlers. Instead monitor for 'Enter' and 'Escape' and act accordingly * Changed PromptDemo's onCancel callback
1 parent eace8a0 commit c839dbc

File tree

4 files changed

+126
-46
lines changed

4 files changed

+126
-46
lines changed

CHANGELOG.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818

1919
### Changed
2020

21-
- updated documentation for Filedset to include label and legend
21+
- `Fieldset` - updated documentation to include label and legend
2222
- `menuItemStyleContext` in `MenuContext` uses a new interface which contains "preserved icon space"-related properties
2323
- `MenuItem` renders an empty Box with the same size as the icon(s) of sibling `MenuItem's (if any)
2424
- `MenuList`, `MenuGroup` contain piece of state the tracks the size of the preserved icon space
@@ -27,7 +27,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
### Fixed
2828

2929
- `ToggleSwitch` React warning
30-
- add placeholder attribute to InlineInputText
30+
- `Prompt`
31+
- Clears out old `defaultValue` when new `defaultValue` is passed in
32+
- Clears out any user input after pressing cancel button
33+
- Updated test suite to prevent future regressions for the above fixes
34+
- `InlineInputText` add placeholder attribute
3135

3236
### Removed
3337

packages/components/src/Modal/Prompt/Prompt.test.tsx

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
*/
2626

2727
import { fireEvent } from '@testing-library/react'
28-
import React from 'react'
28+
import React, { useState } from 'react'
2929

3030
import { renderWithTheme } from '@looker/components-test-utils'
3131
import { semanticColors, SemanticColors } from '@looker/design-tokens'
@@ -52,22 +52,27 @@ afterEach(() => {
5252
})
5353

5454
test('<Prompt/> with defaults', () => {
55-
const { getByText, queryByText } = renderWithTheme(
55+
const { getByText, getByPlaceholderText, queryByText } = renderWithTheme(
5656
<Prompt {...requiredProps}>
57-
{(open) => <Button onClick={open}>Sesame</Button>}
57+
{(open) => <Button onClick={open}>Open Prompt</Button>}
5858
</Prompt>
5959
)
6060

61-
const opener = getByText('Sesame')
61+
const opener = getByText('Open Prompt')
6262
fireEvent.click(opener)
6363

64-
const button = getByText('Save')
64+
const saveButton = getByText('Save')
65+
const input = getByPlaceholderText(requiredProps.inputLabel)
6566

66-
expect(getByText(requiredProps.inputLabel)).toBeVisible()
67+
expect(input).toBeVisible()
6768
expect(getByText(requiredProps.title)).toBeVisible()
68-
expect(button).toHaveStyle(`background: ${semanticColors.primary.main}`)
69+
expect(saveButton).toHaveStyle(`background: ${semanticColors.primary.main}`)
70+
71+
fireEvent.click(saveButton)
72+
expect(requiredProps.onSave).toHaveBeenCalledTimes(0)
6973

70-
fireEvent.submit(button)
74+
fireEvent.change(input, { target: { value: 'Has Text In It' } })
75+
fireEvent.click(saveButton)
7176
expect(requiredProps.onSave).toHaveBeenCalledTimes(1)
7277

7378
expect(queryByText(requiredProps.inputLabel)).toBeNull()
@@ -77,11 +82,11 @@ test('<Prompt/> with defaults', () => {
7782
test('<Prompt/> with custom props', () => {
7883
const { getByDisplayValue, getByText } = renderWithTheme(
7984
<Prompt {...optionalProps} {...requiredProps}>
80-
{(open) => <Button onClick={open}>Sesame</Button>}
85+
{(open) => <Button onClick={open}>Open Prompt</Button>}
8186
</Prompt>
8287
)
8388

84-
const opener = getByText('Sesame')
89+
const opener = getByText('Open Prompt')
8590
fireEvent.click(opener)
8691

8792
const saveButton = getByText(optionalProps.saveLabel)
@@ -98,3 +103,53 @@ test('<Prompt/> with custom props', () => {
98103
expect(optionalProps.onCancel).toHaveBeenCalledTimes(1)
99104
expect(requiredProps.onSave).toHaveBeenCalledTimes(0)
100105
})
106+
107+
test('<Prompt /> clears value after closing', () => {
108+
const { getByText, getByPlaceholderText } = renderWithTheme(
109+
<Prompt {...requiredProps}>
110+
{(open) => <Button onClick={open}>Open Prompt</Button>}
111+
</Prompt>
112+
)
113+
114+
const opener = getByText('Open Prompt')
115+
fireEvent.click(opener)
116+
117+
const cancelButton = getByText('Cancel')
118+
let input = getByPlaceholderText(requiredProps.inputLabel)
119+
120+
fireEvent.change(input, { target: { value: 'Hello World' } })
121+
expect(input).toHaveValue('Hello World')
122+
fireEvent.click(cancelButton)
123+
124+
fireEvent.click(opener)
125+
// Note: Need to requery for the input; not doing so results in stale value on the input element
126+
input = getByPlaceholderText(requiredProps.inputLabel)
127+
expect(input).toHaveValue('')
128+
})
129+
130+
test('<Prompt /> updates when defaultValue changes', () => {
131+
const PromptTest = () => {
132+
const [defaultValue, setDefaultValue] = useState('Gouda')
133+
134+
return (
135+
<>
136+
<Prompt {...requiredProps} defaultValue={defaultValue}>
137+
{(open) => <Button onClick={open}>Open Prompt</Button>}
138+
</Prompt>
139+
<Button onClick={() => setDefaultValue('Swiss')}>
140+
Set Default Value to Swiss
141+
</Button>
142+
</>
143+
)
144+
}
145+
146+
const { getByText, getByDisplayValue } = renderWithTheme(<PromptTest />)
147+
148+
fireEvent.click(getByText('Open Prompt'))
149+
getByDisplayValue('Gouda')
150+
fireEvent.click(getByText('Cancel'))
151+
152+
fireEvent.click(getByText('Set Default Value to Swiss'))
153+
fireEvent.click(getByText('Open Prompt'))
154+
getByDisplayValue('Swiss')
155+
})

packages/components/src/Modal/Prompt/PromptDialog.tsx

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,15 @@
2424
2525
*/
2626

27-
import React, { FC, FormEvent, ReactNode, useState, useCallback } from 'react'
27+
import React, {
28+
FC,
29+
FormEvent,
30+
KeyboardEvent,
31+
ReactNode,
32+
useState,
33+
useCallback,
34+
useEffect,
35+
} from 'react'
2836
import { SemanticColors } from '@looker/design-tokens'
2937
import { Button, ButtonTransparent } from '../../Button'
3038
import { Label, InputText } from '../../Form'
@@ -45,7 +53,7 @@ export interface PromptBaseProps {
4553
/**
4654
* Callback if user clicks Cancel button or closes the dialog
4755
*/
48-
onCancel?: (close: () => void) => void
56+
onCancel?: () => void
4957
/**
5058
* Callback that is triggered when submit button is pressed
5159
*/
@@ -101,49 +109,63 @@ export const PromptDialog: FC<PromptDialogProps> = ({
101109
const [value, setValue] = useState(defaultValue)
102110
const hasValue = !!value.trim()
103111

112+
useEffect(() => {
113+
setValue(defaultValue)
114+
}, [defaultValue])
115+
104116
const onChange = (event: FormEvent<HTMLInputElement>) => {
105117
setValue(event.currentTarget.value)
106118
}
107119

108-
const onSubmit = (event: FormEvent<HTMLElement>) => {
109-
event.preventDefault()
120+
const onSubmit = () => {
110121
onSave(value)
111122
close()
123+
setValue('')
112124
}
113125

114126
const cancel = useCallback(() => {
115-
if (onCancel) {
116-
onCancel(close)
117-
} else {
118-
close()
119-
}
127+
close()
128+
setValue('')
129+
onCancel && onCancel()
120130
}, [close, onCancel])
121131

132+
const onKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
133+
if (event.key === 'Enter' && hasValue) {
134+
onSubmit()
135+
} else if (event.key === 'Escape') {
136+
cancel()
137+
}
138+
}
139+
122140
return (
123141
<Dialog width="30rem" isOpen={isOpen} onClose={cancel}>
124-
<form onSubmit={onSubmit}>
125-
<ModalHeader hideClose>{title}</ModalHeader>
126-
<ModalContent>
127-
<VisuallyHidden>
128-
<Label htmlFor="promptInput">{inputLabel}</Label>
129-
</VisuallyHidden>
130-
<InputText
131-
id="promptInput"
132-
placeholder={inputLabel}
133-
onChange={onChange}
134-
width="100%"
135-
value={value}
136-
/>
137-
</ModalContent>
138-
<ModalFooter secondary={secondary}>
139-
<Button disabled={!hasValue} type="submit" color="primary">
140-
{saveLabel}
141-
</Button>
142-
<ButtonTransparent type="reset" color={cancelColor} onClick={cancel}>
143-
{cancelLabel}
144-
</ButtonTransparent>
145-
</ModalFooter>
146-
</form>
142+
<ModalHeader hideClose>{title}</ModalHeader>
143+
<ModalContent>
144+
<VisuallyHidden>
145+
<Label htmlFor="promptInput">{inputLabel}</Label>
146+
</VisuallyHidden>
147+
<InputText
148+
onKeyDown={onKeyDown}
149+
id="promptInput"
150+
placeholder={inputLabel}
151+
onChange={onChange}
152+
width="100%"
153+
value={value}
154+
/>
155+
</ModalContent>
156+
<ModalFooter secondary={secondary}>
157+
<Button
158+
disabled={!hasValue}
159+
type="submit"
160+
onClick={onSubmit}
161+
color="primary"
162+
>
163+
{saveLabel}
164+
</Button>
165+
<ButtonTransparent type="reset" color={cancelColor} onClick={cancel}>
166+
{cancelLabel}
167+
</ButtonTransparent>
168+
</ModalFooter>
147169
</Dialog>
148170
)
149171
}

packages/playground/src/Dialog/PromptDemo.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ export const PromptDemo: FC = () => {
3434
title={'Choose a cheese!'}
3535
inputLabel={'Name of Cheese'}
3636
saveLabel={'Save'}
37-
onCancel={(close: () => void) => {
37+
onCancel={() => {
3838
alert('Prompt closed')
39-
close()
4039
}}
4140
onSave={(value: string) => alert(`You chose ${value}`)}
4241
secondary={

0 commit comments

Comments
 (0)