Skip to content

Commit e30ddb8

Browse files
authored
Redesigned layout of tags-input control (#443)
* Redesigned layout of tags-input control * Update unit tests for tags-input
1 parent 58d24d5 commit e30ddb8

File tree

7 files changed

+70
-47
lines changed

7 files changed

+70
-47
lines changed

static/src/components.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,8 @@ export namespace Components {
9999
"showSavedLoader": boolean;
100100
}
101101
interface TagsInput {
102-
"label": string;
102+
"label"?: string;
103+
"labelPlacement"?: 'end' | 'fixed' | 'floating' | 'stacked' | 'start';
103104
"suggestions": string[];
104105
"value": string[];
105106
}
@@ -485,6 +486,7 @@ declare namespace LocalJSX {
485486
}
486487
interface TagsInput {
487488
"label"?: string;
489+
"labelPlacement"?: 'end' | 'fixed' | 'floating' | 'stacked' | 'start';
488490
"onValueChanged"?: (event: TagsInputCustomEvent<string[]>) => void;
489491
"suggestions"?: string[];
490492
"value"?: string[];

static/src/components/pages/settings-preferences/page-settings-preferences.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ export class PageSettingsPreferences {
4545
<img alt="Home Image" src={this.settings?.homeImageUrl} hidden={isNullOrEmpty(this.settings?.homeImageUrl)} />
4646
</ion-thumbnail>
4747
</ion-item>
48-
<tags-input label="Favorite Tags" value={this.settings?.favoriteTags ?? []}
49-
onValueChanged={e => this.settings = { ...this.settings, favoriteTags: e.detail }} />
48+
<ion-item lines="full">
49+
<tags-input label="Favorite Tags" label-placement="stacked" value={this.settings?.favoriteTags ?? []}
50+
onValueChanged={e => this.settings = { ...this.settings, favoriteTags: e.detail }} />
51+
</ion-item>
5052
</ion-card-content>
5153
<ion-footer>
5254
<ion-toolbar>

static/src/components/recipe-editor/recipe-editor.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,11 @@ export class RecipeEditor {
9898
inputmode="url"
9999
onIonBlur={e => this.recipe = { ...this.recipe, sourceUrl: e.target.value as string }} />
100100
</ion-item>
101-
<tags-input value={this.recipe.tags} suggestions={this.currentUserSettings?.favoriteTags ?? []}
102-
onValueChanged={e => this.recipe = { ...this.recipe, tags: e.detail }} />
101+
<ion-item lines="full">
102+
<tags-input label="Tags" label-placement="stacked" value={this.recipe.tags}
103+
suggestions={this.currentUserSettings?.favoriteTags ?? []}
104+
onValueChanged={e => this.recipe = { ...this.recipe, tags: e.detail }} />
105+
</ion-item>
103106
</form>
104107
</ion-content>
105108
</Host>

static/src/components/search-filter-editor/search-filter-editor.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,11 @@ export class SearchFilterEditor {
7878
spellcheck
7979
onIonBlur={e => this.searchFilter = { ...this.searchFilter, query: e.target.value as string }} />
8080
</ion-item>
81-
<tags-input value={this.searchFilter.tags} suggestions={this.currentUserSettings?.favoriteTags ?? []}
82-
onValueChanged={e => this.searchFilter = { ...this.searchFilter, tags: e.detail }} />
81+
<ion-item lines="full">
82+
<tags-input label="Tags" label-placement="stacked" value={this.searchFilter.tags}
83+
suggestions={this.currentUserSettings?.favoriteTags ?? []}
84+
onValueChanged={e => this.searchFilter = { ...this.searchFilter, tags: e.detail }} />
85+
</ion-item>
8386
<ion-item lines="full">
8487
<ion-select label="Sort By" label-placement="stacked" value={this.searchFilter.sortBy} onIonChange={e => this.searchFilter = { ...this.searchFilter, sortBy: e.detail.value }}>
8588
{Object.keys(SortBy).map(item =>
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1-
@import '../../global/app.css';
2-
31
:host {
42
display: block;
3+
width: 100%;
4+
}
5+
6+
:host:focus-within ion-label {
7+
color: var(--ion-color-primary);
58
}

static/src/components/tags-input/tags-input.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import { isNullOrEmpty } from '../../helpers/utils';
44
@Component({
55
tag: 'tags-input',
66
styleUrl: 'tags-input.css',
7-
shadow: true,
7+
scoped: true,
88
})
99
export class TagsInput {
10-
@Prop() label = 'Tags';
10+
@Prop() label?: string;
11+
@Prop() labelPlacement?: 'end' | 'fixed' | 'floating' | 'stacked' | 'start';
1112
@Prop() value: string[] = [];
1213
@Prop() suggestions: string[] = [];
1314

@@ -36,28 +37,27 @@ export class TagsInput {
3637
render() {
3738
return (
3839
<Host>
39-
<ion-item lines="full">
40-
{this.internalValue.length > 0 ?
41-
<div class="ion-padding-top">
42-
{this.internalValue.map(tag =>
43-
<ion-chip key={tag} onClick={() => this.removeTag(tag)}>
44-
{tag}
45-
<ion-icon icon="close-circle" />
46-
</ion-chip>
47-
)}
48-
</div>
49-
: ''}
50-
<ion-input label={this.label} label-placement="stacked" enterkeyhint="enter" onKeyDown={e => this.onTagsKeyDown(e)} ref={el => this.tagsInput = el} />
51-
<div class="ion-padding">
52-
{this.filteredSuggestions.map(tag =>
53-
<ion-chip key={tag} class="suggested" color="success" onClick={() => this.addTag(tag)}>
40+
{!isNullOrEmpty(this.label) ? <ion-label position={this.labelPlacement}>{this.label}</ion-label> : ''}
41+
{this.internalValue.length > 0 ?
42+
<div class="ion-padding-top">
43+
{this.internalValue.map(tag =>
44+
<ion-chip key={tag} onClick={() => this.removeTag(tag)}>
5445
{tag}
55-
<ion-icon icon="add-circle" />
46+
<ion-icon icon="close-circle" />
5647
</ion-chip>
5748
)}
5849
</div>
59-
</ion-item>
60-
</Host>
50+
: ''}
51+
<ion-input enterkeyhint="enter" onKeyDown={e => this.onTagsKeyDown(e)} ref={el => this.tagsInput = el} />
52+
<div class="ion-padding-bottom">
53+
{this.filteredSuggestions.map(tag =>
54+
<ion-chip key={tag} class="suggested" color="success" onClick={() => this.addTag(tag)}>
55+
{tag}
56+
<ion-icon icon="add-circle" />
57+
</ion-chip>
58+
)}
59+
</div>
60+
</Host >
6161
);
6262
}
6363

static/src/components/tags-input/test/tags-input.spec.tsx

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,15 @@ describe('tags-input', () => {
1111
expect(page.rootInstance).toBeInstanceOf(TagsInput);
1212
});
1313

14-
it('default label', async () => {
14+
it('default no label', async () => {
1515
const page = await newSpecPage({
1616
components: [TagsInput],
1717
html: '<tags-input></tags-input>',
1818
});
19-
const expectedLabel = 'Tags';
2019
const component = page.rootInstance as TagsInput;
21-
expect(component.label).toEqual(expectedLabel);
22-
const input = page.root.shadowRoot.querySelector('ion-input');
23-
expect(input).not.toBeNull();
24-
expect(input.getAttribute('label')).toEqualText(expectedLabel);
20+
expect(component.label).toBeUndefined();
21+
const label = page.root.querySelector('ion-label');
22+
expect(label).toBeNull();
2523
});
2624

2725
it('label is used', async () => {
@@ -32,9 +30,21 @@ describe('tags-input', () => {
3230
});
3331
const component = page.rootInstance as TagsInput;
3432
expect(component.label).toEqual(expectedLabel);
35-
const input = page.root.shadowRoot.querySelector('ion-input');
36-
expect(input).not.toBeNull();
37-
expect(input.getAttribute('label')).toEqualText(expectedLabel);
33+
const label = page.root.querySelector('ion-label');
34+
expect(label).toEqualText(expectedLabel);
35+
});
36+
37+
it('label placement is used', async () => {
38+
const expectedLabel = 'My Label';
39+
const page = await newSpecPage({
40+
components: [TagsInput],
41+
template: () => (<tags-input label={expectedLabel} label-placement="fixed"></tags-input>),
42+
});
43+
const component = page.rootInstance as TagsInput;
44+
expect(component.label).toEqual(expectedLabel);
45+
const label = page.root.querySelector('ion-label');
46+
expect(label).toEqualText(expectedLabel);
47+
expect(label).toEqualAttribute('position', 'fixed');
3848
});
3949

4050
it('uses tags', async () => {
@@ -45,7 +55,7 @@ describe('tags-input', () => {
4555
});
4656
const component = page.rootInstance as TagsInput;
4757
expect(component.value).toEqual(expectedTags);
48-
const chips = page.root.shadowRoot.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
58+
const chips = page.root.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
4959
expect(chips).toHaveLength(expectedTags.length);
5060
});
5161

@@ -58,7 +68,7 @@ describe('tags-input', () => {
5868
const component = page.rootInstance as TagsInput;
5969
expect(component.value).toEqual([]);
6070
expect(component.suggestions).toEqual(expectedTags);
61-
const chips = page.root.shadowRoot.querySelectorAll<HTMLIonChipElement>('ion-chip.suggested');
71+
const chips = page.root.querySelectorAll<HTMLIonChipElement>('ion-chip.suggested');
6272
expect(chips).toHaveLength(expectedTags.length);
6373
});
6474

@@ -69,13 +79,13 @@ describe('tags-input', () => {
6979
components: [TagsInput],
7080
template: () => (<tags-input value={initialTags} onValueChanged={handleValueChanged}></tags-input>),
7181
});
72-
let chips = page.root.shadowRoot.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
82+
let chips = page.root.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
7383
expect(chips).toHaveLength(initialTags.length);
7484
chips.forEach(chip => chip.click());
7585
await page.waitForChanges();
7686
const component = page.rootInstance as TagsInput;
7787
expect(component.internalValue).toEqual([]);
78-
chips = page.root.shadowRoot.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
88+
chips = page.root.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
7989
expect(chips).toHaveLength(0);
8090
expect(handleValueChanged).toHaveBeenCalledTimes(initialTags.length);
8191
});
@@ -89,15 +99,15 @@ describe('tags-input', () => {
8999
});
90100
const component = page.rootInstance as TagsInput;
91101
expect(component.value).toEqual([]);
92-
const input = page.root.shadowRoot.querySelector('ion-input');
102+
const input = page.root.querySelector('ion-input');
93103
expect(input).not.toBeNull();
94104
for (const tag of expectedTags) {
95105
input.value = tag;
96106
input.dispatchEvent(new KeyboardEvent('keydown', { 'key': 'Enter' }));
97107
await page.waitForChanges();
98108
expect(input.value).toEqualText('');
99109
}
100-
const addedChips = page.root.shadowRoot.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
110+
const addedChips = page.root.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
101111
expect(addedChips).toHaveLength(expectedTags.length);
102112
expect(handleValueChanged).toHaveBeenCalledTimes(expectedTags.length);
103113
expect(component.internalValue).toEqual(expectedTags);
@@ -110,15 +120,15 @@ describe('tags-input', () => {
110120
components: [TagsInput],
111121
template: () => (<tags-input suggestions={initialSuggestions} onValueChanged={handleValueChanged}></tags-input>),
112122
});
113-
let suggestedChips = page.root.shadowRoot.querySelectorAll<HTMLIonChipElement>('ion-chip.suggested');
123+
let suggestedChips = page.root.querySelectorAll<HTMLIonChipElement>('ion-chip.suggested');
114124
expect(suggestedChips).toHaveLength(initialSuggestions.length);
115125
suggestedChips.forEach(chip => chip.click());
116126
await page.waitForChanges();
117127
const component = page.rootInstance as TagsInput;
118128
expect(component.internalValue).toEqual(initialSuggestions);
119-
suggestedChips = page.root.shadowRoot.querySelectorAll<HTMLIonChipElement>('ion-chip.suggested');
129+
suggestedChips = page.root.querySelectorAll<HTMLIonChipElement>('ion-chip.suggested');
120130
expect(suggestedChips).toHaveLength(0);
121-
const addedChips = page.root.shadowRoot.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
131+
const addedChips = page.root.querySelectorAll<HTMLIonChipElement>('ion-chip:not(.suggested)');
122132
expect(addedChips).toHaveLength(initialSuggestions.length);
123133
expect(handleValueChanged).toHaveBeenCalledTimes(initialSuggestions.length);
124134
});

0 commit comments

Comments
 (0)