Skip to content

Commit

Permalink
Add chip keyboard support.
Browse files Browse the repository at this point in the history
 - Up/down arrows navigate chips.
 - Clicking a chip properly focuses it for subsequent keyboard
   navigation.
 - More demos.

References angular#120.
  • Loading branch information
topherfangio committed Nov 28, 2016
1 parent cf1b4b9 commit 55f770d
Show file tree
Hide file tree
Showing 22 changed files with 941 additions and 8 deletions.
68 changes: 68 additions & 0 deletions src/demo-app/chips/chips-demo.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<div class="chips-demo">
<section>
<h3>Static Chips</h3>

<h5>Simple</h5>

<md-chip-list>
<md-chip>Chip 1</md-chip>
<md-chip>Chip 2</md-chip>
<md-chip>Chip 3</md-chip>
</md-chip-list>

<h5>Advanced</h5>

<md-chip-list>
<md-chip class="md-accent selected">Selected/Colored</md-chip>
<md-chip class="md-warn" *ngIf="visible"
(destroy)="alert('chip destroyed')" (click)="toggleVisible()">
With Events
</md-chip>
</md-chip-list>

<h5>Unstyled</h5>

<md-chip-list>
<md-basic-chip>Basic Chip 1</md-basic-chip>
<md-basic-chip>Basic Chip 2</md-basic-chip>
<md-basic-chip>Basic Chip 3</md-basic-chip>
</md-chip-list>

<h3>Material Contributors</h3>

<md-chip-list>
<md-chip *ngFor="let person of people; let even = even" [ngClass]="[color, even ? 'selected' : '' ]">
{{person.name}}
</md-chip>
</md-chip-list>

<br />

<md-input #input (keyup.enter)="add(input)" (blur)="add(input)" placeholder="New Contributor...">
</md-input>

<h3>Stacked Chips</h3>

<p>
You can also stack the chips if you want them on top of each other.
</p>

<md-chip-list class="md-chip-list-stacked">
<md-chip (didfocus)="color = ''" class="selected">
None
</md-chip>

<md-chip (didfocus)="color = 'md-primary'" class="selected md-primary">
Primary
</md-chip>

<md-chip (didfocus)="color = 'md-accent'" class="selected md-accent">
Accent
</md-chip>

<md-chip (didfocus)="color = 'md-warn'" class="selected md-warn">
Warn
</md-chip>
</md-chip-list>
</section>
</div>
6 changes: 6 additions & 0 deletions src/demo-app/chips/chips-demo.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.chips-demo {
.md-chip-list-stacked {
display: block;
max-width: 200px;
}
}
41 changes: 41 additions & 0 deletions src/demo-app/chips/chips-demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {Component} from '@angular/core';

export interface Person {
name: string;
}

@Component({
moduleId: module.id,
selector: 'chips-demo',
templateUrl: 'chips-demo.html',
styleUrls: ['chips-demo.css']
})
export class ChipsDemo {
visible: boolean = true;
color: string = '';

people: Person[] = [
{ name: 'Kara' },
{ name: 'Jeremy' },
{ name: 'Topher' },
{ name: 'Elad' },
{ name: 'Kristiyan' },
{ name: 'Paul' }
];
favorites: Person[] = [];

alert(message: string): void {
alert(message);
}

add(input: HTMLInputElement): void {
if (input.value && input.value.trim() != '') {
this.people.push({ name: input.value.trim() });
input.value = '';
}
}

toggleVisible(): void {
this.visible = false;
}
}
2 changes: 2 additions & 0 deletions src/demo-app/demo-app-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {IconDemo} from './icon/icon-demo';
import {GesturesDemo} from './gestures/gestures-demo';
import {InputDemo} from './input/input-demo';
import {CardDemo} from './card/card-demo';
import {ChipsDemo} from './chips/chips-demo';
import {RadioDemo} from './radio/radio-demo';
import {ButtonToggleDemo} from './button-toggle/button-toggle-demo';
import {ProgressCircleDemo} from './progress-circle/progress-circle-demo';
Expand Down Expand Up @@ -49,6 +50,7 @@ import {ProjectionDemo, ProjectionTestComponent} from './projection/projection-d
ButtonDemo,
ButtonToggleDemo,
CardDemo,
ChipsDemo,
CheckboxDemo,
DemoApp,
DialogDemo,
Expand Down
1 change: 1 addition & 0 deletions src/demo-app/demo-app/demo-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class DemoApp {
{name: 'Button', route: 'button'},
{name: 'Button Toggle', route: 'button-toggle'},
{name: 'Card', route: 'card'},
{name: 'Chips', route: 'chips'},
{name: 'Checkbox', route: 'checkbox'},
{name: 'Dialog', route: 'dialog'},
{name: 'Gestures', route: 'gestures'},
Expand Down
2 changes: 2 additions & 0 deletions src/demo-app/demo-app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {SlideToggleDemo} from '../slide-toggle/slide-toggle-demo';
import {SliderDemo} from '../slider/slider-demo';
import {RadioDemo} from '../radio/radio-demo';
import {CardDemo} from '../card/card-demo';
import {ChipsDemo} from '../chips/chips-demo';
import {MenuDemo} from '../menu/menu-demo';
import {RippleDemo} from '../ripple/ripple-demo';
import {DialogDemo} from '../dialog/dialog-demo';
Expand All @@ -34,6 +35,7 @@ export const DEMO_APP_ROUTES: Routes = [
{path: '', component: Home},
{path: 'button', component: ButtonDemo},
{path: 'card', component: CardDemo},
{path: 'chips', component: ChipsDemo},
{path: 'radio', component: RadioDemo},
{path: 'select', component: SelectDemo},
{path: 'sidenav', component: SidenavDemo},
Expand Down
201 changes: 201 additions & 0 deletions src/lib/chips/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# md-chip-list

`md-chip-list` provides a horizontal display of (optionally) selectable, addable, and removable,
items and an input to create additional ones (again; optional). You can read more about chips
in the [Material Design spec](https://material.google.com/components/chips.html).

## Requirements

1. Show a static list of chips with proper styling
2. Show a dynamic list of chips with an Input
3. Show a dynamic list of chips with an Input/Autocomplete, or a Select

#### Additional Requirements

1. A Chips' Autocomplete or Select should not show items for an existing chip

## Usage

### Static Chips

Static chips can be used to inform a user about a list of existing, unmodifiable, items.

##### static-chips-1.html
```html
<md-chip-list>
<md-chip>Baseball</md-chip>
<md-chip>Basketball</md-chip>
<md-chip>Football</md-chip>
</md-chip-list>
```

Alternatively, you can apply the chip styling to an existing element and this will handle focus and
selection events.

##### static-chips-2.html

```html
<div md-chip-list class="my-tags">
<button md-chip *ngFor="let tag of tags" (select)="tagSelected(tag)">
{{tag.name}}
</button>
</div>
```

### Dynamic Chips

If you want any of the dynamic functionality supplied by chips, you may utilize the `md-chip-input`
component (and associated `(chipAdded)` event) in tandem with the `(remove)` event of the `md-chip`
component.

##### dynamic-chips-1.html

The most basic version simply utilizes the `(chipAdded)` event to push the requested chip onto
the list. This event handles the keyboard interaction and would also work in tandem with the
`[chip-separators]` option that allows user-defined separator keys (like `,` or `;`).

```html
<md-chip-list class="my-tags" #chips="mdChips">
<md-chip *ngFor="let tag of tags" (remove)="tags.remove($chip)">
{{tag.name}}
</md-chip>
</md-chip-list>

<input type="email" placeholder="New email..."
md-chip-input="chips" (chipAdded)="tags.push($chip)" />
```


##### autocomplete-chips.html

If you would like to help users by providing some filtering of predefined, options,
you can simply apply the `md-autocomplete`.

```html
<md-chip-list>
<md-chip md-prefix *ngFor="let tag of tags" (remove)="tags.remove(tag)">
{{tag.name}}
</md-chip>
</md-chip-list>

<input type="text" placeholder="New tag..."
[md-autocomplete-input-for]="autocomplete" />

<md-autocomplete #autocomplete="mdAutocomplete">
<button md-autocomplete-item *ngFor="let suggestion of suggestions"
(click)="tags.add(suggestion.name)">
{{suggestion.name}}
</button>
</md-autocomplete>
```

_**Note:** Notice how this `(remove)` utilizes the `tag` of the `*ngFor` rather than the
`$chip` variable. For chips which are more complex than strings, this may be desirable._

##### select-chips.html

Finally, you could use an `md-select` instead of an `md-autocomplete` if you do not wish
the user to be able to create new/unknown chips.

```html
<md-chip-list>
<md-chip md-prefix *ngFor="let favorite of favoriteFoods"
(remove)="favoriteFoods.remove(favorite)">
{{favorite.name}}
</md-chip>
</md-chip-list>

<md-select placeholder="Food">
<md-option *ngFor="let food of foods" (select)="favoriteFoods.add(food)">
{{ food.viewValue }}
</md-option>
</md-select>
```

## Templates

Obviously, these kinds of complex controls may be a bit burdensome for many developers,
so I propose we add some "high-level" template components which can be used to make
development easier and more in-line with what most users are expecting, while still
providing full flexibility for users who want a more custom component.

##### md-static-chips

The most basic example would be static chips bound to a list of elements which provides
the `chip-text` expression allowing the component to render the chip without requiring
a template.

```html
<md-static-chips [ngModel]="myChips" chip-text="$chip.name">
</md-static-chips>
```

##### md-dynamic-chips

There are two common variations of the dynamic chips:

1. Chips with just an input
2. Chips with an input associated with an autocomplete

Both are listed below.

*only the input*
```html
<md-dynamic-chips [(ngModel)]="myChips" chip-text="$chip.name">
</md-dynamic-chips>
```

*with an autocomplete*
```html
<md-dynamic-chips [(ngModel)]="myChips" chip-text="$chip.name"
[suggestions]="mySuggestions" suggestion-text="$suggestion.name"
max-chips="5">
</md-dynamic-chips>
```

There are two methods for providing the suggestions:

1. `[suggestions]="mySuggestions"` - In this mode, the suggestions are bound to an array
or list and the filtering is handled by the `md-dynamic-chips` component.
2. `[suggestions]="getSuggestions($query)"` - In this mode, the suggestions are filtered
by user-provided code and can immediately return with the value, or it can return a
promise for evaluation at a later time.

The various behaviors could be also be controlled using the `capabilities` option.

```html
<md-dynamic-chips [(ngModel)]="myChips" chip-text="$chip.name"
capabilities="create,remove,edit">
</md-dynamic-chips>
```

In this way, you can easily customize which behaviors are allowed at any given time.

_**Note:** We should also support the `ng-disabled`/`disabled` parameter which turns off all
capabilities._

##### md-contact-chips

Finally, we would offer a simple component for the common case of the Contact Chips which
appears multiple times in the spec.

```html
<md-contact-chips [(ngModel)]="selectedContacts" [contacts]="matchingContacts"
[chipInfo]="getInfo($chip)" placeholder="Find friends..." required>
</md-contact-chips>
```

The `getInfo($chip)` method should return an object with `name`, `email` and `image` properties
to be utilized by the contact chips during rendering. If not provided, the default would simply be

```js
{
name: $chip.name,
email: $chip.email,
image: $chip.image
}
````

Similar to the `md-dynamic-chips` and the suggestions, the developer could also supply
`[contacts]="getContacts($query)"` to asynchronously search for the contacts, or to have
more control over the filtered list.
29 changes: 29 additions & 0 deletions src/lib/chips/_chips-theme.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@import '../core/theming/theming';

@mixin md-chips-theme($theme) {
$is-dark-theme: map-get($theme, is-dark);
$primary: map-get($theme, primary);
$accent: map-get($theme, accent);
$warn: map-get($theme, warn);
$background: map-get($theme, background);


.md-chip.selected {
// TODO: Based on spec, this should be #808080, but we can only use md-contrast with a palette
background-color: md-color($md-grey, 600);
color: md-contrast($md-grey, 600);

&.md-primary {
background-color: md-color($primary, 500);
color: md-contrast($primary, 500);
}
&.md-accent {
background-color: md-color($accent, 500);
color: md-contrast($accent, 500);
}
&.md-warn {
background-color: md-color($warn, 500);
color: md-contrast($warn, 500);
}
}
}
Loading

0 comments on commit 55f770d

Please sign in to comment.