Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(documentation): add custom-select documentation #2573

Merged
merged 8 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/khaki-pets-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-documentation': minor
---

Added a documentation page for the angular pattern `custom-select` which is bades on the ng-bootstrap `dropdown` component.
b1aserlu marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions packages/documentation/.storybook/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const preview: Preview = {
'Card',
'Carousel',
'Collapsible',
'Custom-Select',
b1aserlu marked this conversation as resolved.
Show resolved Hide resolved
'Datepicker',
'Dropdown',
'Forms',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<div ngbDropdown>
<input [ngModel]="selectedOption?.value" name="meansOfTransport" type="hidden" />
<button
#toggle
aria-haspopup="listbox"
(keydown)="setFocus($event)"
id="customSelectButton"
class="form-select text-start no-toggle-arrow"
ngbDropdownToggle
type="button"
>
<span [class.visually-hidden]="selectedOption">Default custom-select</span>
<span *ngIf="selectedOption" aria-hidden="true">{{ selectedOption.label }}</span>
</button>
<div aria-labelledby="customSelectButton" class="w-100 mw-100" ngbDropdownMenu role="listbox">
<button
(click)="toggle.focus()"
(focus)="selectedOption = option"
*ngFor="let option of options"
[attr.aria-selected]="selectedOption?.value === option.value"
[class.active]="selectedOption?.value === option.value"
class="d-flex align-items-center"
ngbDropdownItem
role="option"
type="button"
#option
>
<em aria-hidden="true" class="pi me-small-regular" [class]="'pi-' + option.icon"></em>
<span>{{ option.label }}</span>
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { Component, ElementRef, QueryList, ViewChildren } from '@angular/core';

interface IOption {
label: string;
value: string;
icon: number;
}

@Component({
selector: 'app-custom-select-demo',
templateUrl: './custom-select-demo.component.html',
})
export class CustomSelectDemoComponent {
@ViewChildren('option', { read: ElementRef }) private optionList: QueryList<ElementRef>;

public options: IOption[];
public selectedOption: IOption;

constructor() {
this.options = [
{
label: 'One',
value: '1',
icon: 3126,
},
{
label: 'Two',
value: '2',
icon: 3116,
},
{
label: 'Three',
value: '3',
icon: 3107,
},
];
}

public setFocus(event: KeyboardEvent) {
const activeOptionIndex = Array.from(this.optionList).findIndex(option => {
return option.nativeElement.classList.contains('active');
});

switch (event.code) {
case 'Space':
case 'Enter':
const currentOption = this.optionList.get(activeOptionIndex) || this.optionList.get(0);
setTimeout(() => currentOption.nativeElement.focus(), 100);
break;
case 'ArrowUp':
const previousOption = this.optionList.get(activeOptionIndex + 1) || this.optionList.get(0);
setTimeout(() => previousOption.nativeElement.focus());
break;
case 'ArrowDown':
const nextOption = this.optionList.get(activeOptionIndex + 1) || this.optionList.get(0);
setTimeout(() => nextOption.nativeElement.focus());
break;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<div class="form-floating" ngbDropdown>
<input [ngModel]="selectedOption?.value" name="meansOfTransport" type="hidden" />
<button
#toggle
aria-haspopup="listbox"
(keydown)="setFocus($event)"
aria-labelledby="customSelectLabel"
class="form-select custom-select text-start no-toggle-arrow"
ngbDropdownToggle
type="button"
>
<span *ngIf="selectedOption" aria-hidden="true">{{ selectedOption.label }}</span>
</button>
<label class="form-label" id="customSelectLabel">
Floatinglabel custom-select {{ infoText }}
</label>
<div aria-labelledby="customSelectLabel" class="w-100 mw-100" ngbDropdownMenu role="listbox">
<button
(click)="toggle.focus()"
(focus)="selectedOption = option"
*ngFor="let option of options"
[attr.aria-selected]="selectedOption?.value === option.value"
[class.active]="selectedOption?.value === option.value"
class="d-flex align-items-center"
ngbDropdownItem
role="option"
type="button"
#option
>
<em aria-hidden="true" class="pi me-small-regular" [class]="'pi-' + option.icon"></em>
<span>{{ option.label }}</span>
</button>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { Component, ElementRef, QueryList, ViewChildren, OnInit, Input } from '@angular/core';

interface IOption {
label: string;
value: string;
icon: number;
}

@Component({
selector: 'app-custom-select-floating-demo',
templateUrl: './custom-select-floating-demo.component.html',
})
export class CustomSelectFloatingDemoComponent implements OnInit {
@ViewChildren('option', { read: ElementRef }) private optionList: QueryList<ElementRef>;

@Input() public noSelected: boolean = false;

public infoText: string = '';

ngOnInit() {
if (!this.noSelected) this.selectedOption = this.options[0];

this.infoText = this.noSelected ? `(no selected)` : '';
}

public options: IOption[];
public selectedOption: IOption;

constructor() {
this.options = [
{
label: 'One',
value: '1',
icon: 3126,
},
{
label: 'Two',
value: '2',
icon: 3116,
},
{
label: 'Three',
value: '3',
icon: 3107,
},
];
}

public setFocus(event: KeyboardEvent) {
const activeOptionIndex = Array.from(this.optionList).findIndex(option => {
return option.nativeElement.classList.contains('active');
});

switch (event.code) {
case 'Space':
case 'Enter':
const currentOption = this.optionList.get(activeOptionIndex) || this.optionList.get(0);
setTimeout(() => currentOption.nativeElement.focus(), 100);
break;
case 'ArrowUp':
const previousOption = this.optionList.get(activeOptionIndex + 1) || this.optionList.get(0);
setTimeout(() => previousOption.nativeElement.focus());
break;
case 'ArrowDown':
const nextOption = this.optionList.get(activeOptionIndex + 1) || this.optionList.get(0);
setTimeout(() => nextOption.nativeElement.focus());
break;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Meta, Source } from '@storybook/blocks';
import * as CustomSelectStories from './custom-select.stories.ts';
import StylesPackageImport from '../../../shared/styles-package-import.mdx';
import { PostAlert, PostTabs, PostTabHeader, PostTabPanel } from '@swisspost/design-system-components-react';
import SampleBasicHTML from './custom-select-basic.sample.html?raw';
import SampleBasicTS from './custom-select-basic.sample.ts?raw';
import SampleFloatingHTML from './custom-select-floating.sample.html?raw';
import SampleFloatingTS from './custom-select-floating.sample.ts?raw';

<Meta of={CustomSelectStories} />

<div className="d-flex align-items-center justify-content-between">
# Custom-Select

<a
className="btn btn-rg btn-secondary btn-animated"
href={`https://archive.design-system.post.ch/#/post-samples/custom-select`}
target="_blank"
rel="nofollow noopener noreferrer"
>
<span>See archived documentation</span>
</a>
</div>

<p className="lead">A custom select for NgBootstrap that uses the ngbDropdown.</p>

<PostAlert type="warning">
<h4 slot="heading">This component is deprecated</h4>
<p>It will be removed in the next major version.</p>
</PostAlert>

<ul>
<li>
<a href="#component-import" target="_self">Component Import</a>
</li>
<li>
<a href="#style-imports" target="_self">Style Imports</a>
</li>
<li>
<a href="#examples" target="_self">Examples</a>
</li>
</ul>

## Component Import

This component uses the [Dropdown](/?path=/docs/components-dropdown--docs) component that we also documented. There is no additional NgBootstrap import required.
b1aserlu marked this conversation as resolved.
Show resolved Hide resolved

<StylesPackageImport components={["forms", "form-select"]}
required={{ 'floating-label': true }} />

## Examples

<PostTabs>
<PostTabHeader slot="tabs" panel="sampleBasic">Basic example</PostTabHeader>
<PostTabPanel name="sampleBasic">
<Source code={SampleBasicHTML} language="html" />
<Source code={SampleBasicTS} language="typescript"/>
b1aserlu marked this conversation as resolved.
Show resolved Hide resolved
</PostTabPanel>
<PostTabHeader slot="tabs" panel="sampleFloating">Floating example</PostTabHeader>
<PostTabPanel name="sampleFloating">
<Source code={SampleFloatingHTML} language="html" />
<Source code={SampleFloatingTS} language="typescript"/>
</PostTabPanel>
</PostTabs>

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Meta, StoryObj } from '@storybook/web-components';
import { BADGE } from '../../../../.storybook/constants';

const meta: Meta = {
title: 'Components/Custom-Select',
parameters: {
badges: [BADGE.NEEDS_REVISION],
b1aserlu marked this conversation as resolved.
Show resolved Hide resolved
},
};

export default meta;

type Story = StoryObj;

export const Default: Story = {};
Loading