Ion-select style like segment buttons

I’ve read through the closed issue here, and mhartington’s reasoning why ion-segment doesn’t allow for select multiple -> https://github.com/ionic-team/ionic-framework/issues/11985#issuecomment-428649602

Given that ionic doesn’t want the ability to use ion-segment as an input in that sense, he suggested ion-select.

Whether it’s ion-checklist or ion-radio or ion-select, I don’t see a way to style each option as segmented buttons, as they’ve done in the paid framework example below:


How would I go about styling an ion-select so it looks like the example above for both single and multiple selection? Would love to have each select element side by side in a row and appear like a segment/button with text inside and a clear visual indication of which button(s)/segment(s) are selected.

Here’s a jquery implimentation that styles a properly:
https://www.jqueryscript.net/demo/jQuery-Plugin-To-Create-iOS-Style-Segmented-Controls-Segment-js/

other examples of this design pattern:
image

From the apple design guidelines: “A segmented control can enable a single choice or multiple choices.”
https://developer.apple.com/design/human-interface-guidelines/macos/selectors/segmented-controls/

This is pretty Q&D, but presented on the off-chance that you can use it as at least a starting point:

selectbar.component.ts

import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from "@angular/core";

interface SelectBarElement {
  value: string;
  selected: boolean;
}

@Component({
  selector: "apropos-selectbar",
  templateUrl: "selectbar.component.html",
  styleUrls: ["selectbar.component.scss"],
})
export class SelectBarComponent implements OnChanges {
  @Input() options: string[] = [];
  @Input() selections: string[] = [];
  @Output() selectionsChange = new EventEmitter<string[]>();
  elements: SelectBarElement[] = [];

  ngOnChanges(changes: SimpleChanges): void {
    this.elements = this.options.map(option => {
      return {
        value: option,
        selected: this.selections.includes(option),
      };
    });
  }

  toggle(elem: SelectBarElement): void {
    if (elem.selected) {
      elem.selected = false;
      this.selections = this.selections.filter(s => s !== elem.value);
    } else {
      elem.selected = true;
      this.selections.push(elem.value);
    }
    this.selectionsChange.emit(this.selections);
  }
}

selectbar.component.html

<div class="bar">
  <ion-button *ngFor="let elem of elements" (click)="toggle(elem)"
              [color]="elem.selected ? 'secondary' : 'primary'">
    {{elem.value}}
  </ion-button>
</div>

selectbar.component.scss

:host {
  width: 100%;
}

.bar {
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  width: 100%;
}

app.component.ts

export class AppComponent {
  fruits = ["apple", "banana", "cherry"];
  selectedFruits = ["apple"];
}

app.component.html

<ion-app>
  <ion-content>
    <ion-item>
      <apropos-selectbar [options]="fruits" [(selections)]="selectedFruits"></apropos-selectbar>
    </ion-item>
    <ion-item>
      <ion-label>selected: {{selectedFruits | json}}</ion-label>
    </ion-item>
  </ion-content>
</ion-app>
1 Like

Just love that selector name, apropos

Would u use segment-button as well? (instead of ion-button)

It’s my company name. My handle is a portmanteau of it with my initials. I use it reflexively in component selectors that I use across multiple projects.

Certainly doable, but <ion-segment-button> doesn’t take a color property, so one would have to do something like this:

  <ion-segment-button *ngFor="let elem of elements" (click)="toggle(elem)"
              [class.selected]="elem.selected">
    {{elem.value}}
  </ion-segment-button>
  ion-segment-button.selected {
    background-color: var(--ion-color-primary);
    color: var(--ion-color-primary-contrast);
  }
2 Likes

So awesome that you came up with something so quick!

In the ionic 5 blank angular starter, which has lazy loading, I had to do the following to it going:

./home/home.module.ts

import { SelectBarComponent } from '.././selectbar.component';

declarations: [HomePage, SelectBarComponent]

I had this all working in ionic/angular3, so learning the latest stuff as I go here! Thanks for the amazing help!

I refactored it a bit (keep in mind I’m a n00b) to add an array for each option element. Treating options like its own select element, so i can have properties like visibility, disabled, ID, Value, name. My use case is generating 3 groups of these “selectbar” components with options that change based on other options that are toggled in the group above. All of these options are parameters for a search bar.

example data showing different states

teams = [
    {id: 1 ,name: "Sonics",value: "sonics",hidden: 0, disabled: 1}, 
    {id: 2 ,name: "Seahawks",value: "couldbedifferent",hidden: 1, disabled: 0},
    {id: 3 ,name: "Mariners",value: "mariners",hidden: 0, disabled: 0},
    {id: 4 ,name: "Sounders",value: "sounders",hidden: 0, disabled: 0} 
  ];
  selectedTeams = [];

selectbar.component.html

<div class="bar">
  <ng-container *ngFor="let elem of elements">
    <ion-button *ngIf="!elem['hidden']" (click)="toggle(elem)"
                [color]="elem.selected ? 'secondary' : 'primary'"
                [disabled]="elem['disabled']">
      {{elem.label}}
    </ion-button>
  </ng-container>
  </div>

selectbar.component.ts

export class SelectBarComponent implements OnChanges {
  @Input() options: string[] = [];
  @Input() selections: string[] = [];
  @Output() selectionsChange = new EventEmitter<string[]>();
  elements: SelectBarElement[] = [];

  ngOnChanges(changes: SimpleChanges): void {
    this.elements = this.options.map(option => {
      return {
        id: option["id"],
        value: option["value"],
        label: option["name"],
        hidden: option["hidden"],
        disabled: option["disabled"],
        selected: this.selections.includes(option["id"]),
      };
    });
  }

  toggle(elem: SelectBarElement): void {
    if (elem.selected) {
      elem.selected = false;
      this.selections = this.selections.filter(s => s !== elem.id);
    } else {
      elem.selected = true;
      this.selections.push(elem.id);
    }
    this.selectionsChange.emit(this.selections);
  }
}
1 Like