diff --git a/doc-site/components/Komponentoversikt.md b/doc-site/components/Komponentoversikt.md
index a2b09374..0521ae63 100644
--- a/doc-site/components/Komponentoversikt.md
+++ b/doc-site/components/Komponentoversikt.md
@@ -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'
+ },
{
name: 'nve-attachments',
nodeId: '647-192139',
diff --git a/doc-site/components/nve-aspect-rose.md b/doc-site/components/nve-aspect-rose.md
new file mode 100644
index 00000000..46d78da3
--- /dev/null
+++ b/doc-site/components/nve-aspect-rose.md
@@ -0,0 +1,84 @@
+---
+layout: component
+outline: [2, 3]
+---
+
+
+
+```html
+
+```
+
+
+
+## Eksempler
+
+### Utsatte himmelretninger
+
+Bruk `value` for å sette hvilke himmelretninger som er utsatt (farlige).
+
+
+```html
+ Ingen Kun nord
+ øst, sør-øst, sør og sør-vest
+ Alle Verdien
+blir ignorert hvis vi ikke har eksakt 8 sifre
+```
+
+
+
+### Størrelse
+
+Bruk css-variabelen `--aspect-rose-size` for å endre bredde og høyde. 90px er standard.
+
+
+
+```html
+ 60px
+ 90px (standard)
+ 120px
+```
+
+
+
+### Språk
+
+Bruk `lang` for å angi språk for kompassretningene. `no` (norsk) er standard. Kun norsk og engelsk støttes.
+
+
+```html
+ Norsk
+ Engelsk
+```
+
+
+
+### Aria-label og svg-title
+
+Bruk `label` for å overstyre standard-tekstene for aria-label og svg title.
+
+
+
+```html
+Hold muspeker over for å se label.
+Med standard norsk tekst
+Med standard engelsk tekst
+Overstyrt tekst
+```
+
+
+
+### Farger
+
+Bruk css-variablene`--aspect-rose-outline-color`, `--aspect-rose-affected-color`og`--aspect-rose-unaffected-color` for å overstyre fargene.
+
+
+
+```html
+
+```
+
+
diff --git a/src/components/nve-aspect-rose/nve-aspect-rose.component.ts b/src/components/nve-aspect-rose/nve-aspect-rose.component.ts
new file mode 100644
index 00000000..53744778
--- /dev/null
+++ b/src/components/nve-aspect-rose/nve-aspect-rose.component.ts
@@ -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
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');
+ }
+
+ // 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`
+
+ `;
+ }
+}
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'nve-aspect-rose': NveAspectRose;
+ }
+}
diff --git a/src/components/nve-aspect-rose/nve-aspect-rose.styles.ts b/src/components/nve-aspect-rose/nve-aspect-rose.styles.ts
new file mode 100644
index 00000000..57f1c7b1
--- /dev/null
+++ b/src/components/nve-aspect-rose/nve-aspect-rose.styles.ts
@@ -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);
+ }
+`;
diff --git a/src/components/nve-aspect-rose/nve-aspect-rose.test.ts b/src/components/nve-aspect-rose/nve-aspect-rose.test.ts
new file mode 100644
index 00000000..fb5cfedb
--- /dev/null
+++ b/src/components/nve-aspect-rose/nve-aspect-rose.test.ts
@@ -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(html``);
+ expect(document.body.contains(el)).toBe(true);
+ });
+
+ it('has correct default properties', async () => {
+ const el = await fixture(html``);
+ expect(el.value).toBe('00000000');
+ expect(el.lang).toBe('no');
+ expect(el.label).toBeUndefined();
+ });
+
+ it('renders 8 sectors', async () => {
+ const el = await fixture(html``);
+ const paths = el.shadowRoot?.querySelectorAll('path');
+ expect(paths?.length).toBe(8);
+ });
+
+ it('marks affected sectors with correct css class', async () => {
+ const el = await fixture(html``);
+ 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(html``);
+ 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(html``);
+ 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(html``);
+ 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(html``);
+ 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(html``);
+ 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(html``);
+ 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(html``);
+ 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(html``);
+ 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(html``);
+ 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(html``);
+ const svg = el.shadowRoot?.querySelector('svg');
+ expect(svg?.getAttribute('aria-label')).toBe('Affected aspects');
+ });
+});
diff --git a/src/nve-designsystem.ts b/src/nve-designsystem.ts
index 7f16f435..764696ce 100644
--- a/src/nve-designsystem.ts
+++ b/src/nve-designsystem.ts
@@ -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';