Skip to content

Add support for custom sidebar DocItem generators supporting customisation #963

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

Merged

Conversation

robbieaverill
Copy link
Contributor

@robbieaverill robbieaverill commented Sep 11, 2024

Description

Similarly to markdownGenerators, the optional createDocItem() method in sidebarOptions allows users to configure a custom generator function for sidebar items. This allows customisation of class names or custom props, for example, based on each item.

Motivation and Context

How Has This Been Tested?

I used the demo "petstore versioned" API to define a custom doc item creator function that both adds a custom class name and adds a customProp, and swizzled the component that renders sidebar items to ensure it would pick up the customProp. Details below.

Detailed explanation of testing

In the demo config file I added a custom create method:

diff --git a/demo/docusaurus.config.ts b/demo/docusaurus.config.ts
index 51faf43..b4c30d3 100644
--- a/demo/docusaurus.config.ts
+++ b/demo/docusaurus.config.ts
@@ -5,6 +5,7 @@ import type * as OpenApiPlugin from "docusaurus-plugin-openapi-docs";
 
 import { DOCUSAURUS_VERSION } from "@docusaurus/utils";
 import { myCustomApiMdGenerator } from "./customMdGenerators";
+import clsx from "clsx";
 
 const config: Config = {
   title: "Docusaurus OpenAPI Docs",
@@ -268,6 +269,37 @@ const config: Config = {
             sidebarOptions: {
               groupPathsBy: "tag",
               categoryLinkSource: "tag",
+              createDocItem(item, { sidebarOptions, basePath }) {
+                const sidebar_label = item.frontMatter.sidebar_label;
+                const title = item.title;
+                const id =
+                  item.type === "schema" ? `schemas/${item.id}` : item.id;
+                const className =
+                  item.type === "api" ? clsx({
+                      "menu__list-item--deprecated": item.api.deprecated,
+                      "api-method": !!item.api.method,
+                    },
+                    item.api.method,
+                    "my-example-class-name"
+                  )
+                  : clsx(
+                    { "menu__list-item--deprecated": item.schema.deprecated },
+                    "schema",
+                    "my-example-class-name"
+                  );
+
+                return {
+                  type: "doc" as const,
+                  id:
+                    basePath === "" || undefined ? `${id}` : `${basePath}/${id}`,
+                  label: (sidebar_label as string) ?? title ?? id,
+                  customProps: {
+                    ...sidebarOptions.customProps,
+                    "my-custom-prop": true,
+                  },
+                  className: className ? className : undefined,
+                };
+              },
             },
             version: "2.0.0", // Current version
             label: "v2.0.0", // Current version label

And I swizzled the DocSidebarItem/Link/index.tsx template (and associated CSS module file) from the class theme and added some lines to render a span for nav items that are marked with my-custom-prop in customProps:

diff --git a/demo/src/theme/DocSidebarItem/Link/index.tsx b/demo/src/theme/DocSidebarItem/Link/index.tsx
new file mode 100644
index 0000000..043eea0
--- /dev/null
+++ b/demo/src/theme/DocSidebarItem/Link/index.tsx
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import React from "react";
+import clsx from "clsx";
+import { ThemeClassNames } from "@docusaurus/theme-common";
+import { isActiveSidebarItem } from "@docusaurus/plugin-content-docs/client";
+import Link from "@docusaurus/Link";
+import isInternalUrl from "@docusaurus/isInternalUrl";
+import IconExternalLink from "@theme/Icon/ExternalLink";
+import type { Props } from "@theme/DocSidebarItem/Link";
+
+import styles from "./styles.module.css";
+
+export default function DocSidebarItemLink({
+  item,
+  onItemClick,
+  activePath,
+  level,
+  index,
+  ...props
+}: Props): JSX.Element {
+  const { href, label, className, autoAddBaseUrl } = item;
+  const isActive = isActiveSidebarItem(item, activePath);
+  const isInternalLink = isInternalUrl(href);
+  const isMyCustomProp = item?.customProps?.["my-custom-prop"];
+
+  return (
+    <li
+      className={clsx(
+        ThemeClassNames.docs.docSidebarItemLink,
+        ThemeClassNames.docs.docSidebarItemLinkLevel(level),
+        "menu__list-item",
+        className
+      )}
+      key={label}
+    >
+      <Link
+        className={clsx(
+          "menu__link",
+          !isInternalLink && styles.menuExternalLink,
+          {
+            "menu__link--active": isActive,
+          }
+        )}
+        autoAddBaseUrl={autoAddBaseUrl}
+        aria-current={isActive ? "page" : undefined}
+        to={href}
+        {...(isInternalLink && {
+          onClick: onItemClick ? () => onItemClick(item) : undefined,
+        })}
+        {...props}
+      >
+        {label}
+        {!isInternalLink && <IconExternalLink />}
+        {isMyCustomProp && <span>CUSTOM PROP</span>}
+      </Link>
+    </li>
+  );
+}
diff --git a/demo/src/theme/DocSidebarItem/Link/styles.module.css b/demo/src/theme/DocSidebarItem/Link/styles.module.css
new file mode 100644
index 0000000..4abcb56
--- /dev/null
+++ b/demo/src/theme/DocSidebarItem/Link/styles.module.css
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+.menuExternalLink {
+  align-items: center;
+}

Screenshots (if appropriate)

image
image

Types of changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes if appropriate.
  • All new and existing tests passed.

…ation

Similarly to markdownGenerators, the optional createDocItem() method in
sidebarOptions allows users to configure a custom generator function for
sidebar items. This allows customisation of class names or custom props,
for example, based on each item.
customProps: customProps,
className: className ? className : undefined,
};
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method was extracted from where it was originally because it relied on basePath and customProps from higher scope. I've hoisted it and provided those via the context argument instead.

@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
* ========================================================================== */

import { SidebarItemDoc } from "@docusaurus/plugin-content-docs/src/sidebars/types";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that this is the right path to import types from

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it works, since our plugin aims to be 100% compatible with Docusaurus sidebars, i.e. we don't extend the SidebarItemDoc type.

Copy link

github-actions bot commented Sep 12, 2024

Visit the preview URL for this PR (updated for commit ee9790c):

https://docusaurus-openapi-36b86--pr963-2b6qrebn.web.app

(expires Sun, 13 Oct 2024 13:59:06 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: bf293780ee827f578864d92193b8c2866acd459f

@sserrata
Copy link
Member

Thanks @robbieaverill!

I tested using an x-experimental vendor extension - here are the results:

My updated petstore plugin config:

petstore: {
  specPath: "examples/petstore.yaml",
  proxy: "https://cors.pan.dev",
  outputDir: "docs/petstore",
  sidebarOptions: {
    groupPathsBy: "tag",
    categoryLinkSource: "tag",
    createDocItem(
      item,
      { sidebarOptions: { customProps }, basePath }
    ) {
      const sidebar_label = item.frontMatter.sidebar_label;
      const title = item.title;
      const id =
        item.type === "schema" ? `schemas/${item.id}` : item.id;
      const className =
        item.type === "api"
          ? clsx(
              {
                "menu__list-item--deprecated": item.api.deprecated,
                experimental: item.api["x-experimental"], // checks for existence of extension and adds "experimental" class
                "api-method": !!item.api.method,
              },
              item.api.method
            )
          : clsx(
              {
                "menu__list-item--deprecated": item.schema.deprecated,
              },
              "schema"
            );
      return {
        type: "doc" as const,
        id:
          basePath === "" || undefined
            ? `${id}`
            : `${basePath}/${id}`,
        label: (sidebar_label as string) ?? title ?? id,
        customProps: customProps,
        className: className ? className : undefined,
      };
    },
  },
  template: "api.mustache", // Customize API MDX with mustache template
  downloadUrl: "/petstore.yaml",
  hideSendButton: false,
  showSchemas: true,
 } satisfies OpenApiPlugin.Options,

My custom CSS:

.experimental > .menu__link::after {
  content: "🧑‍🔬";
}

Results:

Screenshot 2024-09-12 at 4 46 29 PM

@sserrata
Copy link
Member

What are your thoughts on possibly refactoring the option from createDocItem to sidebarGenerators? The idea would be to make the option more similar to markdownGenerators, in that it would be an object that accepts specific keys, beginning with createDocItem:

petstore: {
  specPath: "examples/petstore.yaml",
  proxy: "https://cors.pan.dev",
  outputDir: "docs/petstore",
  sidebarOptions: {
    groupPathsBy: "tag",
    categoryLinkSource: "tag",
    sidebarGenerators: { createDocItem: myCreateDocItem }, // sidebar generators example, where myCreateDocItem is my function
  },
  template: "api.mustache", // Customize API MDX with mustache template
  downloadUrl: "/petstore.yaml",
  hideSendButton: false,
  showSchemas: true,
} satisfies OpenApiPlugin.Options,

@robbieaverill
Copy link
Contributor Author

I think that would be a sensible place for it to live. Are you happy with the way I've put it together, particularly in terms of having the extra context argument?

@robbieaverill
Copy link
Contributor Author

@sserrata I've moved createDocItem into a sidebarGenerators object and updated the docs

@sserrata
Copy link
Member

Thanks for the changes @robbieaverill! Tested and it appears to work as expected. I also really appreciate the documentation.

@sserrata sserrata self-assigned this Sep 13, 2024
@sserrata sserrata added enhancement New feature or request v4.0.0 v4.0.0 labels Sep 13, 2024
@sserrata sserrata merged commit eee760d into PaloAltoNetworks:main Sep 13, 2024
13 checks passed
@robbieaverill robbieaverill deleted the feature/custom-doc-item-generator branch September 15, 2024 22:16
@tyler-mairose-sp
Copy link
Contributor

@robbieaverill @sserrata Thank you both for adding this feature! It is much better than my original solution and super extensible :)

I now have icons!!!

image

css to get the icons before the method badge:

.menu__list-item--experimental > .menu__link::after {
  font-size: 20px;
  font-family: 'Font Awesome 6 Free';
  font-weight: 900;
  color: var(--dev-icon-experimental);
  text-rendering: auto;
  -webkit-font-smoothing: antialiased;
  content: '\f0c3';
  position: relative;
  right: 103%;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request v4.0.0 v4.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add classes to sidebar items when certain API specification criteria are met.
3 participants