Skip to content

Commit 17b1361

Browse files
committed
chore: pf-search-input component created
1 parent cc76f5a commit 17b1361

File tree

8 files changed

+247
-0
lines changed

8 files changed

+247
-0
lines changed

elements/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"./pf-progress-stepper/pf-progress-step.js": "./pf-progress-stepper/pf-progress-step.js",
4646
"./pf-progress-stepper/pf-progress-stepper.js": "./pf-progress-stepper/pf-progress-stepper.js",
4747
"./pf-progress/pf-progress.js": "./pf-progress/pf-progress.js",
48+
"./pf-search-input/pf-search-input.js": "./pf-search-input/pf-search-input.js",
4849
"./pf-spinner/pf-spinner.js": "./pf-spinner/pf-spinner.js",
4950
"./pf-switch/pf-switch.js": "./pf-switch/pf-switch.js",
5051
"./pf-table/pf-table.js": "./pf-table/pf-table.js",

elements/pf-search-input/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Search Input
2+
Add a description of the component here.
3+
4+
## Usage
5+
Describe how best to use this web component along with best practices.
6+
7+
```html
8+
<pf-search-input>
9+
10+
</pf-search-input>
11+
```
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<pf-search-input></pf-search-input>
2+
3+
<script type="module">
4+
import '@patternfly/elements/pf-search-input/pf-search-input.js';
5+
</script>
6+
7+
<style>
8+
pf-search-input {
9+
/* insert demo styles */
10+
}
11+
</style>
12+
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{% renderOverview %}
2+
<pf-search-input></pf-search-input>
3+
{% endrenderOverview %}
4+
5+
{% band header="Usage" %}{% endband %}
6+
7+
{% renderSlots %}{% endrenderSlots %}
8+
9+
{% renderAttributes %}{% endrenderAttributes %}
10+
11+
{% renderMethods %}{% endrenderMethods %}
12+
13+
{% renderEvents %}{% endrenderEvents %}
14+
15+
{% renderCssCustomProperties %}{% endrenderCssCustomProperties %}
16+
17+
{% renderCssParts %}{% endrenderCssParts %}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:host {
2+
display: block;
3+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { LitElement, html, type TemplateResult } from 'lit';
2+
import { customElement } from 'lit/decorators/custom-element.js';
3+
import { ComboboxController } from '@patternfly/pfe-core/controllers/combobox-controller.js';
4+
import { query } from 'lit/decorators/query.js';
5+
import { property } from 'lit/decorators/property.js';
6+
import { observes } from '@patternfly/pfe-core/decorators/observes.js';
7+
import {
8+
FloatingDOMController,
9+
type Placement,
10+
} from '@patternfly/pfe-core/controllers/floating-dom-controller.js';
11+
import '@patternfly/elements/pf-text-input/pf-text-input.js';
12+
13+
import styles from './pf-search-input.css';
14+
15+
/**
16+
* Search Input
17+
* @slot - Place element content here
18+
*/
19+
@customElement('pf-search-input')
20+
export class PfSearchInput extends LitElement {
21+
static readonly styles: CSSStyleSheet[] = [styles];
22+
23+
@property({ type: Boolean, reflect: true }) expanded = false;
24+
@property({ reflect: true }) position: Placement = 'bottom';
25+
@property({ attribute: 'enable-flip', type: Boolean }) enableFlip = false;
26+
27+
@query('#listbox') listbox!: HTMLElement;
28+
@query('#button') button!: HTMLButtonElement;
29+
@query('#combobox') combobox!: HTMLInputElement;
30+
@query('#placeholder') placeholder!: HTMLOptionElement;
31+
@query('#listbox-container') private _listboxContainer?: HTMLElement;
32+
33+
34+
#float = new FloatingDOMController(this, { content: () => this._listboxContainer });
35+
36+
37+
static template: TemplateResult<1> = html`
38+
<pf-text-input-autocomplete></pf-text-input-autocomplete>`;
39+
40+
controller: ComboboxController<HTMLOptionElement> = ComboboxController.of(this, {
41+
multi: false,
42+
getItems: () => this.options,
43+
isItem: item => item instanceof HTMLOptionElement,
44+
getFallbackLabel: () => 'options',
45+
getListboxElement: () => this.listbox ?? null,
46+
getToggleButton: () => this.button ?? null,
47+
getComboboxInput: () => this.combobox ?? null,
48+
isExpanded: () => this.expanded,
49+
requestShowListbox: () => void (this.expanded ||= true),
50+
requestHideListbox: () => void (this.expanded &&= false),
51+
setItemActive: (item, active) => item.classList.toggle('active', active),
52+
setItemSelected: (item, selected) => item.selected = selected,
53+
});
54+
55+
56+
/** List of options */
57+
get options(): HTMLOptionElement[] {
58+
return [
59+
...new Set([
60+
this.placeholder,
61+
...this.querySelectorAll('option'),
62+
...this.renderRoot.querySelectorAll('option'),
63+
]),
64+
].filter(x => !!x);
65+
}
66+
67+
get selected(): HTMLOptionElement[] {
68+
return this.options.filter(x => x.selected);
69+
}
70+
71+
get activeOption(): HTMLOptionElement | undefined {
72+
return this.options.find(x => x.classList.contains('active'));
73+
}
74+
75+
76+
render(): TemplateResult<1> {
77+
return html`
78+
<input id="combobox">
79+
<button id="button">Show Options</button>
80+
<div id="listbox" ?hidden="${!this.expanded}">
81+
<option id="placeholder" aria-disabled="true">Select an Option</option>
82+
<option>1</option>
83+
<option>2</option>
84+
<option>31</option>
85+
<option>41</option>
86+
<option>52</option>
87+
<option>63</option>
88+
<option>74</option>
89+
<option>8</option>
90+
<option>9</option>
91+
<option>10</option>
92+
</div>
93+
`;
94+
}
95+
96+
@observes('expanded')
97+
private async expandedChanged(old: boolean, expanded: boolean) {
98+
if (this.dispatchEvent(new Event(this.expanded ? 'close' : 'open'))) {
99+
if (expanded) {
100+
this.#doExpand();
101+
} else {
102+
this.#doCollapse();
103+
}
104+
}
105+
}
106+
107+
async #doExpand() {
108+
try {
109+
await this.#float.show({ placement: this.position || 'bottom', flip: !!this.enableFlip });
110+
return true;
111+
} catch {
112+
return false;
113+
}
114+
}
115+
116+
async #doCollapse() {
117+
try {
118+
await this.#float.hide();
119+
return true;
120+
} catch {
121+
return false;
122+
}
123+
}
124+
125+
/**
126+
* Opens the dropdown
127+
*/
128+
async show(): Promise<void> {
129+
this.expanded = true;
130+
await this.updateComplete;
131+
}
132+
133+
/**
134+
* Closes listbox
135+
*/
136+
async hide(): Promise<void> {
137+
this.expanded = false;
138+
await this.updateComplete;
139+
}
140+
141+
/**
142+
* toggles popup based on current state
143+
*/
144+
async toggle(): Promise<void> {
145+
if (this.expanded) {
146+
await this.hide();
147+
} else {
148+
await this.show();
149+
}
150+
}
151+
}
152+
153+
declare global {
154+
interface HTMLElementTagNameMap {
155+
'pf-search-input': PfSearchInput;
156+
}
157+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { test } from '@playwright/test';
2+
import { PfeDemoPage } from '@patternfly/pfe-tools/test/playwright/PfeDemoPage.js';
3+
import { SSRPage } from '@patternfly/pfe-tools/test/playwright/SSRPage.js';
4+
5+
const tagName = 'pf-search-input';
6+
7+
test.describe(tagName, () => {
8+
test('snapshot', async ({ page }) => {
9+
const componentPage = new PfeDemoPage(page, tagName);
10+
await componentPage.navigate();
11+
await componentPage.snapshot();
12+
});
13+
14+
test('ssr', async ({ browser }) => {
15+
const fixture = new SSRPage({
16+
tagName,
17+
browser,
18+
demoDir: new URL('../demo/', import.meta.url),
19+
importSpecifiers: [
20+
`@patternfly/elements/${tagName}/${tagName}.js`,
21+
],
22+
});
23+
await fixture.snapshots();
24+
});
25+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { expect, html } from '@open-wc/testing';
2+
import { createFixture } from '@patternfly/pfe-tools/test/create-fixture.js';
3+
import { PfSearchInput } from '@patternfly/elements/pf-search-input/pf-search-input.js';
4+
5+
describe('<pf-search-input>', function() {
6+
describe('simply instantiating', function() {
7+
let element: PfSearchInput;
8+
it('imperatively instantiates', function() {
9+
expect(document.createElement('pf-search-input')).to.be.an.instanceof(PfSearchInput);
10+
});
11+
12+
it('should upgrade', async function() {
13+
element = await createFixture<PfSearchInput>(html`<pf-search-input></pf-search-input>`);
14+
const klass = customElements.get('pf-search-input');
15+
expect(element)
16+
.to.be.an.instanceOf(klass)
17+
.and
18+
.to.be.an.instanceOf(PfSearchInput);
19+
});
20+
});
21+
});

0 commit comments

Comments
 (0)