Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ dist/
*.tgz
doc-site/.vitepress/cache
doc-site/.vitepress/dist
.idea/
.idea/
custom-elements-manifest.mjs
127 changes: 101 additions & 26 deletions doc-site/components/nve-switch.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
layout: component
outline: [2, 3]
---

<CodeExamplePreview>
Expand All @@ -10,33 +11,73 @@ layout: component

</CodeExamplePreview>

<nve-message-card variant="primary" label="Native sjekkboks-attributter" size="compact">
<p>
<span class="highlight">nve-switch</span> bygger på et native <span class="highlight">&lt;input&gt;</span>-element
med type <span class="highlight">checkbox</span> og støtter relevante
egenskaper fra sjekkboksen:
</p>

<ul>
<li><span class="highlight">checked</span></li>
<li><span class="highlight">disabled</span></li>
<li> <span class="highlight">value</span> – kan brukes for å knytte en verdi til bryteren.</li>
</ul>

<br>

<p>
Komponenten videresender relevante hendelser fra det interne input-elementet slik at de kan lyttes til utenfor komponentens Shadow DOM.
</p>

<ul>
<li>
<span class="highlight">change</span> – sendes når bryterens status endres.
</li>
</ul>

<br>

<p>
Det native <span class="highlight">switch</span>-attributtet brukes foreløpig ikke på grunn av
begrenset nettleserstøtte. Komponenten bruker i stedet
<span class="highlight">role="switch"</span>.
</p>
</nve-message-card>

## Retningslinjer

- Gi alltid en tydelig <span class="highlight">label</span>.
- Ikke endre <span class="highlight">label</span> basert på bryterens tilstand. Labelen skal beskrive hva bryteren styrer, ikke hvilken handling som utføres. Bruk for eksempel «Vis info som fast label i stedet for å bytte mellom «Vis info og «Skjul info.
- Bruk <span class="highlight">nve-switch</span> når valget representerer en av/på-tilstand og endringen skal få effekt med én gang.
- Hvis endringen utløser en asynkron operasjon, oppdater <span class="highlight">checked</span> med én gang for å gi umiddelbar tilbakemelding, og rull tilbake dersom operasjonen feiler.

## Eksempler

### Varianter

Bruk variant for å velge farge, default er standard.
Du kan bruke <span class="highlight">variant</span> for å sette farger (når bryteren er på) :

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jeg synes det kan være litt forvirrende med denne setningen, sånn jeg forstår den kan jeg velge en hvilken som helst farge her. Kanskje det kan formuleres mer som: Du kan bruke variant for å velge mellom to farger: default eller primary


- <span class="highlight">default</span> som er standard
- <span class="highlight">primary</span>

<CodeExamplePreview>

```html
<nve-switch checked>Default</nve-switch>
<nve-switch variant="primary" checked>Primary</nve-switch>
</div>
<nve-switch checked>Default</nve-switch> <nve-switch checked variant="primary">Primary</nve-switch>
```

</CodeExamplePreview>

### Med ikoner

Bruk <span class="highlight">officon</span>- eller <span class="highlight">onicon</span>-sporet for å inkludere ikoner.

<CodeExamplePreview>

```html
<nve-switch>
<nve-icon slot="officon" name="light_mode"></nve-icon>
<nve-icon slot="onicon" name="dark_mode"></nve-icon>
</nve-switch>
<nve-switch variant="primary">
<nve-icon slot="officon" name="light_mode"></nve-icon>
<nve-icon slot="officon" name="light_mode"> </nve-icon>
<nve-icon slot="onicon" name="dark_mode"></nve-icon>
</nve-switch>
```
Expand All @@ -47,7 +88,8 @@ Se også [nve-darkmode-switch](/components/nve-darkmode-switch)

### Label

For å sette label foran switchen bruk `label-position="start"`. `end` er default.
For å sette label foran bryteren bruk <span class="highlight">label-position="start"</span>.
<span class="highlight">end</span> er default.
<CodeExamplePreview>

```html
Expand All @@ -56,36 +98,69 @@ For å sette label foran switchen bruk `label-position="start"`. `end` er defaul

</CodeExamplePreview>

### Bruk `disabled` for å deaktivere bryteren
### Gruppe med brytere

Hvis flere brytere presenteres som en logisk gruppe med en synlig ledetekst, skal gruppen markeres semantisk på én av følgende måter:

- Plasser bryterne i et element med `role="group"`, og bruk `aria-labelledby` for å referere til elementet som inneholder gruppens ledetekst.

<CodeExamplePreview>

```html
<div style="display: flex; flex-direction: column" role="group" aria-labelledby="label">
<span id="label">Gruppe</span>
<nve-switch> Bryter 1 </nve-switch>
<nve-switch> Bryter 2 </nve-switch>
</div>
```

</CodeExamplePreview>

- Plasser bryterne i et `<fieldset>`, og bruk `<legend>` som ledetekst for gruppen.

<CodeExamplePreview>

```html
<nve-switch disabled checked> </nve-switch>
<fieldset style="display: flex; flex-direction: column">
<legend>Gruppe</legend>
<nve-switch> Bryter 1 </nve-switch>
<nve-switch> Bryter 2 </nve-switch>
</fieldset>
```

</CodeExamplePreview>

### Hent ut verdien
### Deaktivert

<nve-message-card variant="warning" label="Obs!" size="compact">
<p>
En deaktivert bryter (<span class="highlight">disabled</span>) kan ikke få fokus og blir derfor ofte ikke oppdaget av
brukere som navigerer med tastatur eller skjermleser. Bruk <span class="highlight">disabled</span> med omhu, og vurder
å gi en tydelig forklaring i tekst på hvorfor feltet er deaktivert.
</p>
</nve-message-card>

Du kan hente ut `checked`-verdien akkurat som med en vanlig html-checkbox, enten via elementet eller på en event
Bruk attributtet <span class="highlight">disabled</span> for å hindre at bryter kan trykkes på.

Dersom du har html:
<CodeExamplePreview>

```html
<nve-switch onchange="changehandler" />
<nve-switch disabled> </nve-switch>
```

Så kan du ha
</CodeExamplePreview>

## Tilgjengelighet

```javascript
const checked = document.querySelector('nve-switch').checked;
// checked er nå true eller false
### Ledeteksten

// På event:
function changehandler(event) {
const checked = event.target.checked;
}
```
Bryter må alltid ha en ledetekst.

**Viktig:** Ledeteksten til en bryter skal ikke endres når bryterens tilstand endres. Ledeteksten skal beskrive hva bryteren styrer, mens bryterens av/på-tilstand formidles av selve bryteren.
<br>
Unngå derfor å bytte mellom for eksempel «Vis varsler» og «Skjul varsler» når bryterens tilstand endres.

### Beskrivelse

Du kan også bruke attributten `checked` på `nve-switch` for å sette verdien via lytting på change, tilsvarende som for en checkbox
<span class="highlight">nve-switch</span> er en webkomponent med Shadow DOM. ARIA-referanser som <span class="highlight">aria-describedby</span>, som baserer seg på en ID-referanse til et annet element, kan ikke uten videre brukes på tvers av Shadow DOM-grensen.
Hvis bryteren trenger mer forklaring enn det som er hensiktsmessig å ha i ledeteksten, kan du plassere en <a href="./nve-toggletip" class="highlight">nve-toggletip</a> ved siden av bryteren med utfyllende informasjon. Ledeteksten til bryteren bør fortsatt være tydelig nok til at brukeren kan forstå hva bryteren styrer uten å måtte åpne tilleggsinformasjonen.
120 changes: 33 additions & 87 deletions src/components/nve-switch/nve-switch.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { customElement, property, query, state } from 'lit/decorators.js';
import { customElement, property, query } from 'lit/decorators.js';
import { INveComponent } from '@interfaces/NveComponent.interface';
import { CSSResultArray, html, LitElement, TemplateResult } from 'lit';
import { watch } from '../../utils/watch';
import { CSSResultArray, html, LitElement } from 'lit';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { classMap } from 'lit/directives/class-map.js';
Expand All @@ -13,10 +12,7 @@ import styles from './nve-switch.styles';
* @slot onicon - Det første ikonet (til venstre). Synlig når status er PÅ
* @slot officon - Det andre ikonet (til høyre). Synlig når status er AV
*
* @event blur Bryter mister fokus
* @event change Bryter endres
* @event input Bryter endres
* @event focus Bryter får fokus
*
* @csspart base Topp-element
* @csspart control Element rundt bryteren
Expand All @@ -28,99 +24,49 @@ export default class NveSwitch extends LitElement implements INveComponent {
constructor() {
super();
}
@property({ reflect: true, type: String }) testId: string = '';

/** Hidden checkbox som holder state */
@query('input[type="checkbox"]') input!: HTMLInputElement;
@state() private hasFocus = false;
@property() title = ''; // make reactive to pass through

@property({ type: String }) testId: string = '';
/** Navn på switch */
@property() name = '';

/** Switchens verdi, bruk i forms */
@property() value: string = '';
@property({ type: Boolean, reflect: true }) disabled = false;

/** Verdien til switchen. */
@property({ type: Boolean, reflect: true }) checked = false;

/** Bestemmer fargevariant */
@property() variant: 'primary' | 'default' = 'default';

/** Plassering av label-tekst i forhold til bryteren */
@property({ attribute: 'label-position' }) labelPosition: 'start' | 'end' = 'end';

static styles: CSSResultArray = [styles];
private emit(eventname: string): void {
const event = new CustomEvent(eventname, {
bubbles: true,
cancelable: false,
composed: true,
detail: {},
});
this.dispatchEvent(event);
}

private handleBlur() {
this.hasFocus = false;
}

private handleInput() {
//do nothing, bubble event
}

private handleClick() {
this.checked = !this.checked;
this.emit('change');
}

private handleFocus() {
this.hasFocus = true;
}
private handleKeyDown(event: KeyboardEvent) {
if (event.key === 'ArrowLeft') {
event.preventDefault();
this.checked = false;
this.emit('change');
this.emit('input');
}
/** Hidden checkbox som holder state */
@query('input[type="checkbox"]') input!: HTMLInputElement;

if (event.key === 'ArrowRight') {
event.preventDefault();
this.checked = true;
this.emit('change');
this.emit('input');
}
}
@watch('checked', { waitUntilFirstUpdate: true })
handleCheckedChange() {
this.input!.checked = this.checked; // force a sync update
}
static styles: CSSResultArray = [styles];

/** click, focus og blur sendes til input (checkbox) */
click() {
this.input!.click();
}
focus(options?: FocusOptions) {
this.input!.focus(options);
}
blur() {
this.input!.blur();

private handleChange(e: Event) {
const input = e.target as HTMLInputElement;
this.checked = input.checked;
this.dispatchEvent(
new CustomEvent('change', {
bubbles: true,
composed: true,
detail: { value: this.value }, //usikker om vi trenger value her
})
);
Comment on lines +52 to +58
}

render(): TemplateResult {
return html`<div>
render() {
return html`
<label
part="base"
class=${classMap({
switch: true,
'switch--checked': this.checked,
'switch--disabled': this.disabled,
'switch--focused': this.hasFocus,
[`switch--${this.variant}`]: true,
[`switch--label-${this.labelPosition}`]: true
})}
'switch__label--start': this.labelPosition === 'start',
})}
>
<input
class="switch__input"
Expand All @@ -131,25 +77,25 @@ export default class NveSwitch extends LitElement implements INveComponent {
.checked=${live(this.checked)}
.disabled=${this.disabled}
role="switch"
aria-checked=${this.checked ? 'true' : 'false'}
aria-describedby="help-text"
@click=${this.handleClick}
@input=${this.handleInput}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@keydown=${this.handleKeyDown}
@change=${this.handleChange}
/>
<span part="control" class="switch__control">
<span part="thumb" class="switch__thumb"></span>
<span
part="control"
class=${classMap({
switch__control: true,
'switch--primary': this.variant === 'primary',
})}
>
<span class="switch__icon switch__officon"><slot name="officon"></slot></span>
<span part="thumb" class="switch__thumb"></span>
<span class="switch__icon switch__onicon"><slot name="onicon"></slot></span>
</span>

<div part="label">
<span part="label">
<slot></slot>
</div>
</span>
</label>
</div>`;
`;
}
}

Expand Down
Loading
Loading