かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

Sass mixin の中で呼び出される @content に引数を渡したい

Tailwind の md:flex みたいなレスポンシブのクラスみないな prefix のあるクラスと楽に作れる mixin を作りたかった

環境
  • sass: 1.42.1 (Dart Sass)

レスポンシブ用の prefix を付けたクラスを生成する

_breakpoints.scss

$breakpoints: (
  xzs: 0,
  xs: 23.375rem,
  sm: 36rem,
  md: 48rem,
  lg: 62rem,
  xl: 75rem,
  xxl: 87.5rem,
);

@function breakpoint($name, $breakpoints: $breakpoints) {
  $breakpoint: map-get($breakpoints, $name);
  @return if($breakpoint != 0, $breakpoint, null);
}

// media screen and (min-width: breakpoint) { … } なスタイルを出力する mixin
@mixin media-breakpoint-up($key, $breakpoints: $breakpoints) {
  $min: breakpoint($key, $breakpoints);
  @if $min {
    @media screen and (min-width: $min) {
      @content;
    }
  } @else {
    @content;
  }
}

_buttons.scss

@use './breakpoints' as bp;

@each $key, $value in bp.$breakpoints {
  @if $value > 0 {
    $prefix: #{$key}\:;
  } @else {
    $prefix: '';
  }

  @include bp.media-breakpoint-up($key) {
    .#{$prefix}btn--xs {}
    .#{$prefix}btn--sm {}
    .#{$prefix}btn--md {}
    .#{$prefix}btn--lg {}
  }
}

これで .btn--xs, sm:btn-xs, … のようなレスポンシブ用のクラスを作成できるのですが、コンポーネントのスタイルを作成する CSS ファイル内に都度レスポンシブ用の @each を書くのは面倒だし見通しがよろしく無いです。

// _buttons.scss

@include bp.media-breakpoint-up($key) {
    // ここを @content; にして処理を分離したい
    .#{$prefix}btn--xs {}
    .#{$prefix}btn--sm {}
    .#{$prefix}btn--md {}
    .#{$prefix}btn--lg {}
  }
}

Dart Sass 1.15 以降では @content に引数を渡すことができる

@content Arguments
Mixins that take @content blocks can now pass arguments to those blocks. This is written @content(<arguments...>). If a mixin passes arguments to its content block, users of that mixin must accept those arguments by writing @include using (<arguments...>). The argument list for a content block works just like a mixin’s argument list, and the arguments passed to it by @content work just like passing arguments to a mixin.
cf. https://sass-lang.com/blog/page/3#content-arguments

@mixin media($types...) {
  @each $type in $types {
    @media #{$type} {
      @content($type);
    }
  }
}

@include media(screen, print) using ($type) {
  h1 {
    font-size: 40px;
    @if $type == print {
      font-family: Calluna;
    }
  }
}

@content(引数); で引数を渡し、mixin を呼び出す側は @include mixin useing (引数) で変数を受け取ることができるようです。
これを用いて先のレスポンシブなクラスを作る処理をリファクタリングします。

_breakpoints.scss

$breakpoints: (
  xzs: 0,
  xs: 23.375rem,
  sm: 36rem,
  md: 48rem,
  lg: 62rem,
  xl: 75rem,
  xxl: 87.5rem,
);

@function breakpoint($name, $breakpoints: $breakpoints) {
  $breakpoint: map-get($breakpoints, $name);
  @return if($breakpoint != 0, $breakpoint, null);
}

// media screen and (min-width: breakpoint) { … } なスタイルを出力する mixin
@mixin media-breakpoint-up($key, $breakpoints: $breakpoints) {
  $min: breakpoint($key, $breakpoints);
  @if $min {
    @media screen and (min-width: $min) {
      @content;
    }
  } @else {
    @content;
  }
}

@mixin every-breakpoint-up($breakpoints: $breakpoints) {
  @each $key, $value in $breakpoints {
    @if $value > 0 {
      $prefix: #{$key}\:;
    } @else {
      $prefix: '';
    }
     
    @include media-breakpoint-up($key) {
      @content ($prefix);
    }
  }
}

_buttons.scss

@use './breakpoints' as bp;

@include bp.every-breakpoint-up() using ($prefix) {
  .#{$prefix}btn--xs {}
  .#{$prefix}btn--sm {}
  .#{$prefix}btn--md {}
  .#{$prefix}btn--lg {}
}

全ブレイクポイントの media query を吐き出す処理を分離できたので、取り回しが良くなりました!

所管

Sass コンパイルエラーがわかりづらすぎてめんどい
おわり


[参考]