Hopp til innhold

Sass

Ved å bruke Sass får du et kompileringssteg for CSS, som gir ekstra trygghet og fleksibilitet.

Namespacing

I CSS deler alle stilark det samme globale namespacet. Det får konsekvenser for hvordan vi navngir:

  • klasser
  • CSS-variabler
  • animasjoner

I Jøkul skal disse starte med jkl for å synliggjøre hvor CSSen kommer fra og unngå navnekollisjoner.

@include jkl.light-mode-variables {
--jkl-link-color: #{jkl.$color-granitt};
}
@include jkl.dark-mode-variables {
--jkl-link-color: #{jkl.$color-snohvit};
}
.jkl-link {
--_jkl-link-private: #{jkl.$color-stein};
color: var(--jkl-color);
}

Block Element Modifier

Vi bruker metodikken Block Element Modifier (BEM) som navnekonvensjon i Jøkul. BEM og namespacing hjelper oss med å unngå navnekollisjoner både innad i Jøkul, og med brukerne av Jøkul.

Nøsting og parent selector (&) i Sass gjør det enklere å skrive CSS på denne måten.

.jkl-block {
/* ... */
&__element {
/* ... */
&--element-modifier {
/* ... */
}
}
&--block-modifier {
/* ... */
}
}

Unngå flere elementnivåer

Dersom du er i ferd med å skrive en block__element__another-element-selector, stopp og tenk om element egentlig er en annen block.

Moduler

I Jøkul bruker vi modul-syntaks med @use og @forward, ikke @import. Hver fil er en modul, på samme måte som du kanskje kjenner til fra TypeScript.

Moduler bør holdes til ett tema. Et tema kan for eksempel være CSSen for en komponent, mixins som har å gjøre med skjermstørrelser, eller funksjoner for konvertering av data.

Vi bruker ikke prefixes sammen med @forward. Grunnen er at vi ønsker at det skal være enkelt å navigere i Jøkul-koden. Man skal kunne finne igjen variabelen innad i Jøkul med det samme navnet som man kjenner fra det offentlige APIet.

Riktig

Vanlig forwarding uten å endre navn

Feil

Forwarding med prefix endrer navnet utad

Partials

Vi bruker partials (filer hvor navnet starter med _) for alle filer som ikke skal kompileres til sin egen CSS-fil, men brukes som en part i andre CSS-filer.Eksempler er moduler som består utelukkende av variabler, funksjoner og mixins. Det kan også være en modul med CSS for en komponent, men hvor alt skal samles opp i én enkelt CSS-fil til slutt (se for eksempel packages/table).

Mappestruktur

Hver mappe med Sass skal ha en _index.scss hvor det listes opp @forward for alle filer i mappa, litt som en index.ts kan gjøre i TypeScript.

For eksempel, hvis du har en mappe med to partials _one.scss og _two.scssmå den samme mappen ha en _index.scss som ser slik ut:

// _index.scss
@forward "one";
@forward "two";

Et unntak er på rotnivå i komponentpakkene. Her skal _index.scss ha én @forward som peker på hovedfila (for eksempel table.scss), og så har den fila ansvaret for å hente inn alt som skal være med.

// packages/table/_index.scss
@forward "table";
// packages/table/table.scss
@use "table-caption";
@use "table-cell";
@use "table-head";
@use "table-header";
@use "table-row";

Grunnen til dette unntaket er at vi ønsker at Sass skal lage en table.css fra koden vår. Det er enklest dersom vi har en table.scss som henter inn alt den trenger.

Hvorfor ha en index?

Index-fila er der for å gjøre det litt enklere å ta i bruk koden andre steder. Sammenlign for eksempel disse to kodesnuttene.

@forward "variables";
@forward "mixins";
@forward "functions";

I koden over har vi tre mapper: variablesmixins og functions. Jeg som bruker disse mappene trenger ikke forholde meg til hva som er i disse mappene. Jeg vil bare ha det som finnes av variabler, mixins og funksjoner.

@forward "variables/breakpoints";
@forward "variables/colors" as color-* hide varslingsfarge;
@forward "variables/shadow";
@forward "variables/shadows" as shadow-*;
@forward "variables/spacing" hide $spacing;
@forward "variables/typography" as typography-*;
@forward "variables/z-index";
@forward "mixins/helpers";
@forward "mixins/screens";
@forward "mixins/text-style";
@forward "mixins/ornaments";
@forward "mixins/motion";
@forward "mixins/screenreader";
@forward "mixins/underline";
@forward "mixins/navigation";
@forward "functions/convert";
@forward "functions/easing";
@forward "functions/timing";
@forward "functions/responsive-units";

I dette eksempelet har vi ingen index. Jeg som bruker er nødt til å hente inn alle enkeltfiler som finnes. Dersom noen legger til en ny fil må jeg sørge for å oppdatere koden min til å også hente inn denne. Koden min må også endres dersom én av filene endrer navn. Om jeg bare hadde forholdt meg til mappenavn hadde jeg ungått dette.

Variabler

Det er to typer variabler du må forholde deg til:

  • Sass-variabler som er tilgjengelige build time
  • CSS-variabler som er tilgjengelige run time

Vi starter med CSS-variabler.

CSS-variabler

Vi bruker CSS-variabler for verdier som kan tenkes å endres når brukeren ser på siden. Det vanligste er å bruke CSS-variabler for farger, siden de kan endres hvis brukeren bytter tema.

Alle CSS-variabler prefixes med jkl for å synliggjøre at en variabel kommer fra Jøkul og for å unngå navnekollisjoner.

Noen ganger trengs CSS-variabler internt i komponenten, uten intensjon om at de skal kunne endres utenfra. Om navnet starter med underscore betyr det at variabelen er privat. Disse er ikke en del av APIet og kan endres uten forvarsel.

@use "@fremtind/jkl-core/jkl";
@include jkl.light-mode-variables {
--jkl-table-row-border-color: #{jkl.$color-svaberg};
}
@include jkl.dark-mode-variables {
--jkl-table-row-border-color: #{jkl.$color-stein};
}
.jkl-table-row {
--_jkl-table-secret: "jeg kan endres uten forvarsel siden jeg starter med _";
border-color: var(--jkl-table-row-border-color);
}

Sass-variabler

Sass-variabler behandler vi som konstanter som har en fast verdi. Denne verdien kan gi mening å skille ut i en variabel for å unngå duplisering, eller for å tydeliggjøre en relasjon mellom verdier.

@use "@fremtind/jkl-core/jkl";
.jkl-table-row {
$_border-width: jkl.rem(1px);
border-width: $_border-width;
thead > & {
border-width: $_border-width;
}
}

Med unntak av i packages/core skal variabler som regel være private. Private variabler kan ikke brukes av andre moduler. Varibler er private om navnet starter med _ (underscore).

I motsetning til CSS-variabler skal public variabler ikke prefikses med jkl. De fleste variabler er uansett private. For public variabler lener vi oss på Sass sitt modulsystem med innebygget namespacing, så et prefiks er unødvendig.

Variabler bør som regel være definert nærmest mulig der den brukes. Hvis en variabel bare er relevant innefor én selector, definer den der.

Variabler som er definert utenfor en CSS selector anses som globale innenfor sin modul. Globale variabler bør være definert øverst i fila, under @use-statements.

Mixins

Med unntak av i packages/core skal mixins som regel være private. Private mixins kan ikke brukes av andre moduler. Mixins er private om navnet starter med _ (underscore).

Animasjoner

CSS-animasjoner er ansett som private, ikke som en del av det offisielle APIet til en pakke. Vi bruker et mønster basert på interpolation og string.unique-id()for å få garantert unike IDer per bygg.

Vi prefikser likevel animasjonsnavnet med jkl for å synliggjøre at animasjonen kommer fra Jøkul.

$_dismiss-animation-name: jkl-dismiss-#{string.unique-id()};
.jkl-alert-message {
/* ... */
animation: $_dismiss-animation-name jkl.timing("lazy") jkl.easing("exit") forwards;
/* ... */
}
@keyframes #{$_dismiss-animation-name} {
from {
/* ... */
}
to {
/* ... */
}
}

Offentlig API

Mappenavn og filnavn er ikke en del av APIet til en pakke og kan endres uten forvarsel, med unntak av:

  • Filnavnet som matcher pakkens navn, for eksempel core.scss og table.scss. Disse navnene brukes av prosjekter som importerer CSS.
  • packages/core/_jkl.scss, som vi ser på som "hovedinngangen" til Sass-verktøy som mixins, funksjoner og variabler i core

Stabilt

@use "@fremtind/jkl-core/jkl";
@use "@fremtind/jkl-table/table";

Ikke stabilt

@use "@fremtind/jkl-core/functions";
@use "@fremtind/jkl-core/mixins";
@use "@fremtind/jkl-core/variables";
@use "@fremtind/jkl-table/table-row";

Variabler

Sass-variabler som er public er en del av pakkens API. En variabel er public dersom navnet ikke starter med _ (underscore) og variabelen er definert i det globale scopet (utenfor en CSS selector).

Sass-variabler som er public skal dokumenteres med SassDoc.

Navnet til CSS- og Sass-variabelen og typen verdi (for eksempel String) er å anse som stabilt.

/* $_example-padding er brukt av to selectors, og hører hjemme som en global variabel */
$_example-padding: jkl.$spacing-l;
$_code-padding: jkl.$spacing-s;
/// Variabler som ikke starter med _ og som er definert i globalt scope er en del av pakkens API og kan brukes av andre
/// @type String
$i-am-public: "Hello, world!";
.jkl-example-1 {
padding: $_example-padding;
&__design {
$_design-padding: jkl.$spacing-s;
padding: $_design-padding;
}
}
.jkl-example-2 {
padding: $_example-padding;
&__code {
/* $_code-padding er bare brukt her, og burde vært definert her, slik som $_design-padding */
padding: $_code-padding: jkl.$spacing-s;
}
}

Mixins og funksjoner

Public mixins og funksjoner er en del av pakkens API. De er public dersom navnet ikke starter med _ (underscore). Mixins og funksjoner som er public skal dokumenteres med SassDoc.

I public mixins og funksjoner er endring i navnet på parametere å anse som en breaking change. Det er fordi utviklere kan ha brukt keyword arguments.

Om du vil endre navnet på en bakoverkompatibel måte kan du beholdet det gamle navnet som et optional argument og håndtere begge parametere internt.

// Keyword arguments i bruk. Hvis navnet
// på parameternavnet endres vil det brekke koden.
@include jkl.forced-colors-svg-fallback($stroke: ButtonText, $fill: ButtonText);

SassDoc

All Sass-kode som er en del av pakkens offentlige API skal dokumenteres med SassDoc.

/// Kalkuler riktig rem-verdi basert på en gitt pixelverdi
/// @param {Number} $px-size - Pixelverdi, helst med unit
/// @return {Number} - Pixelverdien konvertert til rem
/// @example
/// jkl.rem(16px);
@function rem($px-size) {
// ...
}

De viktigste annotasjonene

Det er typisk bare et utvalg dem du trenger å vite om, men se gjerne over listen med tilgjengelige annotasjoner.

Her er en oppsummering av de viktigste:

  • @content bruker vi til å beskrive hvordan mixins som har en @content bruker den.
  • @deprecated bruker vi for å gi hint om at noe ikke bør brukes lenger, helst med forklaring om hva som bør brukes i stedet. Ting merket @deprecated vil typisk bli fjernet fra Jøkul etter hvert.
  • @example er fint for å gi et kodeeksempel.
  • @parameter beskriver hvert enkelt parameter i funksjonen/mixinen.
  • @return beskriver kort hva funksjonen returnerer.

Du trenger ikke alltid bruke alle annotasjonene. Dokumenter det du synes gir mening, så kan det heller finpusses under code review.

Under er noen eksempler på hvordan du kan bruke de ulike annotasjonene.

/// Tilsvarer 16px
/// @type Number
$spacing-16: 1rem;
/// Forenkler media queries som skal gjelde mellom to skjermbredder.
/// Maksverdien er _eksklusiv_ (verdien vil bli $max - 1px).
/// @content Plasseres i et media query med min-width lik $min og max-width lik $max - 1px
/// @example
/// .class {
/// @include jkl.screen-between(42px, 420px) {
/// display: none;
/// }
/// }
@mixin screen-between($min, $max) {
@media (min-width: $min) and (max-width: #{$max - 1px}) {
@content;
}
}
/// @deprecated Bruk heller .jkl-nav-card, .jkl-task-card eller .jkl-info-card
.jkl-card {
// ...
}
/// Hjelper for å sette riktig farge på SVGer i Chrome for brukere med Forced Color-modus.
/// Se https://melanie-richards.com/blog/currentcolor-svg-hcm/ for detaljer
/// @param {"Canvas" | "CanvasText" | "LinkText" | "GrayText" | "Highlight" | "HighlightText" | "ButtonFace" | "ButtonText"} $stroke - Definer hvilken systemfarge som skal brukes til stroke. Fargene har en forventet betydning for brukeren. Følg den semantiske betydningen til fargen, ikke velg fargen du selv synes "ser best ut".
/// @param {"Canvas" | "CanvasText" | "LinkText" | "GrayText" | "Highlight" | "HighlightText" | "ButtonFace" | "ButtonText"} $fill [null] - Som $stroke, bare for fill. Valgfri.
/// @output Setter angitte verdier på nåværende selector og dens svg og path children, inni et media query som treffer dersom forced-colors er aktiv.
@mixin forced-colors-svg-fallback($stroke, $fill: null) {
// ...
}