Example of creating avatar component in radix subtheme

Created on 8 September 2024, 3 months ago
Updated 19 September 2024, 3 months ago

Problem/Motivation

Maybe it's not the best place to post this, but I want to mention a use case for howto create a component not included in radix. So is more a documentation issue...

In your subtheme generate the component with drupal-radix-cli. In this case avatar component, it will create the avatar directory in components/, here are the contents:
avatar.component.yml , is where the properties and slots are defined, later in avatar.twig template, these properties will be used as twig variables, and slots as twig blocks.

$schema: https://git.drupalcode.org/project/drupal/-/raw/10.1.x/core/modules/sdc/src/metadata.schema.json
name: avatar
status: experimental
description: 'The avatar component auto-generated by drupal-radix-cli'
slots:
  image:
    title: Image
props:
  type: object
  properties:
    size:
      title: Size
      description: "It is possible to set the size of avatars."
      enum:
        - avatar-xs
        - avatar-sm
        - avatar-lg
        - avatar-xl
        - avatar-xxl
    square:
      title: Square
      description: "Disables rounding of the avatar, rending the avatar with square corners."
      type: "boolean"
    image_element:
      title: "Image element"
      description: "The html image element"
      type: "string"
    profile:
      title: "profile"
      description: "The profile object to feed avatar"
      type: object
    text_color:
      title: "Text color"
      description: "The color that will be used for text"
      type: string
    variant:
      title: "variant"
      description: "Use the variant prop to specify one of Bootstrap theme variant colors. The default variant is secondary."
      enum:
        - secondary
        - primary
        - dark
        - light
        - success
        - danger
        - warning
        - info
    text:
      title: text
      description: "Text that will be in avatar if not using image."
      type: "string"
    avatar_url:
      title: "Avatar url"
      description: "The url to point"
      type: string
stories:
  preview:
    title: Preview
    props:
      size: w-24
      square: false
      image_element: "<img src='https://placekitten.com/300/300' />"

avatar.twig , take care with scope, as properties are inherited, so using for example 'attributes' here will be risky if the parent template uses 'attributes' variable name too.

{#
/**
* @file
* Template for avatar component.
*/
#}
{% set avatar_attributes = create_attribute() %}
{% set avatar_attributes = avatar_attributes.addClass(['avatar', 'not-prose']) %}
{% set link_attributes = create_attribute() %}

{% if size %}
    {% set avatar_attributes = avatar_attributes.addClass(size) %}
{% endif %}

{% if square %}
    {% set avatar_attributes = avatar_attributes.addClass(square) %}
{% endif %}

{% if image_element %}
  {% set image_element_local = image_element %}
{% endif %}

{% if profile %}
  {% if profile.user_picture %}
    {% set image_element_local = profile.user_picture %}
  {% endif %}
  {% set entity = profile.entity %}
  {% set text_local = profile.entity.label|first|upper %}
  {% set link_attributes = link_attributes.setAttribute('href', path('entity.user.canonical', {'user':  profile.entity.id })).setAttribute('title', profile.entity.label) %}
{% endif %}

{% if variant %}
  {% set variant_local = variant %}
{% else %}
  {% set variant_local = "secondary" %}
{% endif %}
{% set avatar_attributes = avatar_attributes.addClass('bg-' ~ variant_local ) %}

{% if text_color %}
  {% set avatar_attributes = avatar_attributes.addClass( text_color ) %}
  {% set link_attributes = link_attributes.addClass( text_color ) %}
{% endif %}

{% if text %}
  {% set text_local = text %}
{% endif %}

{% if avatar_url %}
  {%  set link_attributes = link_attributes.setAttribute('href', avatar_url)  %}
{% endif %}


<a {{ link_attributes }}>
  <div {{ avatar_attributes }}>
      {% if image_element_local %}
      <div class="avatar-img">
    {% block image %}
        {{ image_element_local }}
    {% endblock %}
    </div>
      {% else %}
        {% if text_local %}
          {{ text_local }}
        {% else %}
          <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-person-fill" viewBox="0 0 16 16">
            <path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6"/>
          </svg>
        {% endif %}
      {% endif %}
  </div>
</a>

Previous to talk about avatar.scss , we want to declare the enumeration of sizes, this is done in subtheme src/scss/_variables.scss :

$avatar-width: 32px !default;
$avatar-values: (            
  xs: 16px,                  
  sm: 24px,                  
  lg: 40px,                  
  xl: 96px,                  
  xxl: 128px,                
) !default;                  

finally the avatar.scss , the last rule will declare the sizes rules to generate the avatar sizes.

@import "../../src/scss/init";

// Avatar custom styles

.avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  flex-shrink: 0;
  width: var(--#{$prefix}avatar-width);
  height: var(--#{$prefix}avatar-width);
  font-size: inherit;
  font-weight: 400;
  line-height: 1;
  max-width: 100%;
  max-height: auto;
  text-align: center;
  overflow: hidden;
  position: relative;
  border-radius: 50%;
  margin-bottom: 0 !important;

  &.avatar-sm {
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 2.5rem;
  }

  &.avatar-lg {
    width: 3.5rem;
    height: 3.5rem;
    border-radius: 3.5rem;
  }


  &.square {
    border-radius: 0;
  }

  &:focus {
    outline: 0;
  }

  &.btn,
  &[href] {
    padding: 0;
    border: 0;

    .avatar-img img {
      transition: transform 0.15s ease-in-out;
    }

    &:not(:disabled):not(.disabled) {
      cursor: pointer;

      &:hover {
        .avatar-img img {
          transform: scale(1.15);
        }
      }
    }
  }

  &.disabled,
  &:disabled,
  &[disabled] {
    opacity: $btn-disabled-opacity;
    pointer-events: none;
  }

  .avatar-custom,
  .avatar-text,
  .avatar-img {
    border-radius: inherit;
    width: 100%;
    height: 100%;
    overflow: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    // https://gist.github.com/ayamflow/b602ab436ac9f05660d9c15190f4fd7b
    mask-image: radial-gradient(white, black);
  }

  .avatar-text {
    text-transform: uppercase;
    white-space: nowrap;
  }

  &[href] {
    text-decoration: none;
  }

  > .icon {
    width: 60%;
    height: auto;
    max-width: 100%;
  }

  .avatar-img .field--name-user-picture {
    margin: 0;
    img {
    width: 100%;
    height: 100%;
    max-height: auto;
    border-radius: inherit;
    // This is not supported in IE11 and Edge <16
    // https://caniuse.com/object-fit
    object-fit: cover;
  }
  }
}

.avatar-group {
  display: flex;
  .avatar {
    margin-right: calc(-.4 * 2.5rem);
  }
  .avatar:hover {
    margin-right: 0px;
  }

  .avatar:last-child {
    margin-right: 0;
  }
}

@each $name, $width in $avatar-values {
  .avatar-#{$name} {
    --#{$prefix}avatar-width: #{$width};
  }
}

In this component js is not used.

With this component it could be used with in any template:

{% embed 'radix_yoursubtheme:avatar' with { 'size': 'avatar-sm', 'profile': avatar,  'image_element': avatar.user_picture, 'variant': 'warning' , 'text_color': 'text-white' } %}
{% endembed %}

In this example an array of

  $vars['avatar'] = [ 'user_picture': 'rendered user entity using compact view mode', 'entity' : UserEntity ]

could be used to feed the component.

πŸ“Œ Task
Status

Fixed

Version

6.0

Component

Documentation

Created by

πŸ‡ͺπŸ‡ΈSpain aleix

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Production build 0.71.5 2024