diff --git a/pkg/lib/cockpit-components-multi-typeahead-select.tsx b/pkg/lib/cockpit-components-multi-typeahead-select.tsx index a8bc7dc236da..b5709e52d3b3 100644 --- a/pkg/lib/cockpit-components-multi-typeahead-select.tsx +++ b/pkg/lib/cockpit-components-multi-typeahead-select.tsx @@ -156,7 +156,9 @@ export const MultiTypeaheadSelectBase: React.FunctionComponent. + */ + +import cockpit from "cockpit"; + +import React, { useState } from "react"; +import { createRoot, Container } from 'react-dom/client'; + +import { Checkbox } from '@patternfly/react-core'; +import { MultiTypeaheadSelect, MultiTypeaheadSelectOption } from "cockpit-components-multi-typeahead-select"; + +const MultiTypeaheadDemo = ({ options } : { options: MultiTypeaheadSelectOption[] }) => { + const [notFoundIsString, setNotFoundIsString] = useState(false); + const [selected, setSelected] = useState<(string | number)[]>([]); + const [toggles, setToggles] = useState(0); + const [changes, setChanges] = useState(0); + + function add(val: string | number) { + setSelected(selected.concat([val])); + } + + function rem(val: string | number) { + setSelected(selected.filter(v => v != val)); + } + + return ( +
+ cockpit.format("'$0' not found", val) } + options={options} + selected={selected} + onAdd={add} + onRemove={rem} + onToggle={() => setToggles(val => val + 1)} + onInputChange={() => setChanges(val => val + 1)} + /> +
Selected: {JSON.stringify(selected)}
+
Toggles: {toggles}
+
Changes: {changes}
+ setNotFoundIsString(checked)} + /> +
+ ); +}; + +export function showMultiTypeaheadDemo(rootElement: Container) { + const flavors: string[] = [ + "Alumni Swirl", + "Apple Cobbler Crunch", + "Arboretum Breeze", + "August Pie", + "Autumn Delight", + "Bavarian Raspberry Crunch", + "Berkey Brickle", + "Birthday Bash", + "Bittersweet Mint", + "Black Cow", + "Black Raspberry", + "Blueberry Cheesecake", + "Butter Pecan", + "Candy Bar/Snickers", + "Caramel Critters", + "Centennial Vanilla Bean", + "Cherry Cheesecake", + "Cherry Chip", + "Cherry Quist", + "Cherry Sherbet", + "Chocolate", + "Chocolate Cherry Cordia", + "Chocolate Chip", + "Chocolate Chip Cheesecake", + "Chocolate Chip Cookie Dough", + "Chocolate Chocolate Nut", + "Chocolate Marble", + "Chocolate Marshmallow", + "Chocolate Pretzel Crunch", + "Chunky Chocolate", + "Chunky Chocolate- Vanilla", + "Coconut Chip", + "Coffee Mocha Fudge", + "Coffee w/Cream and Sugar", + "Crazy Charlie Sundae Swirl", + "Death By Chocolate", + "Egg Nog", + "Espresso Fudge Pie", + "German Chocolate Cake", + "Golden Chocolate Pecan", + "Goo Goo Cluster", + "Grape Sherbet", + "Happy Happy Joy Joy", + "Heath Bar Candy", + "Just Fudge", + "Kenney Beany Chocolate", + "Lion Tracks", + "LionS'more", + "Mallo Cup", + "Maple Nut", + "Mint Nittany", + "Monster Mash", + "Orange Vanilla Sundae", + "Palmer Mousseum With Almonds", + "Peachy Paterno", + "Peanut Butter Cup", + "Peanut Butter Fudge Cluster", + "Peanut Butter Marshmallow", + "Peanut Butter Swirl", + "Pecan Apple Danish", + "Peppermint Stick", + "Pistachio", + "Pralines N Cream", + "Pumpkin Pie", + "Raspberry Fudge Torte", + "Raspberry Parfait", + "Rum Raisin", + "Russ 'Digs' Roseberry", + "Santa Fe Banana", + "Scholar's Chip", + "Sea Salt Chocolate Caramel", + "Somerset Shortcake", + "Southern Chocolate Pie", + "Southern Pecan Cheesecake", + "Strawberry", + "Strawberry Cheesecake", + "Teaberry", + "Tin Roof Sundae", + "Toasted Almond", + "Toasted Almond Fudge", + "Turtle Creek", + "Vanilla", + "White House", + "Wicked Caramel Sundae", + "WPSU Coffee Break", + ]; + + const options: MultiTypeaheadSelectOption[] = flavors.map((f, i) => ({ value: i + 1, content: f })); + + const root = createRoot(rootElement); + root.render(); +} diff --git a/pkg/playground/react-patterns.html b/pkg/playground/react-patterns.html index 378d9ffd3ec5..0ef97bb56b14 100644 --- a/pkg/playground/react-patterns.html +++ b/pkg/playground/react-patterns.html @@ -23,6 +23,11 @@

Typeahead

+
+

Multi Typeahead

+
+
+

Dialogs

diff --git a/pkg/playground/react-patterns.js b/pkg/playground/react-patterns.js index 712d059f0fa2..f8835d4987d8 100644 --- a/pkg/playground/react-patterns.js +++ b/pkg/playground/react-patterns.js @@ -31,6 +31,7 @@ import { showCardsDemo } from "./react-demo-cards.jsx"; import { showUploadDemo } from "./react-demo-file-upload.jsx"; import { showFileAcDemo, showFileAcDemoPreselected } from "./react-demo-file-autocomplete.jsx"; import { showTypeaheadDemo } from "./react-demo-typeahead.jsx"; +import { showMultiTypeaheadDemo } from "./react-demo-multi-typeahead.jsx"; /* ----------------------------------------------------------------------------- Modal Dialog @@ -130,6 +131,9 @@ document.addEventListener("DOMContentLoaded", function() { // Plain typeahead select with headers and dividers showTypeaheadDemo(document.getElementById('demo-typeahead')); + // Multi typeahead + showMultiTypeaheadDemo(document.getElementById('demo-multi-typeahead')); + // Cards showCardsDemo(document.getElementById('demo-cards')); diff --git a/test/reference b/test/reference index b8704b853a9e..d71be06ea649 160000 --- a/test/reference +++ b/test/reference @@ -1 +1 @@ -Subproject commit b8704b853a9ebd0f137c55ea95bc2e8b818a3148 +Subproject commit d71be06ea64970157dc2a196b4d664ae66d9067d diff --git a/test/verify/check-lib b/test/verify/check-lib index 9fa192947517..3c951e23e39c 100755 --- a/test/verify/check-lib +++ b/test/verify/check-lib @@ -131,6 +131,112 @@ class TestLib(testlib.MachineCase): b.wait_text("#toggles", "14") b.wait_text("#changes", "26") + def testMultiTypeaheadSelect(self): + b = self.browser + + # Login + + self.login_and_go("/playground/react-patterns") + + # No clear button + + b.wait_not_visible("#demo-multi-typeahead .pf-v5-c-text-input-group__utilities button") + + # Open menu, pixel test + + b.click("#demo-multi-typeahead .pf-v5-c-menu-toggle__button") + b.assert_pixels("#multi-typeahead-widget", "menu") + b.wait_text("#multi-toggles", "1") + b.wait_text("#multi-changes", "0") + + # Select from menu (with mouse) + + b.click("#multi-typeahead-widget .pf-v5-c-menu__item:contains(Strawberry Cheesecake)") + b.wait_not_present("#multi-typeahead-widget") + b.assert_pixels("#demo-multi-typeahead .pf-v5-c-menu-toggle", "input") + b.wait_text("#multi-value", "[76]") + b.wait_text("#multi-toggles", "2") + b.wait_text("#multi-changes", "0") + + # Select another one + + b.click("#demo-multi-typeahead .pf-v5-c-menu-toggle__button") + b.wait_visible("#multi-typeahead-widget") + b.click("#multi-typeahead-widget .pf-v5-c-menu__item:contains(Coconut Chip)") + b.wait_not_present("#multi-typeahead-widget") + b.wait_text("#multi-value", "[76,32]") + b.wait_text("#multi-toggles", "4") + b.wait_text("#multi-changes", "0") + + # Remove one + + b.click("#demo-multi-typeahead .pf-v5-c-label:contains(Strawberry Cheesecake) button") + b.wait_text("#multi-value", "[32]") + b.wait_text("#multi-toggles", "4") + b.wait_text("#multi-changes", "0") + + # Open by clicking into input, close with ESC + + b.click("#demo-multi-typeahead .pf-v5-c-text-input-group__text-input") + b.wait_visible("#multi-typeahead-widget") + b.key("Escape") + b.wait_not_present("#multi-typeahead-widget") + b.wait_text("#multi-toggles", "6") + b.wait_text("#multi-changes", "0") + + # Select with keys + + b.click("#demo-multi-typeahead .pf-v5-c-menu-toggle__button") + b.wait_visible("#multi-typeahead-widget") + b.key("ArrowDown") # Alumni Swirl + b.key("ArrowUp") # Wraps, WPSU Coffee break + b.key("ArrowDown") # Wraps + b.key("ArrowDown") + b.key("ArrowDown") + b.key("ArrowDown") + b.key("ArrowDown") # Autumn Delight + b.key("Enter") + b.key("Escape") + b.wait_not_present("#multi-typeahead-widget") + b.wait_text("#multi-value", "[32,5]") + b.wait_text("#multi-toggles", "8") + b.wait_text("#multi-changes", "0") + + b.wait_visible("#demo-multi-typeahead .pf-v5-c-label:contains(Coconut Chip)") + b.wait_visible("#demo-multi-typeahead .pf-v5-c-label:contains(Autumn Delight)") + + # Search for non-existent + + b.click("#demo-multi-typeahead .pf-v5-c-text-input-group__text-input") + b.set_input_text("#demo-multi-typeahead .pf-v5-c-text-input-group__text-input", "Salmiak") + b.wait_text("#multi-typeahead-widget .pf-v5-c-menu__item", "'Salmiak' not found") + b.click("#demo-multi-typeahead .pf-v5-c-menu-toggle__button") + b.wait_not_present("#multi-typeahead-widget") + b.wait_text("#multi-toggles", "10") + b.wait_text("#multi-changes", "7") + + # Again with formatted "not found" message + + b.set_checked("#notFoundIsStringMulti", val=True) + b.click("#demo-multi-typeahead .pf-v5-c-text-input-group__text-input") + b.set_input_text("#demo-multi-typeahead .pf-v5-c-text-input-group__text-input", "Salmiaki") + b.wait_text("#multi-typeahead-widget .pf-v5-c-menu__item", "Not found") + b.click("#demo-multi-typeahead .pf-v5-c-menu-toggle__button") + b.wait_not_present("#multi-typeahead-widget") + b.wait_text("#multi-toggles", "12") + b.wait_text("#multi-changes", "15") + + # Search for existing, pixel test, select + + b.click("#demo-multi-typeahead .pf-v5-c-text-input-group__text-input") + b.set_input_text("#demo-multi-typeahead .pf-v5-c-text-input-group__text-input", "Rum") + b.wait_visible("#multi-typeahead-widget") + b.click("#multi-typeahead-widget .pf-v5-c-menu__item") + b.wait_not_present("#multi-typeahead-widget") + b.wait_text("#multi-value", "[32,5,67]") + b.wait_text("#multi-toggles", "14") + b.wait_text("#multi-changes", "18") + if __name__ == '__main__': testlib.test_main()