Skip to content

Commit 95bcc47

Browse files
committed
fix: switch from Tile UI to Select UI for <Ask/>
1 parent f99f56d commit 95bcc47

File tree

2 files changed

+117
-98
lines changed

2 files changed

+117
-98
lines changed

Diff for: plugins/plugin-codeflare/src/components/Ask.tsx

+116-76
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ import {
3030
Grid,
3131
Form,
3232
FormGroup,
33-
SimpleList,
34-
SimpleListItem,
33+
Select,
34+
SelectGroup,
35+
SelectOption,
36+
SelectOptionObject,
3537
TextInput,
36-
Tile,
3738
} from "@patternfly/react-core"
3839

3940
import HomeIcon from "@patternfly/react-icons/dist/esm/icons/home-icon"
@@ -129,36 +130,11 @@ export default class AskUI extends React.PureComponent<Props, State> {
129130
<CardActions hasNoOffset>{this.actions()}</CardActions>
130131
</CardHeader>
131132

132-
<CardBody className="scrollable scrollable-auto" style={{ paddingTop: "1em" }}>
133-
{body}
134-
</CardBody>
133+
<CardBody>{body}</CardBody>
135134
</Card>
136135
)
137136
}
138137

139-
/** Render a simplified set of choices where the message is the same as the title */
140-
private select(ask: Ask<Prompts.Select>) {
141-
return this.card(
142-
ask.title,
143-
<SimpleList>
144-
{ask.prompt.choices.map((_) => {
145-
const isSuggested = this.state?.userSelection === _.name
146-
return (
147-
<SimpleListItem
148-
key={_.name}
149-
itemId={_.name}
150-
data-name={_.name}
151-
isActive={isSuggested}
152-
onClick={this._onSimpleListClick}
153-
>
154-
{_.name}
155-
</SimpleListItem>
156-
)
157-
})}
158-
</SimpleList>
159-
)
160-
}
161-
162138
private justTheMessage(choice: Ask["prompt"]["choices"][number]) {
163139
return stripAnsi(choice.message).replace(" ◄ you selected this last time", "")
164140
}
@@ -183,60 +159,124 @@ export default class AskUI extends React.PureComponent<Props, State> {
183159
return false
184160
}
185161

186-
/** User has clicked on a tile */
187-
private readonly _onTileClick = (evt: React.MouseEvent) => {
188-
const name = evt.currentTarget.getAttribute("data-name")
189-
if (name && this.props.ask) {
162+
/** User has clicked on a SelectOption */
163+
private readonly _onSelect = (
164+
evt: React.MouseEvent | React.ChangeEvent,
165+
selection: string | SelectOptionObject,
166+
isPlaceholder?: boolean
167+
) => {
168+
if (!isPlaceholder && selection && this.props.ask) {
169+
const name = selection.toString()
190170
this.setState({ userSelection: name })
191171
this.props.ask.onChoose(Promise.resolve(name))
192172
}
193173
}
194174

195-
private tiles(ask: Ask<Prompts.Select>) {
196-
// is every message the same as the title?
197-
const isSimplistic = ask.prompt.choices.every(
198-
(_) => _.name === stripAnsi(_.message).replace(" ◄ you selected this last time", "")
175+
private readonly _selectOptionForChoice = (
176+
_: Ask<Prompts.Select>["prompt"]["choices"][number],
177+
isFocused = false
178+
) => {
179+
const message = this.justTheMessage(_)
180+
const isSuggested = this.state?.userSelection === _.name
181+
const description = (
182+
<React.Fragment>
183+
{" "}
184+
{message !== _.name && (
185+
<div>
186+
<Ansi noWrap="normal" className="sans-serif">
187+
{_.message.split(/\n/).slice(-2)[0]}
188+
</Ansi>
189+
</div>
190+
)}
191+
{isSuggested && (
192+
<div className="top-pad color-base0D">
193+
<InfoIcon /> You selected this last time
194+
</div>
195+
)}
196+
</React.Fragment>
199197
)
200-
if (isSimplistic) {
201-
return this.select(ask)
198+
199+
// re: className: pf-m-focus; patternfly seems to have a bug with isGrouped Select and isFocused SelectOptions
200+
return (
201+
<SelectOption
202+
key={_.name}
203+
id={_.name}
204+
value={_.name}
205+
description={description}
206+
isFocused={isFocused}
207+
className={isFocused ? "pf-m-focus" : undefined}
208+
>
209+
{_.name}
210+
</SelectOption>
211+
)
212+
}
213+
214+
/** PatternFly's <Select> requires an onToggle, but we want the Select to remain ever-open */
215+
// eslint-disable-next-line @typescript-eslint/no-empty-function
216+
private readonly _doNothing = () => {
217+
// Intentionally empty
218+
}
219+
220+
/** Render a UI for making a selection */
221+
private select(ask: Ask<Prompts.Select>) {
222+
const suggested = ask.prompt.choices.find((_) => _.name === this.state?.userSelection)
223+
224+
// present a filtered list of options; note that we filter based
225+
// on the full "message" field, which includes both the title
226+
// (_.name) and the description (which is buried inside of
227+
// _.message
228+
const mkOptions = (filter = "") => {
229+
const pattern = new RegExp(filter, "i")
230+
231+
// options other than the "suggested" (i.e. prior choice)
232+
const others = ask.prompt.choices
233+
.filter((_) => _.name !== this.state?.userSelection && (!filter || pattern.test(_.message)))
234+
.map((_) => this._selectOptionForChoice(_))
235+
236+
// ugh, a bit of syntactic garbage here to make typescript
237+
// happy. this is just creating a filtered list of two groups:
238+
// Prior choice and Other choices, but either one may be empty
239+
// due to the filtering, or the lack of a prior choice
240+
return [
241+
...(suggested && (!filter || pattern.test(suggested.message))
242+
? [<SelectGroup label="Prior choice">{this._selectOptionForChoice(suggested, true)}</SelectGroup>]
243+
: []),
244+
...(others.length > 0
245+
? [<SelectGroup label={suggested ? "Other choices" : "Choices"}>{others}</SelectGroup>]
246+
: []),
247+
]
202248
}
203249

204-
return this.card(
205-
ask.title,
206-
<Grid hasGutter md={3}>
207-
{ask.prompt.choices.map((_) => {
208-
const message = this.justTheMessage(_)
209-
const isSuggested = this.state?.userSelection === _.name
210-
211-
//icon={isSuggested && <Icons icon="Info"/>}
212-
213-
return (
214-
<Tile
215-
className="kui--guide-tile"
216-
isSelected={isSuggested}
217-
key={_.name}
218-
title={_.name}
219-
data-name={_.name}
220-
data-large={!isSimplistic || undefined}
221-
isStacked
222-
onClick={this._onTileClick}
223-
>
224-
{message !== _.name && (
225-
<div>
226-
<Ansi noWrap="normal" className="sans-serif">
227-
{_.message.split(/\n/).slice(-2)[0]}
228-
</Ansi>
229-
</div>
230-
)}
231-
{isSuggested && (
232-
<div className="top-pad color-base0D">
233-
<InfoIcon /> You selected this last time
234-
</div>
235-
)}
236-
</Tile>
237-
)
238-
})}
239-
</Grid>
250+
const onFilter = (evt: React.ChangeEvent | null, filter: string) => mkOptions(filter)
251+
252+
const placeholderText = "Select an option"
253+
const titleId = "kui--madwizard-ask-ui-title"
254+
255+
const props = {
256+
isOpen: true,
257+
isGrouped: true,
258+
hasInlineFilter: true,
259+
isInputValuePersisted: true,
260+
isInputFilterPersisted: true,
261+
// variant: "typeahead" as const,
262+
// typeAheadAriaLabel: "Select an option",
263+
onFilter,
264+
"aria-labelledby": titleId,
265+
noResultsFoundText: "No matching choices",
266+
placeholderText,
267+
onSelect: this._onSelect,
268+
onToggle: this._doNothing,
269+
toggleIndicator: <React.Fragment />,
270+
children: mkOptions(),
271+
}
272+
273+
return (
274+
<React.Fragment>
275+
<span id={titleId} hidden>
276+
{placeholderText}
277+
</span>
278+
{this.card(ask.title, <Select {...props} />)}
279+
</React.Fragment>
240280
)
241281
}
242282

@@ -290,7 +330,7 @@ export default class AskUI extends React.PureComponent<Props, State> {
290330

291331
private ask(ask: Ask) {
292332
if (this.isSelect(ask)) {
293-
return this.tiles(ask)
333+
return this.select(ask)
294334
} else if (this.isMultiSelect(ask)) {
295335
return this.checkboxes(ask)
296336
} else {

Diff for: plugins/plugin-codeflare/web/scss/components/Ask.scss

+1-22
Original file line numberDiff line numberDiff line change
@@ -17,34 +17,13 @@
1717
@import "mixins";
1818

1919
@include Ask {
20-
.kui--guide-tile {
21-
&[data-large] {
22-
--kui-font-factor: 1.25;
23-
.pf-c-tile__title {
24-
font-size: 1.125rem;
25-
line-height: 1.375rem;
26-
padding-bottom: 0.25rem;
27-
}
28-
.pf-c-tile__body {
29-
font-size: 0.875rem;
30-
}
31-
}
32-
}
33-
34-
button:not(.pf-m-link) {
20+
button:not(.pf-m-link):not(.pf-c-select__toggle-button) {
3521
color: var(--pf-c-button--m-primary--Color) !important;
3622
}
3723

3824
&.pf-c-empty-state.pf-m-xs .pf-c-button.larger-text {
3925
font-size: 1.5rem;
4026
}
41-
42-
.pf-c-tile__body {
43-
color: var(--color-text-02);
44-
p {
45-
font-size: var(--pf-c-tile__body--FontSize) !important;
46-
}
47-
}
4827
}
4928

5029
@include Ask {

0 commit comments

Comments
 (0)