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
7 changes: 7 additions & 0 deletions doc-site/components/Komponentoversikt.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ nodeId er ID til komponent-sida i Figma. Den ligger som en parameter i URL'en ti
statusDesign: 'Ferdig',
statusCode: 'Ferdig'
},
{
name: 'nve-aspect-rose',
nodeId: undefined,
description: undefined,
statusDesign: 'Ferdig',
statusCode: 'Ferdig'
},
Comment thread
gruble marked this conversation as resolved.
{
name: 'nve-attachments',
nodeId: '647-192139',
Expand Down
84 changes: 84 additions & 0 deletions doc-site/components/nve-aspect-rose.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
---
layout: component
outline: [2, 3]
---

<CodeExamplePreview>

```html
<nve-aspect-rose value="11000001"></nve-aspect-rose>
```

</CodeExamplePreview>

## Eksempler

### Utsatte himmelretninger

Bruk `value` for å sette hvilke himmelretninger som er utsatt (farlige).
<CodeExamplePreview>

```html
<nve-aspect-rose></nve-aspect-rose> Ingen <nve-aspect-rose value="10000000"> </nve-aspect-rose> Kun nord
<nve-aspect-rose value="00111100"></nve-aspect-rose> øst, sør-øst, sør og sør-vest
<nve-aspect-rose value="11111111"></nve-aspect-rose> Alle <nve-aspect-rose value="1111111"></nve-aspect-rose> Verdien
blir ignorert hvis vi ikke har eksakt 8 sifre
```

</CodeExamplePreview>

### Størrelse

Bruk css-variabelen `--aspect-rose-size` for å endre bredde og høyde. 90px er standard.

<CodeExamplePreview>

```html
<nve-aspect-rose style="--aspect-rose-size: 60px" value="00111110"></nve-aspect-rose> 60px
<nve-aspect-rose value="00111110"></nve-aspect-rose> 90px (standard)
<nve-aspect-rose style="--aspect-rose-size: 120px" value="00111110"></nve-aspect-rose> 120px
```

</CodeExamplePreview>

### Språk

Bruk `lang` for å angi språk for kompassretningene. `no` (norsk) er standard. Kun norsk og engelsk støttes.
<CodeExamplePreview>

```html
<nve-aspect-rose value="00111110"></nve-aspect-rose> Norsk
<nve-aspect-rose lang="en" value="00111110"></nve-aspect-rose> Engelsk
```

</CodeExamplePreview>

### Aria-label og svg-title

Bruk `label` for å overstyre standard-tekstene for aria-label og svg title.

<CodeExamplePreview>

```html
Hold muspeker over for å se label.<br />
<nve-aspect-rose value="00111110"></nve-aspect-rose>Med standard norsk tekst
<nve-aspect-rose lang="en" value="00111110"></nve-aspect-rose>Med standard engelsk tekst
<nve-aspect-rose label="merkelapp" value="00111110"></nve-aspect-rose>Overstyrt tekst
```

</CodeExamplePreview>

### Farger

Bruk css-variablene`--aspect-rose-outline-color`, `--aspect-rose-affected-color`og`--aspect-rose-unaffected-color` for å overstyre fargene.

<CodeExamplePreview>

```html
<nve-aspect-rose
style="--aspect-rose-outline-color: green; --aspect-rose-affected-color: black;--aspect-rose-unaffected-color: white"
value="00111110"
></nve-aspect-rose>
```

</CodeExamplePreview>
100 changes: 100 additions & 0 deletions src/components/nve-aspect-rose/nve-aspect-rose.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { html, svg, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { INveComponent } from '@interfaces/NveComponent.interface';
import styles from './nve-aspect-rose.styles';
import { ifDefined } from 'lit/directives/if-defined.js';

/**
* Viser utsatte himmelretninger som en kompassrose.
* Rosen er delt opp i 8 sektorer. Utsatte sektorer vises i rødt.
* @cssproperty --aspect-rose-size - Høyde og bredde på komponenten. 90px er standard.
* @cssproperty --aspect-rose-outline-color - Farge på sirkelens omriss. Standard er #c6c6c5.
* @cssproperty --aspect-rose-affected-color - Farge på utsatte sektorer. Standard er #d21523.
* @cssproperty --aspect-rose-unaffected-color - Farge på ikke-utsatte sektorer. Standard er #e3e3e3.
*/
@customElement('nve-aspect-rose')
export default class NveAspectRose extends LitElement implements INveComponent {
@property({ type: String }) testId: string | undefined = undefined;

/**
* 8-tegns binærtekst som representerer utsatte sektorer.
* Starter med nordlig sektor og går deretter med klokka.
* Eksempel: "00111110"
*/
@property({ type: String }) value: string = '00000000';

/** Språk for himmelretningene. 'no' for norsk, 'en' for engelsk. */
@property({ type: String }) lang: 'no' | 'en' = 'no';

/**
* Tilgjengelig tittel.
* Vises som aria-label på SVG-elementet og som <title> i SVG.
* Standardverdi avhenger av språket: 'Eksponerte sektorer' for norsk, 'Affected aspects' for engelsk.
* Du kan overstyre denne teksten.
*/
@property({ type: String }) label: string | undefined = undefined;

static styles = [styles];

private get effectiveLabel() {
return this.label ?? (this.lang === 'en' ? 'Affected aspects' : 'Eksponerte sektorer');
}
Comment thread
gruble marked this conversation as resolved.

// Etiketter for de fire himmelretningene. Brukes til å plassere sirklene og teksten inni dem.
private get directions() {
return [
{ cx: 45, cy: 9, label: 'N' },
{ cx: 81, cy: 45, label: this.lang === 'en' ? 'E' : 'Ø' },
{ cx: 45, cy: 81, label: 'S' },
{ cx: 9, cy: 45, label: this.lang === 'en' ? 'W' : 'V' },
];
}

// Gradene for å rotere sektorene slik at de dekker riktig himmelretning. Starter med nordlig sektor og går deretter med klokka.
private readonly rotations = [-22.5, 22.5, 67.5, 112.5, 157.5, 202.5, 247.5, 292.5];

render() {
const value = /^[01]{8}$/.test(this.value) ? this.value : '00000000'; // Ignorer value hvis den ikke er gyldig
return html`
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 90 90"
width="100%"
height="100%"
role="img"
aria-label=${this.effectiveLabel}
testid=${ifDefined(this.testId)}
>
${svg`<title>${this.effectiveLabel}</title>`}
<g>
${this.rotations.map(
(rot, i) => svg`
<path
d="M 45 9 A 36 36 0 0 1 70.45584412271572 19.54415587728429 L 45 45 Z"
transform="rotate(${rot} 45 45)"
stroke-width="1"
stroke="var(--aspect-rose-outline-color, #c6c6c5)"
class=${value[i] === '1' ? 'sector-affected' : 'sector-unaffected'}
></path>
`
)}
<circle cx="45" cy="45" r="36" fill="none" stroke-width="1" class="circle-outline"></circle>
</g>
${this.directions.map(
({ cx, cy, label }) => svg`
<g>
<circle cx=${cx} cy=${cy} r="7.2" stroke-width="1" stroke="var(--aspect-rose-outline-color, #c6c6c5)" fill="#fff"></circle>
<text x=${cx} y=${cy} dy="3.168" font-size="7.92" text-anchor="middle" fill="#6a7a7b">${label}</text>
</g>
`
)}
</svg>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
'nve-aspect-rose': NveAspectRose;
}
}
24 changes: 24 additions & 0 deletions src/components/nve-aspect-rose/nve-aspect-rose.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { css } from 'lit';

export default css`
:host {
display: inline-block;
width: var(--aspect-rose-size, 90px);
height: var(--aspect-rose-size, 90px);
}

text {
font-family: 'Source Sans 3', sans-serif;
font-weight: var(--font-weight-regular);
}

.circle-outline {
stroke: var(--aspect-rose-outline-color, #c6c6c5);
}
.sector-affected {
fill: var(--aspect-rose-affected-color, #d21523);
}
.sector-unaffected {
fill: var(--aspect-rose-unaffected-color, #e3e3e3);
}
`;
118 changes: 118 additions & 0 deletions src/components/nve-aspect-rose/nve-aspect-rose.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { afterAll, describe, expect, it } from 'vitest';
import { fixture, fixtureCleanup } from '@open-wc/testing';
import { html } from 'lit';
import NveAspectRose from './nve-aspect-rose.component';

if (!customElements.get('nve-aspect-rose')) {
customElements.define('nve-aspect-rose', NveAspectRose);
}

describe('nve-aspect-rose', () => {
afterAll(() => {
fixtureCleanup();
});

it('is attached to the DOM', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose></nve-aspect-rose>`);
expect(document.body.contains(el)).toBe(true);
});

it('has correct default properties', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose></nve-aspect-rose>`);
expect(el.value).toBe('00000000');
expect(el.lang).toBe('no');
expect(el.label).toBeUndefined();
});

it('renders 8 sectors', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose></nve-aspect-rose>`);
const paths = el.shadowRoot?.querySelectorAll('path');
expect(paths?.length).toBe(8);
});

it('marks affected sectors with correct css class', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose value="10000001"></nve-aspect-rose>`);
const paths = el.shadowRoot?.querySelectorAll('path');
expect(paths?.[0].classList.contains('sector-affected')).toBe(true);
expect(paths?.[1].classList.contains('sector-unaffected')).toBe(true);
expect(paths?.[7].classList.contains('sector-affected')).toBe(true);
});

it('marks all sectors as unaffected when value is all zeros', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose value="00000000"></nve-aspect-rose>`);
const paths = el.shadowRoot?.querySelectorAll('path');
paths?.forEach((path) => {
expect(path.classList.contains('sector-unaffected')).toBe(true);
});
});

it('marks all sectors as affected when value is all ones', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose value="11111111"></nve-aspect-rose>`);
const paths = el.shadowRoot?.querySelectorAll('path');
paths?.forEach((path) => {
expect(path.classList.contains('sector-affected')).toBe(true);
});
});

it('marks all sectors as unaffected when value is not valid', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose value="1"></nve-aspect-rose>`);
const paths = el.shadowRoot?.querySelectorAll('path');
paths?.forEach((path) => {
expect(path.classList.contains('sector-unaffected')).toBe(true);
});
});

it('shows norwegian direction labels by default', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose></nve-aspect-rose>`);
const texts = el.shadowRoot?.querySelectorAll('text');
const labels = Array.from(texts ?? []).map((t) => t.textContent);
expect(labels).toContain('N');
expect(labels).toContain('Ø');
expect(labels).toContain('S');
expect(labels).toContain('V');
});

it('shows norwegian direction labels by default even if lang is set to something else', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose lang="fr"></nve-aspect-rose>`);
const texts = el.shadowRoot?.querySelectorAll('text');
const labels = Array.from(texts ?? []).map((t) => t.textContent);
expect(labels).toContain('N');
expect(labels).toContain('Ø');
expect(labels).toContain('S');
expect(labels).toContain('V');
});

it('shows english direction labels when lang is en', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose lang="en"></nve-aspect-rose>`);
const texts = el.shadowRoot?.querySelectorAll('text');
const labels = Array.from(texts ?? []).map((t) => t.textContent);
expect(labels).toContain('N');
expect(labels).toContain('E');
expect(labels).toContain('S');
expect(labels).toContain('W');
});

it('uses custom label as aria-label', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose label="Min tittel"></nve-aspect-rose>`);
const svg = el.shadowRoot?.querySelector('svg');
expect(svg?.getAttribute('aria-label')).toBe('Min tittel');
});

it('uses default norwegian label when no label is set', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose></nve-aspect-rose>`);
const svg = el.shadowRoot?.querySelector('svg');
expect(svg?.getAttribute('aria-label')).toBe('Eksponerte sektorer');
});

it('uses default norwegian label when no label is set even if lang is set to something else', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose lang="fr"></nve-aspect-rose>`);
const svg = el.shadowRoot?.querySelector('svg');
expect(svg?.getAttribute('aria-label')).toBe('Eksponerte sektorer');
});

it('uses default english label when lang is en and no label is set', async () => {
const el = await fixture<NveAspectRose>(html`<nve-aspect-rose lang="en"></nve-aspect-rose>`);
const svg = el.shadowRoot?.querySelector('svg');
expect(svg?.getAttribute('aria-label')).toBe('Affected aspects');
});
});
2 changes: 2 additions & 0 deletions src/nve-designsystem.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
/** Alle komponenter som er tilgjengelige, i alfabetisk rekkefølge. */
/** Denne filen blir genererert av pnpm run add-component */

export { default as NveAccordion } from './components/nve-accordion/nve-accordion.component';
export { default as NveAccordionItem } from './components/nve-accordion-item/nve-accordion-item.component';
export { default as NveAlert } from './components/nve-alert/nve-alert.component';
export { default as NveAspectRose } from './components/nve-aspect-rose/nve-aspect-rose.component';
export { default as NveBadge } from './components/nve-badge/nve-badge.component';
export { default as NveButton } from './components/nve-button/nve-button.component';
export { default as NveCarousel } from './components/nve-carousel/nve-carousel.component';
Expand Down
Loading