From 891051091718f99c9c96e902a325262c552261b4 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Mon, 13 Jan 2025 11:19:26 +0000
Subject: [PATCH 01/19] Add button directive

Co-authored-by: Angus Hollands <angus@oieltd.com>
---
 packages/myst-directives/src/button.ts | 37 ++++++++++++++++++++++++++
 packages/myst-spec-ext/src/types.ts    |  2 ++
 2 files changed, 39 insertions(+)
 create mode 100644 packages/myst-directives/src/button.ts

diff --git a/packages/myst-directives/src/button.ts b/packages/myst-directives/src/button.ts
new file mode 100644
index 000000000..87efe4713
--- /dev/null
+++ b/packages/myst-directives/src/button.ts
@@ -0,0 +1,37 @@
+import type { DirectiveSpec, DirectiveData, GenericNode } from 'myst-common';
+import type { Link } from 'myst-spec-ext';
+import { addClassOptions, classDirectiveOption } from './utils.js';
+
+export const buttonDirective: DirectiveSpec = {
+  name: 'button',
+  doc: 'Button to navigate to external or internal links.',
+  arg: {
+    type: String,
+    doc: 'Target link of the button.',
+    required: true,
+  },
+  options: {
+    ...classDirectiveOption('button'),
+  },
+  body: {
+        type: 'myst',
+        doc: 'The body of the button.',
+        required: false,
+  },
+  run(data: DirectiveData): GenericNode[] {
+    const children: GenericNode[] = [];
+    if (data.body) {
+      children.push(...data.body as GenericNode[]);
+    }
+    const node: Link = {
+      type: 'link',
+      kind: 'button',
+      url: data.arg as string,
+      children: children as any[],
+  };
+    addClassOptions(data, node)
+    return [
+      node
+    ];
+  },
+};
diff --git a/packages/myst-spec-ext/src/types.ts b/packages/myst-spec-ext/src/types.ts
index b808b82be..8b241d688 100644
--- a/packages/myst-spec-ext/src/types.ts
+++ b/packages/myst-spec-ext/src/types.ts
@@ -284,6 +284,8 @@ export type Link = SpecLink & {
   static?: true;
   protocol?: string;
   error?: true;
+  kind?: 'button';
+  class?: Image['class'];
 };
 
 // Search types

From 9a8dae9a17c6a8bb1b83e0d1d377b724f48ac268 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Mon, 13 Jan 2025 13:40:04 +0000
Subject: [PATCH 02/19] Update index.ts

---
 packages/myst-directives/src/index.ts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/packages/myst-directives/src/index.ts b/packages/myst-directives/src/index.ts
index f3766426d..8d6f15c48 100644
--- a/packages/myst-directives/src/index.ts
+++ b/packages/myst-directives/src/index.ts
@@ -1,4 +1,5 @@
 import { admonitionDirective } from './admonition.js';
+import { buttonDirective } from './button.js';
 import { bibliographyDirective } from './bibliography.js';
 import { codeDirective, codeCellDirective } from './code.js';
 import { dropdownDirective } from './dropdown.js';
@@ -21,6 +22,7 @@ import { divDirective } from './div.js';
 
 export const defaultDirectives = [
   admonitionDirective,
+  buttonDirective,
   bibliographyDirective,
   csvTableDirective,
   codeDirective,
@@ -50,6 +52,7 @@ export const defaultDirectives = [
 
 export * from './utils.js';
 export { admonitionDirective } from './admonition.js';
+export { buttonDirective } from './button.js';
 export { bibliographyDirective } from './bibliography.js';
 export { codeDirective } from './code.js';
 export { dropdownDirective } from './dropdown.js';

From 413f7066e9a20d95233b583d247504c9bd0073b3 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Mon, 13 Jan 2025 14:36:45 +0000
Subject: [PATCH 03/19] Change from directive to role

---
 package-lock.json                             |  16 +++
 packages/myst-cli/package.json                |   1 +
 packages/myst-cli/src/process/myst.ts         |   6 +-
 packages/myst-directives/src/button.ts        |  37 ------
 packages/myst-directives/src/index.ts         |   3 -
 packages/myst-ext-button/.eslintrc.cjs        |   4 +
 packages/myst-ext-button/CHANGELOG.md         | 125 ++++++++++++++++++
 packages/myst-ext-button/README.md            |   3 +
 packages/myst-ext-button/package.json         |  41 ++++++
 packages/myst-ext-button/src/index.ts         |  30 +++++
 packages/myst-ext-button/tests/button.spec.ts |   0
 packages/myst-ext-button/tsconfig.json        |   8 ++
 12 files changed, 233 insertions(+), 41 deletions(-)
 delete mode 100644 packages/myst-directives/src/button.ts
 create mode 100644 packages/myst-ext-button/.eslintrc.cjs
 create mode 100644 packages/myst-ext-button/CHANGELOG.md
 create mode 100644 packages/myst-ext-button/README.md
 create mode 100644 packages/myst-ext-button/package.json
 create mode 100644 packages/myst-ext-button/src/index.ts
 create mode 100644 packages/myst-ext-button/tests/button.spec.ts
 create mode 100644 packages/myst-ext-button/tsconfig.json

diff --git a/package-lock.json b/package-lock.json
index 40bbd6411..e7c3028bf 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10032,6 +10032,10 @@
       "resolved": "packages/myst-execute",
       "link": true
     },
+    "node_modules/myst-ext-button": {
+      "resolved": "packages/myst-ext-button",
+      "link": true
+    },
     "node_modules/myst-ext-card": {
       "resolved": "packages/myst-ext-card",
       "link": true
@@ -15482,6 +15486,7 @@
         "myst-common": "^1.7.6",
         "myst-config": "^1.7.6",
         "myst-execute": "^0.1.2",
+        "myst-ext-button": "^0.0.0",
         "myst-ext-card": "^1.0.9",
         "myst-ext-exercise": "^1.0.9",
         "myst-ext-grid": "^1.0.9",
@@ -15776,6 +15781,17 @@
         "node": "^16.13.0 || >=18.0.0"
       }
     },
+    "packages/myst-ext-button": {
+      "version": "0.0.0",
+      "license": "MIT",
+      "dependencies": {
+        "myst-common": "^1.7.2",
+        "myst-spec-ext": "^1.7.6"
+      },
+      "devDependencies": {
+        "myst-parser": "^1.5.7"
+      }
+    },
     "packages/myst-ext-card": {
       "version": "1.0.9",
       "license": "MIT",
diff --git a/packages/myst-cli/package.json b/packages/myst-cli/package.json
index d9e8133b5..29e78d42d 100644
--- a/packages/myst-cli/package.json
+++ b/packages/myst-cli/package.json
@@ -72,6 +72,7 @@
     "myst-common": "^1.7.6",
     "myst-config": "^1.7.6",
     "myst-execute": "^0.1.2",
+    "myst-ext-button": "^0.0.0",
     "myst-ext-card": "^1.0.9",
     "myst-ext-exercise": "^1.0.9",
     "myst-ext-grid": "^1.0.9",
diff --git a/packages/myst-cli/src/process/myst.ts b/packages/myst-cli/src/process/myst.ts
index 175139819..d9461e08e 100644
--- a/packages/myst-cli/src/process/myst.ts
+++ b/packages/myst-cli/src/process/myst.ts
@@ -1,4 +1,5 @@
 import { mystParse } from 'myst-parser';
+import { buttonRole } from 'myst-ext-button';
 import { cardDirective } from 'myst-ext-card';
 import { gridDirectives } from 'myst-ext-grid';
 import { proofDirective } from 'myst-ext-proof';
@@ -47,7 +48,10 @@ export function parseMyst(
     extensions: {
       frontmatter: !opts?.ignoreFrontmatter,
     },
-    roles: [...(session.plugins?.roles ?? [])],
+    roles: [
+      buttonRole,
+      ...(session.plugins?.roles ?? [])
+    ],
     vfile,
   });
   logMessagesFromVFile(session, vfile);
diff --git a/packages/myst-directives/src/button.ts b/packages/myst-directives/src/button.ts
deleted file mode 100644
index 87efe4713..000000000
--- a/packages/myst-directives/src/button.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { DirectiveSpec, DirectiveData, GenericNode } from 'myst-common';
-import type { Link } from 'myst-spec-ext';
-import { addClassOptions, classDirectiveOption } from './utils.js';
-
-export const buttonDirective: DirectiveSpec = {
-  name: 'button',
-  doc: 'Button to navigate to external or internal links.',
-  arg: {
-    type: String,
-    doc: 'Target link of the button.',
-    required: true,
-  },
-  options: {
-    ...classDirectiveOption('button'),
-  },
-  body: {
-        type: 'myst',
-        doc: 'The body of the button.',
-        required: false,
-  },
-  run(data: DirectiveData): GenericNode[] {
-    const children: GenericNode[] = [];
-    if (data.body) {
-      children.push(...data.body as GenericNode[]);
-    }
-    const node: Link = {
-      type: 'link',
-      kind: 'button',
-      url: data.arg as string,
-      children: children as any[],
-  };
-    addClassOptions(data, node)
-    return [
-      node
-    ];
-  },
-};
diff --git a/packages/myst-directives/src/index.ts b/packages/myst-directives/src/index.ts
index 8d6f15c48..f3766426d 100644
--- a/packages/myst-directives/src/index.ts
+++ b/packages/myst-directives/src/index.ts
@@ -1,5 +1,4 @@
 import { admonitionDirective } from './admonition.js';
-import { buttonDirective } from './button.js';
 import { bibliographyDirective } from './bibliography.js';
 import { codeDirective, codeCellDirective } from './code.js';
 import { dropdownDirective } from './dropdown.js';
@@ -22,7 +21,6 @@ import { divDirective } from './div.js';
 
 export const defaultDirectives = [
   admonitionDirective,
-  buttonDirective,
   bibliographyDirective,
   csvTableDirective,
   codeDirective,
@@ -52,7 +50,6 @@ export const defaultDirectives = [
 
 export * from './utils.js';
 export { admonitionDirective } from './admonition.js';
-export { buttonDirective } from './button.js';
 export { bibliographyDirective } from './bibliography.js';
 export { codeDirective } from './code.js';
 export { dropdownDirective } from './dropdown.js';
diff --git a/packages/myst-ext-button/.eslintrc.cjs b/packages/myst-ext-button/.eslintrc.cjs
new file mode 100644
index 000000000..76787609a
--- /dev/null
+++ b/packages/myst-ext-button/.eslintrc.cjs
@@ -0,0 +1,4 @@
+module.exports = {
+  root: true,
+  extends: ['curvenote'],
+};
diff --git a/packages/myst-ext-button/CHANGELOG.md b/packages/myst-ext-button/CHANGELOG.md
new file mode 100644
index 000000000..627aa43d5
--- /dev/null
+++ b/packages/myst-ext-button/CHANGELOG.md
@@ -0,0 +1,125 @@
+# myst-ext-card
+
+## 1.0.9
+
+### Patch Changes
+
+- ce3c11c: Update inter-version deps
+
+## 1.0.8
+
+### Patch Changes
+
+- c758f1b5: Directive option flag is always a boolean
+- 0a516e5: Add documentation and an alias for url/link in cards.
+
+## 1.0.7
+
+### Patch Changes
+
+- b3e9df9d: Update to Project Jupyter and change all URLs
+- Updated dependencies [b3e9df9d]
+  - myst-common@1.5.1
+
+## 1.0.6
+
+### Patch Changes
+
+- 69457615: Update dependencies
+- Updated dependencies [e0cd47e3]
+  - myst-common@1.3.0
+
+## 1.0.5
+
+### Patch Changes
+
+- 6354420: Update versions of myst packages
+
+## 1.0.4
+
+### Patch Changes
+
+- 4183c05c: Change to use String/Number/Boolean instead of ParsedEnumType
+- d35e02bc: Change from alias as string to alias as a string-list.
+- Updated dependencies [d35e02bc]
+- Updated dependencies [b74fb3c1]
+- Updated dependencies [ed7b430f]
+- Updated dependencies [239ae762]
+- Updated dependencies [b74fb3c1]
+- Updated dependencies [86c78957]
+- Updated dependencies [d35e02bc]
+- Updated dependencies [d35e02bc]
+- Updated dependencies [99659250]
+  - myst-common@1.1.7
+
+## 1.0.3
+
+### Patch Changes
+
+- 7752cb70: Bump dependency versions
+- Updated dependencies [7752cb70]
+  - myst-common@1.1.5
+
+## 1.0.2
+
+### Patch Changes
+
+- Updates to internal dependencies
+- Updated dependencies [44ff6917]
+- Updated dependencies
+  - myst-common@1.1.0
+
+## 1.0.1
+
+### Patch Changes
+
+- b0a2a34b: Move repositories from mystjs --> mystmd
+- Updated dependencies [b0a2a34b]
+  - myst-common@1.0.2
+
+## 1.0.0
+
+### Major Changes
+
+- 00c05fe9: Migrate to ESM modules
+
+## 0.0.7
+
+### Patch Changes
+
+- Updated dependencies [79e24fd7]
+  - myst-common@0.0.17
+
+## 0.0.6
+
+### Patch Changes
+
+- Updated dependencies [d28b5e9d]
+  - myst-common@0.0.16
+
+## 0.0.5
+
+### Patch Changes
+
+- Updated dependencies [c832b38e]
+  - myst-common@0.0.15
+
+## 0.0.4
+
+### Patch Changes
+
+- Updated dependencies [9105d991]
+  - myst-common@0.0.14
+
+## 0.0.3
+
+### Patch Changes
+
+- Updated dependencies
+  - myst-common@0.0.13
+
+## 0.0.2
+
+### Patch Changes
+
+- a22fafa0: Separate packages for card/tab/grid/reactive roles/directives
diff --git a/packages/myst-ext-button/README.md b/packages/myst-ext-button/README.md
new file mode 100644
index 000000000..bdf031fd0
--- /dev/null
+++ b/packages/myst-ext-button/README.md
@@ -0,0 +1,3 @@
+# myst-ext-button
+
+`mystmd` extension for `button` role
diff --git a/packages/myst-ext-button/package.json b/packages/myst-ext-button/package.json
new file mode 100644
index 000000000..24770f392
--- /dev/null
+++ b/packages/myst-ext-button/package.json
@@ -0,0 +1,41 @@
+{
+  "name": "myst-ext-button",
+  "version": "0.0.0",
+  "sideEffects": false,
+  "license": "MIT",
+  "description": "MyST extension for button role",
+  "author": "Jenny Wong <jwong@2i2c.org>",
+  "homepage": "https://github.com/jupyter-book/mystmd/tree/main/packages/myst-ext-button",
+  "type": "module",
+  "exports": "./dist/index.js",
+  "types": "./dist/index.d.ts",
+  "files": [
+    "dist"
+  ],
+  "publishConfig": {
+    "access": "public"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/jupyter-book/mystmd.git"
+  },
+  "scripts": {
+    "clean": "rimraf dist",
+    "lint": "eslint \"src/**/!(*.spec).ts\" -c ./.eslintrc.cjs",
+    "lint:format": "npx prettier --check \"src/**/*.ts\"",
+    "test": "vitest run",
+    "test:watch": "vitest watch",
+    "build:esm": "tsc",
+    "build": "npm-run-all -l clean -p build:esm"
+  },
+  "bugs": {
+    "url": "https://github.com/jupyter-book/mystmd/issues"
+  },
+  "dependencies": {
+    "myst-common": "^1.7.2",
+    "myst-spec-ext": "^1.7.6"
+  },
+  "devDependencies": {
+    "myst-parser": "^1.5.7"
+  }
+}
diff --git a/packages/myst-ext-button/src/index.ts b/packages/myst-ext-button/src/index.ts
new file mode 100644
index 000000000..dc5bc9193
--- /dev/null
+++ b/packages/myst-ext-button/src/index.ts
@@ -0,0 +1,30 @@
+import type { RoleSpec, RoleData, GenericNode } from 'myst-common';
+import type { Link } from 'myst-spec-ext';
+
+const REF_PATTERN = /^(.+?)<([^<>]+)>$/;
+
+export const buttonRole: RoleSpec = {
+  name: 'button',
+  doc: 'Button to navigate to external or internal links.',
+  body: {
+        type: String,
+        doc: 'The body of the button.',
+        required: true,
+  },
+  run(data: RoleData): GenericNode[] {
+    const body = data.body as string;
+    const match = REF_PATTERN.exec(body);
+    const [, modified, rawLabel] = match ?? [];
+    const url = rawLabel ?? body;
+    const node: Link = {
+      type: 'link',
+      kind: 'button',
+      url,
+      children: [],
+    };
+    if (modified) node.children = [{ type: 'text', value: modified.trim() }];
+    return [
+      node
+    ];
+  },
+};
diff --git a/packages/myst-ext-button/tests/button.spec.ts b/packages/myst-ext-button/tests/button.spec.ts
new file mode 100644
index 000000000..e69de29bb
diff --git a/packages/myst-ext-button/tsconfig.json b/packages/myst-ext-button/tsconfig.json
new file mode 100644
index 000000000..1c5c0f1c4
--- /dev/null
+++ b/packages/myst-ext-button/tsconfig.json
@@ -0,0 +1,8 @@
+{
+  "extends": "../tsconfig/base.json",
+  "compilerOptions": {
+    "outDir": "dist"
+  },
+  "include": ["."],
+  "exclude": ["dist", "build", "node_modules", "src/**/*.spec.ts", "tests"]
+}

From 3a3feea38203712201d4930a39f2767d68650a47 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Mon, 13 Jan 2025 15:49:01 +0000
Subject: [PATCH 04/19] Delete changelog

---
 packages/myst-ext-button/CHANGELOG.md | 125 --------------------------
 1 file changed, 125 deletions(-)
 delete mode 100644 packages/myst-ext-button/CHANGELOG.md

diff --git a/packages/myst-ext-button/CHANGELOG.md b/packages/myst-ext-button/CHANGELOG.md
deleted file mode 100644
index 627aa43d5..000000000
--- a/packages/myst-ext-button/CHANGELOG.md
+++ /dev/null
@@ -1,125 +0,0 @@
-# myst-ext-card
-
-## 1.0.9
-
-### Patch Changes
-
-- ce3c11c: Update inter-version deps
-
-## 1.0.8
-
-### Patch Changes
-
-- c758f1b5: Directive option flag is always a boolean
-- 0a516e5: Add documentation and an alias for url/link in cards.
-
-## 1.0.7
-
-### Patch Changes
-
-- b3e9df9d: Update to Project Jupyter and change all URLs
-- Updated dependencies [b3e9df9d]
-  - myst-common@1.5.1
-
-## 1.0.6
-
-### Patch Changes
-
-- 69457615: Update dependencies
-- Updated dependencies [e0cd47e3]
-  - myst-common@1.3.0
-
-## 1.0.5
-
-### Patch Changes
-
-- 6354420: Update versions of myst packages
-
-## 1.0.4
-
-### Patch Changes
-
-- 4183c05c: Change to use String/Number/Boolean instead of ParsedEnumType
-- d35e02bc: Change from alias as string to alias as a string-list.
-- Updated dependencies [d35e02bc]
-- Updated dependencies [b74fb3c1]
-- Updated dependencies [ed7b430f]
-- Updated dependencies [239ae762]
-- Updated dependencies [b74fb3c1]
-- Updated dependencies [86c78957]
-- Updated dependencies [d35e02bc]
-- Updated dependencies [d35e02bc]
-- Updated dependencies [99659250]
-  - myst-common@1.1.7
-
-## 1.0.3
-
-### Patch Changes
-
-- 7752cb70: Bump dependency versions
-- Updated dependencies [7752cb70]
-  - myst-common@1.1.5
-
-## 1.0.2
-
-### Patch Changes
-
-- Updates to internal dependencies
-- Updated dependencies [44ff6917]
-- Updated dependencies
-  - myst-common@1.1.0
-
-## 1.0.1
-
-### Patch Changes
-
-- b0a2a34b: Move repositories from mystjs --> mystmd
-- Updated dependencies [b0a2a34b]
-  - myst-common@1.0.2
-
-## 1.0.0
-
-### Major Changes
-
-- 00c05fe9: Migrate to ESM modules
-
-## 0.0.7
-
-### Patch Changes
-
-- Updated dependencies [79e24fd7]
-  - myst-common@0.0.17
-
-## 0.0.6
-
-### Patch Changes
-
-- Updated dependencies [d28b5e9d]
-  - myst-common@0.0.16
-
-## 0.0.5
-
-### Patch Changes
-
-- Updated dependencies [c832b38e]
-  - myst-common@0.0.15
-
-## 0.0.4
-
-### Patch Changes
-
-- Updated dependencies [9105d991]
-  - myst-common@0.0.14
-
-## 0.0.3
-
-### Patch Changes
-
-- Updated dependencies
-  - myst-common@0.0.13
-
-## 0.0.2
-
-### Patch Changes
-
-- a22fafa0: Separate packages for card/tab/grid/reactive roles/directives

From b68fb3b07396ef412829a9c7fae393960116e8dd Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Wed, 15 Jan 2025 12:11:08 +0000
Subject: [PATCH 05/19] Fix linting

---
 packages/myst-cli/src/process/myst.ts |  5 +----
 packages/myst-ext-button/src/index.ts | 10 ++++------
 2 files changed, 5 insertions(+), 10 deletions(-)

diff --git a/packages/myst-cli/src/process/myst.ts b/packages/myst-cli/src/process/myst.ts
index d9461e08e..389b65e2a 100644
--- a/packages/myst-cli/src/process/myst.ts
+++ b/packages/myst-cli/src/process/myst.ts
@@ -48,10 +48,7 @@ export function parseMyst(
     extensions: {
       frontmatter: !opts?.ignoreFrontmatter,
     },
-    roles: [
-      buttonRole,
-      ...(session.plugins?.roles ?? [])
-    ],
+    roles: [buttonRole, ...(session.plugins?.roles ?? [])],
     vfile,
   });
   logMessagesFromVFile(session, vfile);
diff --git a/packages/myst-ext-button/src/index.ts b/packages/myst-ext-button/src/index.ts
index dc5bc9193..735ba3914 100644
--- a/packages/myst-ext-button/src/index.ts
+++ b/packages/myst-ext-button/src/index.ts
@@ -7,9 +7,9 @@ export const buttonRole: RoleSpec = {
   name: 'button',
   doc: 'Button to navigate to external or internal links.',
   body: {
-        type: String,
-        doc: 'The body of the button.',
-        required: true,
+    type: String,
+    doc: 'The body of the button.',
+    required: true,
   },
   run(data: RoleData): GenericNode[] {
     const body = data.body as string;
@@ -23,8 +23,6 @@ export const buttonRole: RoleSpec = {
       children: [],
     };
     if (modified) node.children = [{ type: 'text', value: modified.trim() }];
-    return [
-      node
-    ];
+    return [node];
   },
 };

From 2f735713b621ae523197b0076b8243a2fad8c035 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Wed, 15 Jan 2025 12:29:51 +0000
Subject: [PATCH 06/19] Remove test

---
 packages/myst-ext-button/tests/button.spec.ts | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 packages/myst-ext-button/tests/button.spec.ts

diff --git a/packages/myst-ext-button/tests/button.spec.ts b/packages/myst-ext-button/tests/button.spec.ts
deleted file mode 100644
index e69de29bb..000000000

From 3b0795ef40a09ba6b9162132ef52359fd08c7cda Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Wed, 15 Jan 2025 12:44:14 +0000
Subject: [PATCH 07/19] Add buttonRoles test

---
 packages/myst-ext-button/tests/button.spec.ts | 31 +++++++++++++++++++
 1 file changed, 31 insertions(+)
 create mode 100644 packages/myst-ext-button/tests/button.spec.ts

diff --git a/packages/myst-ext-button/tests/button.spec.ts b/packages/myst-ext-button/tests/button.spec.ts
new file mode 100644
index 000000000..ff4c6e166
--- /dev/null
+++ b/packages/myst-ext-button/tests/button.spec.ts
@@ -0,0 +1,31 @@
+import { describe, expect, it } from 'vitest';
+import { buttonRole } from '../src';
+import type { RoleData } from 'myst-common';
+
+describe('Button component', () => {
+  it('should process button role correctly', () => {
+    const data: RoleData = { body: 'Click me<http://example.com>' };
+    const result = buttonRole.run(data);
+    expect(result).toEqual([
+      {
+        type: 'link',
+        kind: 'button',
+        url: 'http://example.com',
+        children: [{ type: 'text', value: 'Click me' }],
+      },
+    ]);
+  });
+
+  it('should process button role without label correctly', () => {
+    const data: RoleData = { body: 'http://example.com' };
+    const result = buttonRole.run(data);
+    expect(result).toEqual([
+      {
+        type: 'link',
+        kind: 'button',
+        url: 'http://example.com',
+        children: [],
+      },
+    ]);
+  });
+});

From f30bb66a0d39753cdbf33cc66721d311a2ebfbcc Mon Sep 17 00:00:00 2001
From: Angus Hollands <goosey15@gmail.com>
Date: Thu, 23 Jan 2025 13:22:41 +0000
Subject: [PATCH 08/19] refactor: use 'class' exclusively for buttons

---
 packages/myst-ext-button/src/index.ts         |  2 +-
 packages/myst-ext-button/tests/button.spec.ts | 16 +++++++++-------
 packages/myst-spec-ext/src/types.ts           |  2 +-
 3 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/packages/myst-ext-button/src/index.ts b/packages/myst-ext-button/src/index.ts
index 735ba3914..cd93b91c8 100644
--- a/packages/myst-ext-button/src/index.ts
+++ b/packages/myst-ext-button/src/index.ts
@@ -18,9 +18,9 @@ export const buttonRole: RoleSpec = {
     const url = rawLabel ?? body;
     const node: Link = {
       type: 'link',
-      kind: 'button',
       url,
       children: [],
+      class: 'button', // TODO: allow users to extend this
     };
     if (modified) node.children = [{ type: 'text', value: modified.trim() }];
     return [node];
diff --git a/packages/myst-ext-button/tests/button.spec.ts b/packages/myst-ext-button/tests/button.spec.ts
index ff4c6e166..8dceb74e5 100644
--- a/packages/myst-ext-button/tests/button.spec.ts
+++ b/packages/myst-ext-button/tests/button.spec.ts
@@ -1,15 +1,18 @@
 import { describe, expect, it } from 'vitest';
 import { buttonRole } from '../src';
-import type { RoleData } from 'myst-common';
+import { VFile } from 'vfile';
 
 describe('Button component', () => {
   it('should process button role correctly', () => {
-    const data: RoleData = { body: 'Click me<http://example.com>' };
-    const result = buttonRole.run(data);
+    const result = buttonRole.run(
+      { name: 'button', body: 'Click me<http://example.com>' },
+      new VFile(),
+    );
+
     expect(result).toEqual([
       {
         type: 'link',
-        kind: 'button',
+        class: 'button',
         url: 'http://example.com',
         children: [{ type: 'text', value: 'Click me' }],
       },
@@ -17,12 +20,11 @@ describe('Button component', () => {
   });
 
   it('should process button role without label correctly', () => {
-    const data: RoleData = { body: 'http://example.com' };
-    const result = buttonRole.run(data);
+    const result = buttonRole.run({ name: 'button', body: 'http://example.com' }, new VFile());
     expect(result).toEqual([
       {
         type: 'link',
-        kind: 'button',
+        class: 'button',
         url: 'http://example.com',
         children: [],
       },
diff --git a/packages/myst-spec-ext/src/types.ts b/packages/myst-spec-ext/src/types.ts
index 8b241d688..0a2d793b9 100644
--- a/packages/myst-spec-ext/src/types.ts
+++ b/packages/myst-spec-ext/src/types.ts
@@ -275,6 +275,7 @@ export type CrossReference = SpecCrossReference & {
   dataUrl?: string;
   remoteBaseUrl?: string;
   html_id?: string;
+  class?: Image['class'];
 };
 
 export type Link = SpecLink & {
@@ -284,7 +285,6 @@ export type Link = SpecLink & {
   static?: true;
   protocol?: string;
   error?: true;
-  kind?: 'button';
   class?: Image['class'];
 };
 

From 67850a6d5931b2192fdce6436f46b7bdc0706086 Mon Sep 17 00:00:00 2001
From: Angus Hollands <goosey15@gmail.com>
Date: Thu, 23 Jan 2025 17:04:35 +0000
Subject: [PATCH 09/19] chore: add changeset

---
 .changeset/lazy-houses-tell.md | 7 +++++++
 1 file changed, 7 insertions(+)
 create mode 100644 .changeset/lazy-houses-tell.md

diff --git a/.changeset/lazy-houses-tell.md b/.changeset/lazy-houses-tell.md
new file mode 100644
index 000000000..b37eb3fdc
--- /dev/null
+++ b/.changeset/lazy-houses-tell.md
@@ -0,0 +1,7 @@
+---
+"myst-ext-button": patch
+"myst-spec-ext": patch
+"myst-cli": patch
+---
+
+Add new button role

From 6a1c60e3e5a8bbacacfdb19c54fc2ad1235a2873 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Fri, 24 Jan 2025 12:25:39 +0000
Subject: [PATCH 10/19] Update doc

---
 packages/myst-ext-button/src/index.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/myst-ext-button/src/index.ts b/packages/myst-ext-button/src/index.ts
index cd93b91c8..830878c13 100644
--- a/packages/myst-ext-button/src/index.ts
+++ b/packages/myst-ext-button/src/index.ts
@@ -5,7 +5,7 @@ const REF_PATTERN = /^(.+?)<([^<>]+)>$/;
 
 export const buttonRole: RoleSpec = {
   name: 'button',
-  doc: 'Button to navigate to external or internal links.',
+  doc: 'Button element with an action to navigate to internal or external links.',
   body: {
     type: String,
     doc: 'The body of the button.',

From 45c9b9be4eca34ae64281da5055db8c5a23babc5 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Fri, 24 Jan 2025 12:25:57 +0000
Subject: [PATCH 11/19] Add buttonRole to directives plugin

---
 docs/directives.mjs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/docs/directives.mjs b/docs/directives.mjs
index 4f28ca3d7..d2007009a 100644
--- a/docs/directives.mjs
+++ b/docs/directives.mjs
@@ -3,6 +3,7 @@ import { mystParse } from 'myst-parser';
 import { defaultDirectives } from 'myst-directives';
 import { defaultRoles } from 'myst-roles';
 import { cardDirective } from 'myst-ext-card';
+import { buttonRole } from 'myst-ext-button';
 import { gridDirectives } from 'myst-ext-grid';
 import { proofDirective } from 'myst-ext-proof';
 import { exerciseDirectives } from 'myst-ext-exercise';
@@ -17,7 +18,7 @@ const allDirectives = [
   cardDirective,
   proofDirective,
 ];
-const allRoles = [...defaultRoles];
+const allRoles = [...defaultRoles, buttonRole];
 
 /**
  * @param {import('myst-common').OptionDefinition} option

From f8827ca59cf902889fc3ded89e8da67a5e4bda97 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Fri, 24 Jan 2025 12:26:08 +0000
Subject: [PATCH 12/19] Add buttonRole to roles reference

---
 docs/roles.md | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/docs/roles.md b/docs/roles.md
index 56dbac85d..28990e3f7 100644
--- a/docs/roles.md
+++ b/docs/roles.md
@@ -6,6 +6,9 @@ description: A full list of the roles included in MyST Markdown by default.
 :::{myst:role} abbreviation
 :::
 
+:::{myst:role} button
+:::
+
 :::{myst:role} chemicalFormula
 :::
 

From 06316d4ce50d5ffb15cbc40b1fb715e92997e86c Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Fri, 24 Jan 2025 13:25:58 +0000
Subject: [PATCH 13/19] Add button docs

---
 docs/dropdowns-cards-and-tabs.md | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/docs/dropdowns-cards-and-tabs.md b/docs/dropdowns-cards-and-tabs.md
index 224cfbe68..320fe5aef 100644
--- a/docs/dropdowns-cards-and-tabs.md
+++ b/docs/dropdowns-cards-and-tabs.md
@@ -77,7 +77,7 @@ Footer
 Note that, card headers and footers are optional. If you don’t include ^^^ or +++ in your card, they will not show up.
 ````
 
-### `card` reference
+#### `card` reference
 
 **Arguments** _(optional, markdown)_
 : The `card` can take a single argument that is the title as a string.
@@ -94,6 +94,18 @@ Note that, card headers and footers are optional. If you don’t include ^^^ or
     link _(optional, string)_
     : If given, clicking the card will direct you to the URL given here.
 
+### Buttons
+
+A button is an element with text content that triggers an action to navigate to an internal or external reference upon a user click. Use the {myst:role}`button` role followed by the text content and target path to create a button.
+
+```{myst}
+{button}`MyST Role Spec <roles.md>`
+```
+
+```{myst}
+{button}`MyST-MD GitHub <https://github.com/jupyter-book/
+```
+
 ### Grids
 
 Grids allow you to structure arbitrary chunks of content in a grid-like system.

From dcac0445094f4ad87a90f74e477f9b4a536743bb Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Fri, 24 Jan 2025 14:33:22 +0000
Subject: [PATCH 14/19] Typo

---
 docs/dropdowns-cards-and-tabs.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/dropdowns-cards-and-tabs.md b/docs/dropdowns-cards-and-tabs.md
index 320fe5aef..e1ab91953 100644
--- a/docs/dropdowns-cards-and-tabs.md
+++ b/docs/dropdowns-cards-and-tabs.md
@@ -103,7 +103,7 @@ A button is an element with text content that triggers an action to navigate to
 ```
 
 ```{myst}
-{button}`MyST-MD GitHub <https://github.com/jupyter-book/
+{button}`MyST-MD GitHub <https://github.com/jupyter-book/>
 ```
 
 ### Grids

From 4b473ebbb3fe23bd3b488c3e2eda56e5af830ab4 Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Fri, 24 Jan 2025 14:34:27 +0000
Subject: [PATCH 15/19] Typo

---
 docs/dropdowns-cards-and-tabs.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/docs/dropdowns-cards-and-tabs.md b/docs/dropdowns-cards-and-tabs.md
index e1ab91953..40cb975c8 100644
--- a/docs/dropdowns-cards-and-tabs.md
+++ b/docs/dropdowns-cards-and-tabs.md
@@ -98,6 +98,10 @@ Note that, card headers and footers are optional. If you don’t include ^^^ or
 
 A button is an element with text content that triggers an action to navigate to an internal or external reference upon a user click. Use the {myst:role}`button` role followed by the text content and target path to create a button.
 
+{button}`MyST Role Spec <roles.md>`
+
+{button}`MyST-MD GitHub <https://github.com/jupyter-book/>`
+
 ```{myst}
 {button}`MyST Role Spec <roles.md>`
 ```

From f6d5bcc6d639435baf49daeecd42d2ba8375d11a Mon Sep 17 00:00:00 2001
From: jnywong <jnywong.pro@gmail.com>
Date: Fri, 24 Jan 2025 15:21:57 +0000
Subject: [PATCH 16/19] Minor edit

---
 docs/dropdowns-cards-and-tabs.md | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/docs/dropdowns-cards-and-tabs.md b/docs/dropdowns-cards-and-tabs.md
index 40cb975c8..63e13e09e 100644
--- a/docs/dropdowns-cards-and-tabs.md
+++ b/docs/dropdowns-cards-and-tabs.md
@@ -98,16 +98,12 @@ Note that, card headers and footers are optional. If you don’t include ^^^ or
 
 A button is an element with text content that triggers an action to navigate to an internal or external reference upon a user click. Use the {myst:role}`button` role followed by the text content and target path to create a button.
 
-{button}`MyST Role Spec <roles.md>`
-
-{button}`MyST-MD GitHub <https://github.com/jupyter-book/>`
-
 ```{myst}
 {button}`MyST Role Spec <roles.md>`
 ```
 
 ```{myst}
-{button}`MyST-MD GitHub <https://github.com/jupyter-book/>
+{button}`MyST-MD GitHub <https://github.com/jupyter-book/mystmd>`
 ```
 
 ### Grids

From ebb93aaf5593f44d2d262d2a5199ec13e2133187 Mon Sep 17 00:00:00 2001
From: Angus Hollands <goosey15@gmail.com>
Date: Mon, 27 Jan 2025 15:17:33 +0000
Subject: [PATCH 17/19] docs: use MyST directive for extensions

---
 docs/dropdowns-cards-and-tabs.md | 47 ++++++++------------------------
 1 file changed, 11 insertions(+), 36 deletions(-)

diff --git a/docs/dropdowns-cards-and-tabs.md b/docs/dropdowns-cards-and-tabs.md
index 63e13e09e..5b13bcd33 100644
--- a/docs/dropdowns-cards-and-tabs.md
+++ b/docs/dropdowns-cards-and-tabs.md
@@ -76,23 +76,8 @@ Footer
 
 Note that, card headers and footers are optional. If you don’t include ^^^ or +++ in your card, they will not show up.
 ````
-
-#### `card` reference
-
-**Arguments** _(optional, markdown)_
-: The `card` can take a single argument that is the title as a string.
-
-**Options**
-: No options for the `card` are required
-
-    header _(optional, markdown)_
-    : Styled content at the top of the card
-
-    footer _(optional, markdown)_
-    : Styled content at the bottom of the card
-
-    link _(optional, string)_
-    : If given, clicking the card will direct you to the URL given here.
+:::{myst:directive} card
+:::
 
 ### Buttons
 
@@ -105,6 +90,8 @@ A button is an element with text content that triggers an action to navigate to
 ```{myst}
 {button}`MyST-MD GitHub <https://github.com/jupyter-book/mystmd>`
 ```
+:::{myst:directive} button
+:::
 
 ### Grids
 
@@ -136,6 +123,9 @@ Execute notebook cells, store results, and insert outputs across pages.
 ::::
 ```
 
+:::{myst:directive} grid
+:::
+
 ## Tabs
 
 You can also produce tabbed content. This allows you to display a variety of tabbed content blocks that users can click on.
@@ -166,23 +156,8 @@ Synced content for tab 2
 ```
 ````
 
-### `tab-item` reference
-
-**Arguments** _(required: `1`, string)_
-: The `tab-item` requires a single argument that is the title as a string.
-
-    ```{warning}
-    :class: dropdown
-    # Note: the `tab-item` title is not currently not parsed
-
-    The current implementation does not parse the tab title properly, and markup in this field will not be parsed.
-    ```
-
-**Options**
-: No options for the `tab-item` are required
-
-    sync _(optional, string)_
-    : A key that is used to sync the selected tab across multiple tab-sets.
+:::{myst:directive} tab-set
+:::
 
-    selected _(flag, no-value)_
-    : a flag indicating whether the tab should be selected by default.
+:::{myst:directive} tab-item
+:::

From aa3d6a863a95717abf2c9c6b14219910b4e02537 Mon Sep 17 00:00:00 2001
From: Angus Hollands <goosey15@gmail.com>
Date: Mon, 27 Jan 2025 15:18:13 +0000
Subject: [PATCH 18/19] docs: use myst:role instead of myst:directive

---
 docs/dropdowns-cards-and-tabs.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/dropdowns-cards-and-tabs.md b/docs/dropdowns-cards-and-tabs.md
index 5b13bcd33..133aefe81 100644
--- a/docs/dropdowns-cards-and-tabs.md
+++ b/docs/dropdowns-cards-and-tabs.md
@@ -90,7 +90,7 @@ A button is an element with text content that triggers an action to navigate to
 ```{myst}
 {button}`MyST-MD GitHub <https://github.com/jupyter-book/mystmd>`
 ```
-:::{myst:directive} button
+:::{myst:role} button
 :::
 
 ### Grids

From 7e72ca2238a53c1d741f874d5431ddf6d6ed6df6 Mon Sep 17 00:00:00 2001
From: Angus Hollands <goosey15@gmail.com>
Date: Mon, 27 Jan 2025 15:28:29 +0000
Subject: [PATCH 19/19] fix: correct heading depth

---
 docs/dropdowns-cards-and-tabs.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/docs/dropdowns-cards-and-tabs.md b/docs/dropdowns-cards-and-tabs.md
index 133aefe81..5fb0b5adc 100644
--- a/docs/dropdowns-cards-and-tabs.md
+++ b/docs/dropdowns-cards-and-tabs.md
@@ -27,7 +27,7 @@ You can also hide the body of your admonition blocks so that users must click a
 To turn an admonition into a dropdown, add the option `:class: dropdown` to them. See [](#admonition-dropdown) for more information.
 ```
 
-### Cards
+## Cards
 
 Cards provide an easy way for you to content into a standard “header”, “body”, “footer” structure that has a similar alignment and visual style. It is useful for creating galleries or high-visibility collections of links and information.
 For example, a card with a header, title, body, and footer:
@@ -79,7 +79,7 @@ Note that, card headers and footers are optional. If you don’t include ^^^ or
 :::{myst:directive} card
 :::
 
-### Buttons
+## Buttons
 
 A button is an element with text content that triggers an action to navigate to an internal or external reference upon a user click. Use the {myst:role}`button` role followed by the text content and target path to create a button.
 
@@ -93,7 +93,7 @@ A button is an element with text content that triggers an action to navigate to
 :::{myst:role} button
 :::
 
-### Grids
+## Grids
 
 Grids allow you to structure arbitrary chunks of content in a grid-like system.