Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
83f4aef
okay lets go!
fhi-designsystem-bot Feb 25, 2026
a55e114
wip
fhi-designsystem-bot Feb 25, 2026
b823ffd
wip using grid. missing rows
fhi-designsystem-bot Feb 27, 2026
3dc7acc
add rows functionality to grid
fhi-designsystem-bot Feb 27, 2026
3a54f97
edit the docs
fhi-designsystem-bot Feb 27, 2026
9871f69
change rows docs
dafn Feb 28, 2026
5078eeb
Merge branch '378-add-rows-to-grid' of https://github.com/FHIDev/Fhi.…
dafn Mar 2, 2026
4a21b19
wip
dafn Mar 2, 2026
f28524b
try out some examples
dafn Mar 2, 2026
6ca2dee
add some tokens
dafn Mar 3, 2026
61e7bfb
Merge branch 'main' of https://github.com/FHIDev/Fhi.Designsystem int…
dafn Mar 3, 2026
2c91f6e
add more tokens
dafn Mar 3, 2026
0907272
better example
dafn Mar 4, 2026
218b829
experiment with row elements
dafn Mar 4, 2026
796481d
fix some examples and remove unused apis
dafn Mar 5, 2026
108c499
change defaulr width
dafn Mar 11, 2026
0e94972
make row set cell as header
dafn Mar 11, 2026
d16e20d
clean stories
fhi-designsystem-bot Mar 12, 2026
7c6ef03
Merge branch 'main' of https://github.com/FHIDev/Fhi.Designsystem int…
fhi-designsystem-bot Mar 12, 2026
43ef619
only apply header variant to child cells
fhi-designsystem-bot Mar 12, 2026
6e1431c
add api definition for fhi-table
fhi-designsystem-bot Mar 12, 2026
58ee307
add api definitions forsub-components
fhi-designsystem-bot Mar 12, 2026
891e75c
work with new table component tokens
dafn Mar 18, 2026
555169e
Merge branch 'main' of https://github.com/FHIDev/Fhi.Designsystem int…
dafn Mar 18, 2026
843615a
add zebra example implementation
fhi-designsystem-bot Mar 20, 2026
245ce01
add jsdocs
fhi-designsystem-bot Apr 9, 2026
921bc43
zebra -> striped
fhi-designsystem-bot Apr 9, 2026
56d0591
update story
fhi-designsystem-bot Apr 9, 2026
4711fca
clean up
fhi-designsystem-bot Apr 9, 2026
9b136f4
wip table border
fhi-designsystem-bot Apr 9, 2026
02886e7
adjust the final rows border style to fit with the table border
fhi-designsystem-bot Apr 9, 2026
72361f0
add border radius token to row and override tokens on last row in table
fhi-designsystem-bot Apr 9, 2026
1e17153
refine fhi-table
fhi-designsystem-bot Apr 10, 2026
210b5ab
move column definition to fhi-table and add some guards
fhi-designsystem-bot Apr 10, 2026
c2bfead
Merge branch 'main' into 214-implement-first-iteration-of-table
dafn Apr 14, 2026
4b81fb0
add tests
dafn Apr 14, 2026
8cba267
add guard for table columns
dafn Apr 14, 2026
a985568
adjustments
dafn Apr 14, 2026
83aff3c
update example in JSDoc
dafn Apr 15, 2026
358d66f
Update packages/fhi-designsystem/src/components/table/fhi-table/fhi-t…
dafn Apr 16, 2026
1a22e50
Update packages/fhi-designsystem/src/components/table/docs.mdx
dafn Apr 16, 2026
53d16d7
remove unnecessary text
dafn Apr 16, 2026
a49a308
add white background for odd rows when striped
dafn Apr 16, 2026
82043dc
change confisung sizeing
dafn Apr 17, 2026
de04c95
add ellipsis to cells
dafn Apr 21, 2026
067167a
try out display: table
dafn Apr 22, 2026
90c4ccf
Merge branch 'main' into 214-implement-first-iteration-of-table
dafn Apr 22, 2026
a7bb661
Apply suggestions from code review
dafn Apr 28, 2026
39e7c27
rename to data table and adjust docs
dafn Apr 28, 2026
c333743
Merge branch '214-implement-first-iteration-of-table' of https://gith…
dafn Apr 28, 2026
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
33 changes: 33 additions & 0 deletions packages/fhi-designsystem/src/components/table/docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { FhiDataTable } from './fhi-data-table/fhi-data-table.component.ts';

import * as FhiDataTableStories from './fhi-data-table/fhi-data-table.stories.ts';
import * as FhiDataTableRowStories from './fhi-data-table-row/fhi-data-table-row.stories.ts';
import * as FhiDataTableCellStories from './fhi-data-table-cell/fhi-data-table-cell.stories.ts';

import { Meta, Primary, Canvas, Controls } from '@storybook/addon-docs/blocks';

import { ApiDefinition } from '../../../.storybook/blocks/api-definition.jsx';

<Meta of={FhiDataTableStories} />

# Data Table

Data babell brukes for å presentere data og informasjon på en ryddig måte. Brukeren kan raskt få oversikt og skanne innholdet effektivt.

Tabellen består av rader med celler som inneholder data. Rader defineres med `<fhi-data-table-row>`, og celler med `<fhi-data-table-cell>`.

## Passer til

- Å vise lister med større mengder strukturert data på en måte som er lett å lese og sammenligne.

<Canvas of={FhiDataTableStories.Preview} />
<Controls of={FhiDataTableStories.Preview} />

## Eksempler
<Canvas of={FhiDataTableStories.ComplexData} />
<br />
<Canvas of={FhiDataTableStories.WithCheckboxes} />

<ApiDefinition of={FhiDataTableStories.Preview} />
<ApiDefinition of={FhiDataTableRowStories.Preview} />
<ApiDefinition of={FhiDataTableCellStories.Preview} />
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { html, css, LitElement, PropertyValues } from 'lit';
import { customElement, property } from 'lit/decorators.js';

export const FhiDataTableCellSelector = 'fhi-data-table-cell';

/**
* ## FHI Table Cell
*
* {@link https://designsystem.fhi.no/?path=/docs/komponenter-data-table--docs}
*
* The `<fhi-data-table-cell>` component is an implementation of a table cell according to the FHI Design System guidelines.
* It allows users to properly display data within a `<fhi-data-table-row>`.
*
* The `<fhi-table-cell>` component does not use the native HTML `<td>` or `<th>` elements. Instead, it relies on CSS Grid to achieve the desired layout and styling.
*
* @tag fhi-data-table-cell
* @element fhi-data-table-cell
*/
@customElement(FhiDataTableCellSelector)
export class FhiDataTableCell extends LitElement {
/**
* Defines the variant of the table cell, which can be either 'header' or 'body'. This determines the styling and role of the cell within the table.
* @type {'header' | 'body'}
*/
@property({ type: String, reflect: true })
variant: 'header' | 'body' = 'body';

protected update(changedProperties: PropertyValues): void {
if (changedProperties.has('variant')) {
this.role = this.variant === 'header' ? 'columnheader' : 'cell';
}

super.update(changedProperties);
}

render() {
return html`
<div class="cell-content">
<slot></slot>
</div>
`;
}

static styles = css`
:host {
--fhi-data-table-cell-justify-content: unset;
--fhi-data-table-cell-align-items: unset;

--fhi-data-table-cell-border-style: unset;
--fhi-data-table-cell-border-width: unset;
--fhi-data-table-cell-border-color: unset;
}

:host {
--fhi-data-table-cell-justify-content: start;
--fhi-data-table-cell-align-items: center;

display: table-cell;

padding: var(--fhi-spacing-150);
color: var(--fhi-color-neutral-text-default);

border-style: var(--fhi-data-table-cell-border-style);
border-width: var(--fhi-data-table-cell-border-width);
border-color: var(--fhi-data-table-cell-border-color);

.cell-content {
display: flex;
justify-content: var(--fhi-data-table-cell-justify-content);
align-items: var(--fhi-data-table-cell-align-items);
}
}

:host([variant='body']) {
letter-spacing: var(--fhi-typography-body-medium-letter-spacing);

font: var(--fhi-typography-body-medium-font-weight)
var(--fhi-typography-body-medium-font-size) /
var(--fhi-typography-body-medium-line-height)
var(--fhi-font-family-default);
}

:host([variant='header']) {
white-space: nowrap;

letter-spacing: var(--fhi-typography-label-medium-letter-spacing);

font: var(--fhi-typography-label-medium-font-weight)
var(--fhi-typography-label-medium-font-size) /
var(--fhi-typography-label-medium-line-height)
var(--fhi-font-family-default);
}
`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { StoryObj } from '@storybook/web-components-vite';
import { html } from 'lit';
import { FhiDataTableCell } from './fhi-data-table-cell.component';
import { ifDefined } from 'lit/directives/if-defined.js';

import { FhiStorybookMeta } from '../../../../.storybook/fhi-meta';

new FhiDataTableCell();

const meta: FhiStorybookMeta<FhiDataTableCell> = {
title: 'Komponenter/Table/Cell',
component: 'fhi-data-table-cell',
parameters: {
slotTypes: [
{
name: '-',
description:
'Innholdet i cellen. Kan være tekst eller andre HTML-elementer.',
},
],
},
decorators: [],
render: args =>
html`<fhi-data-table-cell variant="${ifDefined(args.variant)}">
374 964
</fhi-data-table-cell>`,
argTypes: {
variant: {
control: 'select',
options: ['header', 'body'],
description:
'Definerer om cellen er en header eller body celle. Når <fhi-data-table-cell> brukes i en header <fhi-data-table-row>, vil variant automatisk bli satt til header.',
defaultValue: { summary: 'body' },
},
},
};

type Story = StoryObj<FhiDataTableCell>;

export const Preview: Story = {
tags: ['!dev'],
args: {},
};

export default meta;
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { fixture, expect } from '@open-wc/testing';
import { html } from 'lit/static-html.js';
import { FhiDataTableCell } from './fhi-data-table-cell.component';
import { FhiDataTableRow } from '../fhi-data-table-row/fhi-data-table-row.component';
import { FhiDataTable } from '../fhi-data-table/fhi-data-table.component';

describe('fhi-data-table-cell', () => {
new FhiDataTableCell();
new FhiDataTableRow();
new FhiDataTable();

let component: FhiDataTableCell;

describe('accessibility', () => {
beforeEach(async () => {
component = await fixture(
html`<fhi-data-table-cell></fhi-data-table-cell>`,
);
});

it('is accessible when it has a correct parent structure', async () => {
const table = await fixture(
html` <fhi-data-table>
<fhi-data-table-row>
<fhi-data-table-cell></fhi-data-table-cell>
</fhi-data-table-row>
</fhi-data-table>`,
);

const cell = table.querySelector('fhi-data-table-cell');
await expect(cell).to.be.accessible();
});

it('is not accessible when it does not have a correct parent', async () => {
await expect(component).not.to.be.accessible();
});

it('should have the role "cell" by default', async () => {
expect(component.getAttribute('role')).to.equal('cell');
});

it('should have the role "columnheader" when variant is set to "header"', async () => {
component.variant = 'header';
await component.updateComplete;

expect(component.getAttribute('role')).to.equal('columnheader');
});
});

describe('setting attributes', () => {
it('has an attribute to set the variant', async () => {
component = await fixture(
html`<fhi-data-table-cell variant="header"></fhi-data-table-cell>`,
);

expect(component.getAttribute('variant')).to.equal('header');
expect(component.variant).to.equal('header');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { html, css, LitElement, PropertyValues } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { queryAssignedElements } from 'lit/decorators.js';
import { FhiDataTableCell } from '../fhi-data-table-cell/fhi-data-table-cell.component';

export const FhiDataTableRowSelector = 'fhi-data-table-row';

/**
* ## FHI Table Row
*
* {@link https://designsystem.fhi.no/?path=/docs/komponenter-data-table-row--docs}
*
* The `<fhi-data-table-row>` component is an implementation of a table row according to the FHI Design System guidelines.
* It allows users to properly display data within a `<fhi-data-table>` using `<fhi-data-table-cell>` components.
*
* For various reasons, the `<fhi-data-table-row>` component does not use the native HTML `<tr>` element. Instead, it relies on CSS Grid to achieve the desired layout and styling.
*
* @tag fhi-data-table-row
* @element fhi-data-table-row
*/
@customElement(FhiDataTableRowSelector)
export class FhiDataTableRow extends LitElement {
/**
* Defines the variant of the table row, which can be either 'header' or 'body'. This determines the styling and role of the row within the table.
* If the variant is set to 'header', all child `<fhi-data-table-cell>` elements will also be set to the 'header' variant to ensure consistent styling.
* @type {'header' | 'body'}
*/
@property({ type: String, reflect: true })
variant: 'header' | 'body' = 'body';

@queryAssignedElements()
slotElements!: Array<HTMLElement>;

connectedCallback(): void {
super.connectedCallback();
this.role = 'row';
}

protected update(changedProperties: PropertyValues): void {
if (changedProperties.has('variant')) {
if (this.variant !== 'body' && this.variant !== 'header') {
this.variant = 'body';
}
}

super.update(changedProperties);
}

protected updated(changedProperties: PropertyValues): void {
if (changedProperties.has('variant')) {
this.setCellVariants();
}

super.updated(changedProperties);
}

private handleSlotChange() {
this.setCellVariants();
}

private setCellVariants() {
this.slotElements.forEach(element => {
if (element.tagName.toLowerCase() === 'fhi-data-table-cell') {
const tableCell = element as FhiDataTableCell;

if (tableCell.variant !== this.variant) {
tableCell.variant = this.variant;
}
}
});
}

render() {
return html`<slot @slotchange=${this.handleSlotChange}></slot>`;
}

static styles = css`
:host {
--fhi-data-table-row-border-style: unset;
--fhi-data-table-row-border-width: unset;
--fhi-data-table-row-border-color: unset;

--fhi-data-table-row-background: unset;
}

:host {
display: table-row;
background: var(--fhi-data-table-row-background);

--fhi-data-table-row-border-style: none none solid none;
--fhi-data-table-row-border-width: var(--fhi-dimension-border-width);
--fhi-data-table-row-border-color: var(
--fhi-color-neutral-surface-active
);

::slotted(fhi-data-table-cell) {
--fhi-data-table-cell-background: var(--fhi-data-table-row-background);
--fhi-data-table-cell-border-style: var(
--fhi-data-table-row-border-style
);
--fhi-data-table-cell-border-width: var(
--fhi-data-table-row-border-width
);
--fhi-data-table-cell-border-color: var(
--fhi-data-table-row-border-color
);
}
}
`;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { StoryObj } from '@storybook/web-components-vite';
import { html } from 'lit';
import { FhiDataTableRow } from './fhi-data-table-row.component';

import { FhiStorybookMeta } from '../../../../.storybook/fhi-meta';

new FhiDataTableRow();

const meta: FhiStorybookMeta<FhiDataTableRow> = {
title: 'Komponenter/Table/Row',
component: 'fhi-data-table-row',
parameters: {
slotTypes: [
{
name: '-',
description:
'Alle celler i raden. Bruk <fhi-data-table-cell> for å definere celler i raden.',
},
],
},
decorators: [],
render: () => html`<fhi-data-table-row></fhi-data-table-row>`,
argTypes: {
variant: {
control: 'select',
options: ['header', 'body'],
description:
'Definerer om raden er en header-rad eller en vanlig rad. Header-rader har en annen stil og brukes for å definere kolonneoverskrifter. Alle <fhi-data-table-cell> elementer i en header <fhi-data-table-row> vil automatisk få variant satt til header.',
defaultValue: { summary: 'body' },
},
},
};

type Story = StoryObj<FhiDataTableRow>;

export const Preview: Story = {
tags: ['!dev'],
args: {},
};

export default meta;
Loading
Loading