Commit ae024e8a authored by Pierre Dureau's avatar Pierre Dureau
Browse files

feat: #3531854 Add a Design Tokens & CSS variables API

By: pdureau
By: d34dman
By: grimreaper
By: rikki_iki
By: larowlan
By: kim.pepper
By: wim leers
By: penyaskito
By: e0ipso
By: 4aficiona2
parent 0f7caab8
Loading
Loading
Loading
Loading
Loading
+48 −0
Changes for core/config/schema/core.design_token_values.schema.yml: 48 added lines, 0 removed lines.
Original line number Diff line number Diff line
design_token_value.dimension:
  type: mapping
  label: 'Dimension'
  mapping:
    value:
      type: float
      label: 'Value'
    unit:
      type: string
      label: 'Unit'

design_token_value.fontFamily:
  type: sequence
  label: 'Font families'
  sequence:
    type: string

design_token_value.fontWeight:
  type: integer
  label: 'Font weight'
  constraints:
    Range:
      min: 1
      max: 1000

design_token_value.number:
  type: float
  label: 'Number'

design_token_value.typography:
  type: mapping
  label: 'Typography'
  mapping:
    fontFamily:
      type: design_token_value.fontFamily
      label: 'Font families'
    fontSize:
      type: design_token_value.dimension
      label: 'Font size'
    fontWeight:
      type: design_token_value.fontWeight
      label: 'Font weight'
    letterSpacing:
      type: design_token_value.dimension
      label: 'Letter spacing'
    lineHeight:
      type: design_token_value.number
      label: 'Line height'
+33 −0
Changes for core/config/schema/core.entity.schema.yml: 33 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -89,6 +89,39 @@ core.entity_view_display.*.*.*:
        type: boolean
        label: 'Value'

core.design_token.*:
  type: config_entity
  label: 'Design token'
  constraints:
    FullyValidatable: ~
  mapping:
    id:
      type: machine_name
      label: 'ID'
    path:
      type: string
      label: 'The CSS variable path'
      constraints:
        Regex:
          pattern: '/^[a-z0-9_\s\-\.]+$/'
          message: "The %value path is not valid."
    type:
      type: string
      label: 'The design token value type'
      constraints:
        PluginExists:
          manager: plugin.manager.design_token_value
          interface: 'Drupal\Core\Theme\DesignToken\DesignTokenValueInterface'
    scopes:
      type: sequence
      label: 'The scopes of the value overrides.'
      sequence:
        type: design_token_value.[%parent.%parent.type]
      constraints:
        ValidSequenceKeys:
          constraints:
            CssSelector: []

field_formatter:
  type: mapping
  label: 'Field formatter'
+12 −0
Changes for core/core.services.yml: 12 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -2085,6 +2085,18 @@ services:
      ]
    tags:
      - { name: needs_destruction }
  plugin.manager.design_token:
    class: Drupal\Core\Theme\DesignToken\DesignTokenPluginManager
    arguments:
      - '@module_handler'
      - '@theme_handler'
      - '@cache.discovery'
      - '@plugin.manager.design_token_value'
  Drupal\Core\Theme\DesignToken\DesignTokenPluginManagerInterface: '@plugin.manager.design_token'
  plugin.manager.design_token_value:
    class: Drupal\Core\Theme\DesignToken\DesignTokenValuePluginManager
    parent: default_plugin_manager
  Drupal\Core\Theme\DesignToken\DesignTokenValuePluginManagerInterface: '@plugin.manager.design_token_value'
  Drupal\Core\Extension\ThemeSettingsProvider:
    autowire: true
  Drupal\Core\Extension\ModuleWeight:
+32 −0
Changes for core/lib/Drupal/Core/Theme/DesignToken/Attribute/DesignTokenValue.php: 32 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Theme\DesignToken\Attribute;

use Drupal\Component\Plugin\Attribute\Plugin;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
 * Defines design token value attribute.
 */
#[\Attribute(\Attribute::TARGET_CLASS)]
class DesignTokenValue extends Plugin {

  /**
   * Constructor.
   *
   * @param string $id
   *   The plugin ID.
   * @param \Drupal\Core\StringTranslation\TranslatableMarkup|null $label
   *   (optional) The human-readable name of the plugin.
   * @param class-string|null $deriver
   *   (optional) The deriver class.
   */
  public function __construct(
    public readonly string $id,
    public readonly ?TranslatableMarkup $label = NULL,
    public readonly ?string $deriver = NULL,
  ) {}

}
+81 −0
Changes for core/lib/Drupal/Core/Theme/DesignToken/DesignToken.php: 81 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace Drupal\Core\Theme\DesignToken;

use Drupal\Component\Plugin\Definition\PluginDefinition;
use Drupal\Core\StringTranslation\TranslatableMarkup;

/**
 * A token: distinguishable by containing a $value property.
 */
class DesignToken extends PluginDefinition implements DtcgInterface {

  /**
   * DTCG: $type.
   *
   * Actual data type which ensures consistency, validation, and tool
   * compatibility across platforms.
   */
  public readonly ?string $type;

  /**
   * Construct a DTCG token.
   *
   * @param string $provider
   *   The Drupal extension providing the token.
   * @param string $name
   *   The token's machine name.
   * @param \Drupal\Core\Theme\DesignToken\DesignTokenValueInterface $value
   *   The token value.
   * @param ?\Drupal\Core\Theme\DesignToken\Group $parent
   *   The parent group.
   * @param ?\Drupal\Core\StringTranslation\TranslatableMarkup $description
   *   A plain text description explaining the token's purpose.
   * @param ?string $type
   *   The token type if present.
   */
  public function __construct(
    string $provider,
    public readonly string $name,
    public readonly DesignTokenValueInterface $value,
    public readonly ?Group $parent = NULL,
    public readonly ?TranslatableMarkup $description = NULL,
    ?string $type = NULL,
  ) {
    $this->type = $type ?? $parent->type ?? NULL;
    $this->provider = $provider;
    // Spaces are allowed path but not for plugin IDs.
    $this->id = \strtr($this->getPath(), [' ' => '_']);
  }

  /**
   * {@inheritdoc}
   */
  public function getType(): ?string {
    return $this->type;
  }

  /**
   * {@inheritdoc}
   */
  public function getPath(): string {
    return $this->parent ? ($this->parent->getPath() . '.' . $this->name) : $this->name;
  }

  /**
   * {@inheritdoc}
   */
  public function toDtcg(): array {
    $data = [
      '$type' => $this->type,
      '$value' => $this->value->toDtcg(),
    ];
    if ($this->description) {
      $data['$description'] = $this->description->render();
    }
    return $data;
  }

}
Loading