Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom jsdoc storybook blocks #43

Open
pascalvos opened this issue Feb 9, 2024 · 3 comments
Open

custom jsdoc storybook blocks #43

pascalvos opened this issue Feb 9, 2024 · 3 comments

Comments

@pascalvos
Copy link

pascalvos commented Feb 9, 2024

like discussed on discord :)

the idea is to be able to use custom jsdoc annotations to document things in storybook
based on this work https://github.com/shoelace-style/shoelace/blob/dafb35c6e210193a9ca31efddc3429ba2bb66be3/custom-elements-manifest.config.js

for example, we can use @since annotation to get a version this could be in turn used to be displayed in storybook.
i did some hacking around with storyblocks and i got something based on this plugin

annotation example based on shoelace example

/**
 * A button is used to trigger an action. For more information about a button, click [here](https://unify.nedap.healthcare/42a5b6c3c/p/17c689-button/b/05c6fc)
 *
 * @status stable
 * @since 2.0.0
 *
 * @dependency uc-icon
 * @dependency uc-badge
 *
 **/

the cem plugin

import {parse} from 'comment-parser';

function noDash(string) {
  return string.replace(/^\s?-/, '').trim();
}

export function customTags() {
  return {
    name: 'custom-tags',
    analyzePhase({ts, node, moduleDoc}) {
      switch (node.kind) {
        case ts.SyntaxKind.ClassDeclaration: {
          const className = node.name.getText();
          const classDoc = moduleDoc?.declarations?.find(declaration => declaration.name === className);
          const customTags = ['aria-rules', 'deprecated-attribute','dependency', 'status', 'since'];
          let customComments = '/**';

          node.jsDoc?.forEach(jsDoc => {
            jsDoc?.tags?.forEach(tag => {
              const tagName = tag.tagName.getText();

              if (customTags.includes(tagName)) {
                customComments += `\n * @${tagName} ${tag.comment}`;
              }
            });
          });

          // This is what allows us to map JSDOC comments to ReactWrappers.
          classDoc['jsDoc'] = node.jsDoc?.map(jsDoc => jsDoc.getFullText()).join('\n');

          const parsed = parse(`${customComments}\n */`);
          parsed[0].tags?.forEach(t => {
            switch (t.tag) {
              // custom rule for aria
              case 'aria-rules':
                if (!Array.isArray(classDoc['aria-rules'])) {
                  classDoc['aria-rules'] = [];
                }
                classDoc['aria-rules'].push({
                  name: t.name,
                  description: noDash(t.description),
                });
                break;

              // custom deprecated attribute rule
              case 'deprecated-attribute':
                if (!Array.isArray(classDoc['deprecated-attribute'])) {
                  classDoc['deprecated-attribute'] = [];
                }
                classDoc['deprecated-attribute'].push({
                  name: t.name,
                  description: noDash(t.description),
                });
                break;

              // custom deprecated element rule
              case 'deprecated':
                if (!Array.isArray(classDoc['deprecated'])) {
                  classDoc['deprecated'] = [];
                }
                classDoc['deprecated'].push({
                  description: noDash(t.description),
                });
                break;

              // Dependencies
              case 'dependency':
                if (!Array.isArray(classDoc['dependencies'])) {
                  classDoc['dependencies'] = [];
                }
                classDoc['dependencies'].push(t.name);
                break;

              // Value-only metadata tags
              case 'since':
              case 'status':
                classDoc[t.tag] = t.name;
                break;

              // All other tags
              default:
                if (!Array.isArray(classDoc[t.tag])) {
                  classDoc[t.tag] = [];
                }

                classDoc[t.tag].push({
                  name: t.name,
                  description: t.description,
                  type: t.type || undefined
                });
            }
          });
        }
      }
    }
  }
}

preview.ts

import { setCustomElementsManifest } from "@storybook/web-components";
import { setWcStorybookHelpersConfig } from "wc-storybook-helpers";
import customElements from "custom-elements.json";
import DocumentationTemplate from "./DocumentationTemplate.mdx";

setWcStorybookHelpersConfig({ typeRef: "expandedType", hideArgRef: true });
setCustomElementsManifest(customElements);

const preview: Preview = {
    // ... more settings and things
    docs: {
      page: DocumentationTemplate,
    },
}
export default preview;

DocumentationTemplate.mdx

import { Meta, Title, Subtitle, Primary, Controls, Stories, Description } from '@storybook/blocks';
import MyCustomBlock from './MyCustomBlock';

{/*
  * 👇 The isTemplate property is required to tell Storybook that this is a template
  * See https://storybook.js.org/docs/api/doc-block-meta
  * to learn how to use
*/}

<Meta isTemplate />

<Title />

<Subtitle/>

<Description />

<MyCustomBlock />

# Default implementation

<Primary />

## Inputs

The component accepts the following inputs (props):

<Controls />

---

## Additional variations

Listed below are additional variations of the component.

<Stories />

MyCustomBlock.jsx

import React, { useContext } from "react";
import { DocsContext } from "@storybook/addon-docs/blocks";

import customElements from "@nedap/unify-components/custom-elements.json";
import { getComponentByTagName } from "wc-storybook-helpers/dist/cem-utilities.js";

const MyCustomBlock = ({ children, ...props }) => {
  const context = useContext(DocsContext);
  const tag = context.storyById().component;
  const component = getComponentByTagName(tag, customElements);
  const { since, dependencies, status } = component;

  return (
    <div {...props}>
      {since && <p>Since: {since}</p>}
      {dependencies && (
        <p>
          Dependencies:
          <ul>
            {dependencies.map((dep) => (
              <li>{dep}</li>
            ))}
          </ul>
        </p>
      )}
      {status && <p>Status: {status}</p>}
      {/* Render your custom content or children */}
      {children}
    </div>
  );
};

export default MyCustomBlock;
@pascalvos
Copy link
Author

Screenshot 2024-02-09 at 15 17 22
this is how it pops up on the screen

@break-stuff
Copy link
Owner

The first piece of this has been created:

https://www.npmjs.com/package/cem-plugin-custom-jsdoc-tags

@pascalvos
Copy link
Author

sweet 👍 I'll see if I can get it integrated over the next few weeks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants