Extend the "Quick start" guide using React's one as inspiration

Created on 4 June 2023, over 1 year ago
Updated 15 June 2023, over 1 year ago

Problem/Motivation

In 📌 Create SDC "Quick start" documentation / code Fixed we created the first version of the "Quick Start" guide in Drupal.org.

We want to keep iterating it to make it more comprehensive. A good model of a good Quick Start guide in an Open Source project is the one for React.

Proposed Resolution

Let's see if we can use React's guide as an inspiration to cover the nuances of writing, using, and maintaining Drupal's Single Directory Components.

As tempting as an SPA for documentation is, I think we should keep the documentation in Drupal.org. This is so anyone can help maintaining it without having to learn an additional system for these docs.

📌 Task
Status

Fixed

Version

10.1

Component
Documentation 

Last updated 2 days ago

No maintainer
Created by

e0ipso Can Picafort

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

Comments & Activities

  • Issue created by @e0ipso
  • Assigned to eojthebrave
  • 🇺🇸United States eojthebrave Minneapolis, MN

    I went through this, and have a few thoughts about how to improve it. The biggest one would be to simplify the example code. There's a lot of code in for example the CSS file that's not really required to help someone learn how to create a component. The essential part is getting the .css file created and loading, but what's in it doesn't matter. So keeping it shorter would be helpful.

    I'm willing to take a pass at updating this page. I'll assign it to me while I work on it just to make sure there's no overlapping work going on. :)

  • Issue was unassigned.
  • Status changed to Needs review over 1 year ago
  • 🇺🇸United States eojthebrave Minneapolis, MN

    Okay, here's my proposed update to the page content:

    A component is a piece of the UI (user interface) that has its own logic and appearance. A component can be as small as a button, or as large as an entire node. Drupal's Single Directory Components consist of metadata that describes the component, HTML markup, and optionally CSS and JavaScript. All located in the same directory. Hence the name, Single Directory Components.
    
    We're going to walk through creating a 'chip' component, and using it in a theme, as an example to demonstrate how this works.
    
    ## Prerequistes to working with components
    
    - You must be using Drupal 10.1 or greater
    - You must have the Single Directory Components module enabled (at admin/modules)
    - You must have a theme (or module) that you want to add components too. And it must be enabled (at admin/apperance)
    - Know where in the filesystem the theme's code is located. (We'll be using the Drupal core Olivero theme, located at themes/olivero/ in our example.)
    
    ## Define a component
    
    Every component requires a single directory for it's assets to reside in, placed into the _components/_ subdirectory of your theme so that Drupal can find it. And a {NAME}.component.yml file with metadata about the component.
    
    Pick a name for you component, we'll use "chip" for ours, it should be unique within the theme. Components are namespaced so multiple different theme's could declare a chip component. Once you have a name, create a directory, and a metadata file.
    
    Create the directory _core/themes/olivero/**components/chip/**_. Then in that directory create a file starting with your component name, and follow by _.components.yml_. Example: _chip.components.yml_.
    
    Populate the _chip.components.yml_ file with metadata that describes the component:
    
    ```yaml
    name: Chip
    props:
      type: object
      required:
        - color
      properties:
        # Can the chip be dismissed by clicking on it?
        dismissable:
          type: boolean
        # One of 'primary', or 'secondary'.
        color:
          type: string
    slots:
      # Content to display in the chip.
      chip_content: {}
    ```
    
    The data in this file describes your component to Drupal, and to anyone who wants to make use of it. This one includes a human readable `name` for the component. Definitions of the components inputs in the form of props (for data whose type, and structure, is well defined) and slots (for data with an unknown structure, like nested components). These act as an API, or contract for the component; "We promise to always accept, and use, these inputs."
    
    [Learn about additional metadata values](https://www.drupal.org/docs/develop/theming-drupal/using-single-directory-components/annotated-example-componentyml#s-full-example).
    
    ## Add some markup
    
    A component that doesn't output anything isn't that useful. So lets add some markup via a Twig file. Within the component directory, create a _.twig_ file using the name of your component.
    
    Example: _components/chip/chip.twig_, and populate it with the following:
    
    ```html
    {%
      set classes = [
      'chip',
      'chip--color-' ~ color|clean_class,
      dismissable ? 'chip--dismissable',
    ]
    %}
    <div class="{{ classes|join(" ") }}">
      {% block chip_content %}{% endblock %}
    </div>
    ```
    
    This contains some HTML markup, and Twig expressions that make use of component inputs to output dynamic content. Note that the Twig variable names (and their values) map to the props and slots defined in your _.component.yml_ metadata file.
    
    ## Use your component in a template file
    
    In order to use the markup generated by your component you'll need to embed it into a Twig template file in your theme. We're going to add ours to the node template file, because we're going to display a the node type as a chip.
    
    Edit the the _core/themes/olivero/templates/content/node.html.twig_ template file and add the following where you want the chip to display:
    
    ```html
    {% embed 'olivero:chip' with {
      color: 'primary',
      dismissable: true
    } only %}
    
      {% block chip_content %}
        Type: <span>{{ node.bundle() }}</span>
      {% endblock %}
    
    {% endembed %}
    ```
    
    This Twig code embeds the chip component into the Twig template file and defines the values to pass to the components inputs. This uses standard Twig embed syntax. Some things to note are the name spacing of the component `oliver:chip` which resolves to the 'chip' component in the 'olivero' theme. And the definition of the input props (color and dismissable), and slots (chip_content).
    
    If you clear the cache, and view a node on your site you should see the node type output as a 'chip' on the page wherever you embedded the component in the template file.
    
    ## Add some style
    
    Let's add some CSS and make that chip look a little more attractive. This requires creating a _.css_ file with the name of your component in the component directory, and well ... that's it!
    
    Create the file _core/themes/olivero/components/chip/chip.css_ with the following content:
    
    ```css
    .chip {
      display: inline-block;
      padding: 0.25rem;
    }
    
    .chip--color-primary {
      background-color: var(--color--primary-50);
      color: white;
    }
    
    .chip--color-secondary {
      background-color: var(--color--gray-100);
      color: var(--color-text-primary-medium);
    }
    
    .chip--dismissed {
      display: none;
    }
    ```
    
    If the component _chip.css_ file exists Drupal will automatically find it, and include it in the page, whenever the chip component it used.
    
    ## Finally, add some interaction with JavaScript
    
    Adding some JavaScript to our component isn't any harder. Let's add some JavaScript that will make it so the chip can be dismissed (hidden) when a user clicks on it. This requires creating a _.js_ file with the component name in the component directory.
    
    Create the file _core/themes/olivero/components/chip/chip.js_ with the following JavaScript content:
    
    ```js
    ((Drupal) => {
      Drupal.behaviors.chip = {
        attach(context) {
          context.querySelectorAll('.chip--dismissable').forEach((chip) => {
            chip.addEventListener('click', () => {
              chip.classList.toggle('chip--dismissed');
            })
          });
        },
      };
    })(Drupal);
    ```
    
    Refresh the page and Drupal should automatically locate this new _.js_ file and include it whenever the chip component is used now too. You can test that it's working by setting the `dismissable` prop of your component to `true` and then clicking on the chip element. It should disappear. Now set it to `false` and clicking should have no impact.
    
    Note how this ties together code in CSS where we have classes to show/hide the chip, and the Twig file where we dynamically decide whether or not to add the `.chip--dismissable` class depending on the `dismissable` prop's value.
    
    ## Take if further
    
    This a quick example, but components can do a lot more, checkout the [complete annotated example](https://www.drupal.org/docs/develop/theming-drupal/using-single-directory-components/annotated-example-componentyml#s-full-example) to learn more. Here are a few things to try:
    
    - You can include other assets in the directory too, like images, but they won't be automatically loaded. You'll need to reference them via your Twig, CSS, or JavaScript files. Can you add an icon to your chip component?
    - Use the `libraryOverrides` in your _.component.yml_ file to require additional JavaScript libraries like drupal/once or jQuery. Can you rewrite the JavaScrip so that it uses the drupal/once library?
    

    And include the attached image near the top.

  • 🇺🇸United States Amber Himes Matz Portland, OR USA

    @eojthebrave asked if I would take a look at the copy and check for spelling/grammar. This is kind of awkward to do because we’re not dealing with a file or patch. So here’s the whole blob with my edits included.

    In the prereqs, does a module need to be installed if it contains a SDC? It just says the theme must be enabled (which I changed to the word, “installed”. We should include both cases, I think, if that’s the case.

    A component is a piece of the user interface (UI) that has its own logic and appearance. A component can be as small as a button, or as large as an entire node. Drupal's Single Directory Components consist of metadata that describe the component, HTML markup, and, optionally, CSS and JavaScript, which are all located in the same directory. Hence, the name, Single Directory Components.
    
    To demonstrate how Single Directory Components work, we're going to walk through creating an example 'chip' component and use it in a theme.
    
    ## Prerequisites to working with components
    
    - You must be using Drupal 10.1 or greater
    - You must have the Single Directory Components module enabled (at _admin/modules_)
    - You must have a theme (or module) that you want to add components to. The theme must be installed (at _admin/appearance_)
    - You must know where in the filesystem the theme's code is located. (We'll be using the Drupal core Olivero theme, located at _core/themes/olivero/_ in this example.)
    
    ## Define a component
    
    Every component requires a single directory for its assets to reside in. This directory must exist in a _components/_ subdirectory of your theme (so that Drupal can find it). A _{NAME}.component.yml_ file with metadata about the component is required.
    
    Pick a name for your component. We'll use "chip" for our example. It should be unique within the theme. Components are namespaced, so multiple different themes could declare a "chip" component.
    
    Create the directory _core/themes/olivero/**components/chip/**_. Then, in that directory, create a file starting with your component name,  followed by _.components.yml_. For example, _chip.components.yml_.
    
    Populate the _chip.components.yml_ file with metadata that describes the component:
    
    ```yaml
    name: Chip
    props:
      type: object
      required:
        - color
      properties:
        # Can the chip be dismissed by clicking on it?
        dismissable:
          type: boolean
        # One of 'primary', or 'secondary'.
        color:
          type: string
    slots:
      # Content to display in the chip.
      chip_content: {}
    ```
    
    The data in this file describes your component to Drupal and to anyone who wants to make use of it. This one includes a human-readable `name` for the component. Definitions of the component’s inputs in the form of **props** (for data whose type and structure is well defined) and **slots** (for data with an unknown structure, like nested components). These act as an API or contract for the component, and is like saying, "We promise to always accept and use these inputs."
    
    [Learn about additional metadata values](https://www.drupal.org/docs/develop/theming-drupal/using-single-directory-components/annotated-example-componentyml#s-full-example).
    
    ## Add some markup
    
    A component that doesn't output anything isn't that useful. So let’s add some markup via a Twig file. Within the component’s directory, create a _.twig_ file using the name of your component.
    
    For example, in _components/chip/chip.twig_, add the following:
    
    ```html
    {%
      set classes = [
      'chip',
      'chip--color-' ~ color|clean_class,
      dismissable ? 'chip--dismissable',
    ]
    %}
    <div class="{{ classes|join(" ") }}">
      {% block chip_content %}{% endblock %}
    </div>
    ```
    
    The above code contains some HTML markup and Twig expressions that make use of component inputs in order to output dynamic content. Note: the Twig variable names (and their values) map to the props and slots defined in your _{NAME}.component.yml_ metadata file.
    
    ## Use your component in a template file
    
    In order to use the markup generated by your component, you'll need to embed it into a Twig template file in your theme. We're going to add ours to the node template file, because we're going to display the node type (bundle) as a chip.
    
    Edit the _core/themes/olivero/templates/content/node.html.twig_ template file and add the following (where you want the chip to display):
    
    ```html
    {% embed 'olivero:chip' with {
      color: 'primary',
      dismissable: true
    } only %}
    
      {% block chip_content %}
        Type: <span>{{ node.bundle() }}</span>
      {% endblock %}
    
    {% endembed %}
    ```
    
    This Twig code embeds the chip component into the Twig template file and defines the values to pass to the component’s inputs. This uses standard Twig embed syntax. Some things to note: the namespacing of the component `olivero:chip` resolves to the 'chip' component in the _olivero_ theme. And, the definition of the input props (`color` and `dismissable`) and slots (`chip_content`) correspond to the props and slots you defined in your component’s metadata file (_{NAME}.component.yml_).
    
    Clear the cache and view a node on your site. You should see the node type output as a `chip` on the page in the place you embedded the component in the node template file.
    
    ## Add some style
    
    Let's add some CSS and make that chip look a little more attractive. This requires creating a CSS file with the name of your component in the component directory, and well, ...that's it!
    
    Create the file, _core/themes/olivero/components/chip/chip.css_, with the following contents:
    
    ```css
    .chip {
      display: inline-block;
      padding: 0.25rem;
    }
    
    .chip--color-primary {
      background-color: var(--color--primary-50);
      color: white;
    }
    
    .chip--color-secondary {
      background-color: var(--color--gray-100);
      color: var(--color-text-primary-medium);
    }
    
    .chip--dismissed {
      display: none;
    }
    ```
    
    If the component’s _{NAME}.css_ file exists, Drupal will automatically find it, and include it on the page. In the case of our chip component, since the _chip.css_ file exists, Drupal will use it whenever the chip component is used in a template file.
    
    ## Finally, add some interaction with JavaScript
    
    Adding some JavaScript to our component isn't any harder. Let's add some JavaScript that will make it so the chip can be dismissed (hidden) when a user clicks on it. This requires creating a _{NAME}.js_ file with the component name in the component’s directory.
    
    Create the file, _core/themes/olivero/components/chip/chip.js_, with the following JavaScript code:
    
    ```js
    ((Drupal) => {
      Drupal.behaviors.chip = {
        attach(context) {
          context.querySelectorAll('.chip--dismissable').forEach((chip) => {
            chip.addEventListener('click', () => {
              chip.classList.toggle('chip--dismissed');
            })
          });
        },
      };
    })(Drupal);
    ```
    
    Refresh the page and Drupal should automatically locate this new JavaScript file and include it whenever the chip component is used. You can test that it's working by setting the `dismissable` prop of your component to `true`, and then clicking on the chip element. (It should disappear.) Now set it to `false`, and clicking should have no impact.
    
    Note: this ties together code in CSS where we have classes to show/hide the chip and the Twig file where we dynamically decide whether or not to add the `.chip--dismissable` class depending on the `dismissable` prop's value.
    
    ## Take it further
    
    Components can do a lot more. Now that you understand this brief example, check out the [complete annotated example](https://www.drupal.org/docs/develop/theming-drupal/using-single-directory-components/annotated-example-componentyml#s-full-example) to learn more. Here are a few things to experiment with:
    
    - You can include other assets in the directory, too, like images, but they won't be automatically loaded. You'll need to reference them via your Twig, CSS, or JavaScript files. Can you add an icon to your chip component?
    - Use the `libraryOverrides` in your _{NAME}.component.yml_ file to require additional JavaScript libraries like _drupal/once_ or jQuery. Can you rewrite the JavaScript so that it uses the _drupal/once_ library?
    
  • 🇺🇸United States smustgrave

    Since this has to do with drupal.org vs core repo should it be moved to that project?

  • Status changed to RTBC over 1 year ago
  • 🇺🇸United States eojthebrave Minneapolis, MN

    I just edited the page at https://www.drupal.org/docs/develop/theming-drupal/using-single-director... to contain the updated content from this issue. I think we can probably close this issue. But I'll leave that to one of the SDC maintainers, or really someone other than me since I wrote the updated text and just want to make sure that these updates are accomplishing what they are hoping for.

    I don't really feel strongly one way or another about moving the issue. But if we do I think https://www.drupal.org/project/documentation is probably the appropriate place.

  • Status changed to Fixed over 1 year ago
  • e0ipso Can Picafort

    Added credit and marked as fixed. I think the Component: documentation is good enough for the archives.

    I really like how this small example component seems to touch on all the critical places 👏

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024