Published on

Making resusable components

Authors

How to make reusable components

$accent-color: cornflowerblue;
$text-color: #444;
$width: 300px;
$font-size: 0.85em;
$background-color: white;
$border-color: #ddd;

.dropdown-collection {
  width: $width;
  position: relative;
  margin-right: 8px;
}

.dropdown-button {
  border: none;
  border-top: 1px solid $border-color;
  position: relative;
  justify-content: space-between;
  padding: 12px 32px 12px 12px;
  background-color: $background-color;
  display: flex;
  font-size: $font-size;
  width: 100%;
  cursor: pointer;
  align-items: center;
  background-image: url("/assets/svg/chevron.svg");
  background-size: 10px;
  background-position: right 16px bottom 45%;
  background-repeat: no-repeat;
  &:hover {
    outline: 1px solid fade_out($accent-color, 0.5);
  }
}

.dropdown-value {
  font-weight: 600;
}

.dropdown-list {
  display: none;
  background: $background-color;
  list-style: none;
  border: 1px solid $accent-color;
  box-shadow: 0px 0px 15px -1px fade_out($accent-color, 0.6);
  position: absolute;
  bottom: 0;
  left: 0;
  padding: 0;
  z-index: 10;
  right: 0;
  margin: 0;
  transform: translateY(100%);
  li {
    padding: 4px 16px;
    cursor: pointer;
    font-weight: 700;
    &.selected {
      background: darken($background-color, 5%);
    }
    &:hover {
      background: fade_out($accent-color, 0.6);
    }
  }
  &.dropdown-open {
    display: block;
  }
}

@Component({
  selector: 'custom-select',
  templateUrl: './select.component.html',
  styleUrls: ['./select.component.scss'],
  host: {
      '(document:keydown)': 'handleKeyboardEvents($event)'
  }
})
export class SelectComponent {

    @Input() options: any;
    @Input() title: string;
    @Output() currentValueChange = new EventEmitter();

    public currentValue;
    public dropdownOpen: boolean = false;
    public get dropdownElement(): Element {return this.elem.nativeElement.querySelector('.dropdown-list')}

    private currentIndex = -1;

    constructor(
        private elem: ElementRef
    ) { }

    ngOnInit(): void {
        this.currentValue = this.options[0];
    }

    handleKeyboardEvents($event: KeyboardEvent) {
        if (this.dropdownOpen) {
            $event.preventDefault();
        } else {
            return;
        }
        if ($event.code === 'ArrowUp') {
            if (this.currentIndex < 0) {
                this.currentIndex = 0;
            } else if (this.currentIndex > 0) {
                this.currentIndex--;
            }
            this.elem.nativeElement.querySelectorAll('li').item(this.currentIndex).focus();
        } else if ($event.code === 'ArrowDown') {
            if (this.currentIndex < 0) {
                this.currentIndex = 0;
            } else if (this.currentIndex < this.options.length-1) {
                this.currentIndex++;
            }
            this.elem.nativeElement.querySelectorAll('li').item(this.currentIndex).focus();
        } else if (($event.code === 'Enter' || $event.code === 'NumpadEnter') && this.currentIndex >= 0) {
            this.selectByIndex(this.currentIndex);
        } else if ($event.code === 'Escape') {
            this.closeDropdown();
        }
    }

    closeDropdown() {
        this.dropdownElement.setAttribute('aria-expanded', "false");
        this.currentIndex = -1;
        this.dropdownOpen = false;
    }

    selectByIndex(i: number) {
        let value = this.options[i];
        this.select(value);
    }

    select(value) {
        this.currentValue = value;
        this.closeDropdown();
        this.currentValueChange.emit(this.currentValue);
    }

    toggleDropdown() {
        this.dropdownOpen = !this.dropdownOpen;
        this.dropdownElement.setAttribute('aria-expanded', this.dropdownOpen ? "true" : "false");
    }
}

Implementation

<custom-select [title]="'My dropdown'" [options]="dropdownOptions"(currentValueChange)="dropdownValueChanged($event)"></custom-select>
  • Come and see our updated blog next week.

  • Thanks, Anders