storybook-addon-md 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,52 @@
1
1
  # storybook-addon-md
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 298284c: Add an opt-in `links` option that rewrites relative Markdown links instead of bundling their targets as assets.
8
+
9
+ - `links.documents` (default `true` when `links` is set) turns links to other discovered documents into ordinary links to their Docs page in the Storybook manager.
10
+ - `links.repository` turns links to other files or folders inside root into `<repository>/<relative path>` links, so source files are no longer copied into the bundle and folder links no longer fail discovery.
11
+
12
+ Images, image reference definitions, manifests, and the default behavior without `links` are unchanged.
13
+
14
+ ## 0.7.0
15
+
16
+ ### Minor Changes
17
+
18
+ - 91cc4a7: Add back focused styling hooks for the page, links, quotes, callouts, tables, and images, built on two new shared tokens.
19
+
20
+ **New shared tokens**
21
+
22
+ - `--sbmd-accent-color` colors links and task-list checkboxes. `--sbmd-link-color` now defaults to it.
23
+ - `--sbmd-radius` rounds quotes, callouts, images, and code. It is unset by default, so code keeps its `0.1875rem` radius and other elements stay square until you set it.
24
+
25
+ **New element variables**
26
+
27
+ | Area | Variables |
28
+ | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
29
+ | Page | `--sbmd-page-max-width`, `--sbmd-page-margin`, `--sbmd-page-padding`, `--sbmd-page-background` |
30
+ | Links | `--sbmd-link-hover-decoration`, `--sbmd-link-underline-offset`. `--sbmd-link-decoration` accepts shorthands like `underline 1px` |
31
+ | Quotes | `--sbmd-quote-background` |
32
+ | Callouts | `--sbmd-callout-accent`, `--sbmd-callout-border`, `--sbmd-callout-background` |
33
+ | Tables | `--sbmd-table-border`, `--sbmd-table-heading-background` |
34
+ | Images | `--sbmd-image-border` |
35
+
36
+ **Callout accents**
37
+
38
+ Each callout now exposes its resolved color as `--sbmd-callout-accent`. Reference it from a `.storybook-addon-md-callout` rule to derive tinted backgrounds or borders for every type at once:
39
+
40
+ ```css
41
+ .storybook-addon-md-callout {
42
+ --sbmd-callout-background: color-mix(in srgb, var(--sbmd-callout-accent) 8%, transparent);
43
+ }
44
+ ```
45
+
46
+ **Behavior change**
47
+
48
+ `--sbmd-quote-border` no longer applies to callouts. Set `--sbmd-callout-border` instead; its default is `0.25rem solid var(--sbmd-callout-accent)`.
49
+
3
50
  ## 0.6.0
4
51
 
5
52
  ### Minor Changes
package/README.md CHANGED
@@ -159,6 +159,7 @@ Invalid frontmatter, missing or ambiguous story references, and missing local as
159
159
  | `manifests` | `false` | Include original Markdown in Storybook documentation manifests. |
160
160
  | `tagFields` | `[]` | Additional frontmatter fields displayed as tags. |
161
161
  | `presentation` | None | Module exporting `Layout` and/or `MarkdownRenderer`. |
162
+ | `links` | None | Rewrite relative links to Docs pages and repository files. |
162
163
 
163
164
  Globs and customization paths start from your project folder (the parent of `.storybook` by default). Keep the config and local files inside that folder. Restart Storybook after changing options.
164
165
 
@@ -186,6 +187,30 @@ const config: StorybookConfig = {
186
187
 
187
188
  `tagFields` adds string values and string array items from those fields to the existing tags. Missing fields, blank strings, and non-string values are ignored. Labels are deduplicated by exact value across documents, tags, configured fields, and status; status takes precedence and retains its original `data-status`. Metadata is not modified. Consumers can remove adapters that only copied these fields into `tags`.
188
189
 
190
+ ### Relative links
191
+
192
+ By default every relative link is bundled as an asset, so a link to another Markdown file opens its raw source. Set `links` to rewrite relative links instead:
193
+
194
+ ```ts
195
+ {
196
+ name: 'storybook-addon-md',
197
+ options: {
198
+ patterns: ['src/**/*.md', 'docs/**/*.md'],
199
+ links: {
200
+ documents: true,
201
+ repository: 'https://github.com/acme/design-system/blob/main',
202
+ },
203
+ },
204
+ }
205
+ ```
206
+
207
+ | Field | Default | Behavior |
208
+ | ------------ | ------- | ------------------------------------------------------------------------------------------------------------------------ |
209
+ | `documents` | `true` | Links to discovered Markdown documents become ordinary links to their Docs page in the manager, opened in the top frame. |
210
+ | `repository` | None | Links to other files or folders inside the project folder become `<repository>/<path relative to root>` links. |
211
+
212
+ Fragments and query strings are preserved. Attached documents link to their story file's Docs page using the CSF `title`; when the story file has no explicit title, the link falls back to the `story:<path>` key and will not resolve until a title is set. Images and image reference definitions remain bundled assets, and manifests keep the original Markdown. Relative links to files outside root, or to missing files, still fail the build. Without `links`, behavior is unchanged.
213
+
189
214
  ### Node parsing and CI checks
190
215
 
191
216
  Use the Node-only `storybook-addon-md/node` export. It shares discovery's parser and story resolution without loading Storybook or browser code:
@@ -255,14 +280,14 @@ Set `stylesheet: '.storybook/markdown.css'` to override the defaults:
255
280
  }
256
281
  ```
257
282
 
258
- Variables cover typography, spacing, borders, links, code, tables, chips, and callouts. Defaults follow Storybook’s Docs theme in light and dark mode.
283
+ Shared tokens cover accent, border, radius, and spacing. Element variables cover the page layout, typography, links, quotes, callouts, code, tables, images, and chips. Defaults follow Storybook’s Docs theme in light and dark mode.
259
284
 
260
285
  See [Styling](STYLING.md) for all variables, status and callout colors, theme switching, and custom layouts or Markdown renderers. The [example stylesheet](https://github.com/ruijdacd/storybook-addon-md/blob/main/example/.storybook/markdown.css) provides a complete GitHub-inspired theme.
261
286
 
262
287
  ## Links and limitations
263
288
 
264
289
  - Relative links and images resolve from the Markdown source and are included in static builds. Root-relative assets use Storybook’s `staticDirs`.
265
- - Links to `.md` files open the original source, not a rendered Docs page. Use a Storybook URL such as `/?path=/docs/guides-introduction--docs` for page navigation.
290
+ - Without the `links` option, links to `.md` files open the original source, not a rendered Docs page. Set `links` or use a Storybook URL such as `?path=/docs/guides-introduction--docs` for page navigation.
266
291
  - Braces and JSX-like text are treated as content. Raw HTML renders as text by default.
267
292
  - Set Storybook’s `parameters.options.storySort` for explicit sidebar ordering. See the [example preview](https://github.com/ruijdacd/storybook-addon-md/blob/main/example/.storybook/preview.ts).
268
293
  - Multiple development Storybooks sharing one config directory are unsupported.
package/STYLING.md CHANGED
@@ -6,72 +6,93 @@ Set these variables on `.storybook-addon-md-page` in the file configured by `sty
6
6
  .storybook-addon-md-page {
7
7
  --sbmd-font-size: 1rem;
8
8
  --sbmd-monospace-font-family: 'JetBrains Mono', monospace;
9
+ --sbmd-accent-color: #0969da;
9
10
  --sbmd-border-color: #d1d9e0;
11
+ --sbmd-radius: 0.375rem;
10
12
  --sbmd-block-spacing: 1.25rem;
11
- --sbmd-quote-border: 0.1875rem solid currentColor;
13
+ --sbmd-link-decoration: underline 0.0625rem;
12
14
  --sbmd-table-cell-padding: 0.75rem 1rem;
13
- --sbmd-tag-radius: 0.375rem;
14
15
  }
15
16
  ```
16
17
 
17
18
  Default lengths use `rem`, preserving their original sizes at a 16px root font size and scaling with the document root font size. Set `--sbmd-monospace-font-family` to customize inline code and fenced code blocks; it defaults to Storybook’s monospace theme font.
18
19
 
19
- Variables accept normal CSS values for the property listed below, including `clamp()`, `calc()`, and references to your own theme variables. Border variables accept full border shorthands.
20
-
21
- Three shared tokens cover most of the page. `--sbmd-border-color` colors heading rules, table cells, inline code, horizontal rules, and the default quote border. `--sbmd-block-spacing` sets the vertical rhythm of paragraphs, lists, quotes, callouts, tables, the title, and the tag list. `--sbmd-heading-spacing` sets the space above headings and around horizontal rules.
22
-
23
- Use your theme selector to override colors in dark mode. The [complete example](https://github.com/ruijdacd/storybook-addon-md/blob/main/example/.storybook/markdown.css) maps the addon variables to Tailwind v4 theme variables. Its `light-dark()` colors follow the system preference.
24
-
25
- | Variable | Applies to |
26
- | -------------------------------- | ------------------------------------------------- |
27
- | `--sbmd-font-family` | Page and Markdown text |
28
- | `--sbmd-font-size` | Markdown text |
29
- | `--sbmd-line-height` | Markdown text |
30
- | `--sbmd-color` | Page and Markdown text |
31
- | `--sbmd-monospace-font-family` | Inline code and code blocks |
32
- | `--sbmd-border-color` | Heading rules, tables, inline code, rules, quotes |
33
- | `--sbmd-block-spacing` | Vertical margin of blocks, title, and tag list |
34
- | `--sbmd-heading-spacing` | Space above headings and around rules |
35
- | `--sbmd-heading-color` | Headings and title |
36
- | `--sbmd-heading-font-family` | Headings and title |
37
- | `--sbmd-heading-weight` | Headings and title |
38
- | `--sbmd-h1-size` | Title and `h1` |
39
- | `--sbmd-h2-size` | `h2` |
40
- | `--sbmd-h3-size` | `h3` |
41
- | `--sbmd-h4-size` | `h4` |
42
- | `--sbmd-h5-size` | `h5` |
43
- | `--sbmd-h6-size` | `h6` |
44
- | `--sbmd-link-color` | Links |
45
- | `--sbmd-link-decoration` | Link `text-decoration` |
46
- | `--sbmd-list-item-spacing` | Space between list items |
47
- | `--sbmd-quote-border` | Start border of quotes and callouts |
48
- | `--sbmd-quote-padding` | Quote padding |
49
- | `--sbmd-callout-padding` | Callout padding |
50
- | `--sbmd-callout-label-weight` | Callout label weight |
51
- | `--sbmd-callout-note-color` | Note accent color |
52
- | `--sbmd-callout-tip-color` | Tip accent color |
53
- | `--sbmd-callout-important-color` | Important accent color |
54
- | `--sbmd-callout-warning-color` | Warning accent color |
55
- | `--sbmd-callout-caution-color` | Caution accent color |
56
- | `--sbmd-code-radius` | Inline code and code block radius |
57
- | `--sbmd-code-padding` | Code block padding |
58
- | `--sbmd-inline-code-padding` | Inline code padding |
59
- | `--sbmd-inline-code-background` | Inline code background |
60
- | `--sbmd-inline-code-size` | Inline code font size |
61
- | `--sbmd-table-cell-padding` | Table cell padding |
62
- | `--sbmd-table-stripe-background` | Even table row background |
63
- | `--sbmd-tag-color` | Tag text |
64
- | `--sbmd-tag-background` | Tag background |
65
- | `--sbmd-tag-border` | Tag border |
66
- | `--sbmd-tag-radius` | Tag radius |
67
- | `--sbmd-tag-padding` | Tag padding |
68
- | `--sbmd-tag-font-size` | Tag font size |
20
+ Variables accept normal CSS values for the property they map to, including `clamp()`, `calc()`, and references to your own theme variables. Border variables accept full border shorthands, and decoration variables accept full `text-decoration` shorthands such as `underline 2px`.
21
+
22
+ ## Shared tokens
23
+
24
+ Five tokens cover most of the page. Element variables fall back to them, so set the token first and reach for an element variable only where one element should differ.
25
+
26
+ | Token | Default | Used by |
27
+ | ------------------------ | ---------------------- | ------------------------------------------------------------------------ |
28
+ | `--sbmd-accent-color` | Storybook link color | Links and task-list checkboxes |
29
+ | `--sbmd-border-color` | Storybook border color | Heading rules, table cells, inline code, horizontal rules, quote borders |
30
+ | `--sbmd-radius` | Unset | Quotes, callouts, images, and code; code alone defaults to `0.1875rem` |
31
+ | `--sbmd-block-spacing` | `1rem` | Paragraphs, lists, quotes, callouts, tables, the title, and the tag list |
32
+ | `--sbmd-heading-spacing` | `1.5rem` | Space above headings and around horizontal rules |
33
+
34
+ ## Element variables
35
+
36
+ | Variable | Applies to |
37
+ | --------------------------------- | --------------------------------------------- |
38
+ | `--sbmd-font-family` | Page and Markdown text |
39
+ | `--sbmd-font-size` | Markdown text |
40
+ | `--sbmd-line-height` | Markdown text |
41
+ | `--sbmd-color` | Page and Markdown text |
42
+ | `--sbmd-monospace-font-family` | Inline code and code blocks |
43
+ | `--sbmd-page-max-width` | Page `max-width` |
44
+ | `--sbmd-page-margin` | Page `margin`, for example `0 auto` to center |
45
+ | `--sbmd-page-padding` | Page `padding` |
46
+ | `--sbmd-page-background` | Page `background` |
47
+ | `--sbmd-heading-color` | Headings and title |
48
+ | `--sbmd-heading-font-family` | Headings and title |
49
+ | `--sbmd-heading-weight` | Headings and title |
50
+ | `--sbmd-h1-size` | Title and `h1` |
51
+ | `--sbmd-h2-size` | `h2` |
52
+ | `--sbmd-h3-size` | `h3` |
53
+ | `--sbmd-h4-size` | `h4` |
54
+ | `--sbmd-h5-size` | `h5` |
55
+ | `--sbmd-h6-size` | `h6` |
56
+ | `--sbmd-link-color` | Link color, defaults to the accent |
57
+ | `--sbmd-link-decoration` | Link `text-decoration` |
58
+ | `--sbmd-link-hover-decoration` | Link `text-decoration` on hover |
59
+ | `--sbmd-link-underline-offset` | Link `text-underline-offset` |
60
+ | `--sbmd-list-item-spacing` | Space between list items |
61
+ | `--sbmd-quote-border` | Quote start border |
62
+ | `--sbmd-quote-padding` | Quote padding |
63
+ | `--sbmd-quote-background` | Quote background |
64
+ | `--sbmd-callout-accent` | Resolved accent of the current callout |
65
+ | `--sbmd-callout-border` | Callout start border |
66
+ | `--sbmd-callout-padding` | Callout padding |
67
+ | `--sbmd-callout-background` | Callout background |
68
+ | `--sbmd-callout-label-weight` | Callout label weight |
69
+ | `--sbmd-callout-note-color` | Note accent color |
70
+ | `--sbmd-callout-tip-color` | Tip accent color |
71
+ | `--sbmd-callout-important-color` | Important accent color |
72
+ | `--sbmd-callout-warning-color` | Warning accent color |
73
+ | `--sbmd-callout-caution-color` | Caution accent color |
74
+ | `--sbmd-code-radius` | Inline code and code block radius |
75
+ | `--sbmd-code-padding` | Code block padding |
76
+ | `--sbmd-inline-code-padding` | Inline code padding |
77
+ | `--sbmd-inline-code-background` | Inline code background |
78
+ | `--sbmd-inline-code-size` | Inline code font size |
79
+ | `--sbmd-table-border` | Table cell borders |
80
+ | `--sbmd-table-cell-padding` | Table cell padding |
81
+ | `--sbmd-table-heading-background` | Table header cell background |
82
+ | `--sbmd-table-stripe-background` | Even table row background |
83
+ | `--sbmd-image-border` | Image border |
84
+ | `--sbmd-tag-color` | Tag text |
85
+ | `--sbmd-tag-background` | Tag background |
86
+ | `--sbmd-tag-border` | Tag border |
87
+ | `--sbmd-tag-radius` | Tag radius |
88
+ | `--sbmd-tag-padding` | Tag padding |
89
+ | `--sbmd-tag-font-size` | Tag font size |
69
90
 
70
91
  Status chips have `data-status` set to the original frontmatter value. Override `--sbmd-tag-*` on selectors such as `.storybook-addon-md-tag[data-status="stable" i]` to assign a status-specific appearance.
71
92
 
72
- Callouts are `.storybook-addon-md-callout` elements with `data-callout` set to `note`, `tip`, `important`, `warning`, or `caution`, and a `.storybook-addon-md-callout-label` paragraph. Each type’s accent color sets the border color and label color. Per-type accent variables default to Storybook theme colors chosen for the light or dark base. Callouts share `--sbmd-quote-border` for the border width and style.
93
+ Callouts are `.storybook-addon-md-callout` elements with `data-callout` set to `note`, `tip`, `important`, `warning`, or `caution`, and a `.storybook-addon-md-callout-label` paragraph. The addon sets `--sbmd-callout-accent` on each callout from its type’s accent variable, and uses it for the default border and the label color. Per-type accent variables default to Storybook theme colors chosen for the light or dark base.
73
94
 
74
- Properties without a variable use ordinary CSS. The addon does not set backgrounds, radii, or borders on the page, quotes, callouts, or images, so selectors such as `.storybook-addon-md img` work at any specificity. Properties the addon does set, such as heading letter spacing or table alignment, need a selector that matches the addon’s specificity, for example `.sbdocs-content .storybook-addon-md h2`.
95
+ Properties without a variable use ordinary CSS. Match the addon’s specificity for properties it sets, for example `.sbdocs-content .storybook-addon-md h2` for heading letter spacing or `.sbdocs-content .storybook-addon-md-tags` for the tag gap.
75
96
 
76
97
  These styles target Markdown content and its title/chips. Story canvases, props controls, and syntax-highlighting colors still use Storybook’s theme. Custom renderers can use the shared styles when they produce matching HTML elements; custom layouts own any additional structure. Internal `--sbmd-native-*` variables carry Storybook theme values and are not customization hooks.
77
98
 
@@ -88,13 +109,14 @@ Set `stylesheet: '.storybook/markdown.css'` in the addon options, then define yo
88
109
  --sbmd-font-size: 1rem;
89
110
  --sbmd-monospace-font-family: 'JetBrains Mono', monospace;
90
111
  --sbmd-line-height: 1.8;
91
- --sbmd-heading-color: currentColor;
92
- --sbmd-tag-radius: 0.375rem;
93
- --sbmd-tag-border: 0.0625rem solid currentColor;
112
+ --sbmd-accent-color: #0969da;
113
+ --sbmd-radius: 0.375rem;
114
+ --sbmd-page-max-width: 60rem;
115
+ --sbmd-page-margin: 0 auto;
94
116
  }
95
117
  ```
96
118
 
97
- Variables cover typography, spacing, borders, links, code, tables, callouts, and chips. They inherit from your theme container, and default text and link colors follow the active Docs theme. See the [reference](#css-variable-reference) for the complete list.
119
+ Shared tokens cover accent, borders, radius, and spacing. Element variables cover the page layout, typography, links, quotes, callouts, code, tables, images, and chips. They inherit from your theme container, and default text and link colors follow the active Docs theme. See the [reference](#css-variable-reference) for the complete list.
98
120
 
99
121
  Use ordinary CSS for other properties. `.storybook-addon-md` wraps Markdown content, including custom renderer output; titles, props, and examples sit outside it. Other stable selectors are `.storybook-addon-md-page`, `.storybook-addon-md-title`, `.storybook-addon-md-tags`, and `.storybook-addon-md-tag`.
100
122
 
@@ -130,12 +152,20 @@ Map the accent colors to your design tokens, and adjust the shared box and label
130
152
  }
131
153
  ```
132
154
 
133
- Use `data-callout` for anything that differs per type beyond the accent color, such as a tinted background or a rounded corner:
155
+ `--sbmd-callout-accent` resolves to the current callout’s color. Values that reference it must be set on the callout selector so they resolve per type. This tints every callout with its own accent:
156
+
157
+ ```css
158
+ .storybook-addon-md-callout {
159
+ --sbmd-callout-background: color-mix(in srgb, var(--sbmd-callout-accent) 8%, transparent);
160
+ --sbmd-callout-border: 0.375rem solid var(--sbmd-callout-accent);
161
+ }
162
+ ```
163
+
164
+ Use `data-callout` for anything specific to one type:
134
165
 
135
166
  ```css
136
167
  .storybook-addon-md-callout[data-callout='caution'] {
137
- background: light-dark(#ffebe9, #2d1214);
138
- border-radius: 0.375rem;
168
+ --sbmd-callout-background: light-dark(#ffebe9, #2d1214);
139
169
  }
140
170
  ```
141
171
 
@@ -17,12 +17,13 @@ const storyExtensions = [
17
17
  ];
18
18
  const slash = (value) => value.split(path.sep).join("/");
19
19
  const fail = (source, message) => /* @__PURE__ */ new Error(`[storybook-addon-md] ${source}: ${message}`);
20
- async function localFile(file, root, source, kind) {
20
+ async function localFile(file, root, source, kind, directories = false) {
21
21
  try {
22
22
  const actual = await realpath(file);
23
23
  const relative = path.relative(await realpath(root), actual);
24
24
  if (relative.startsWith(`..${path.sep}`) || relative === ".." || path.isAbsolute(relative)) throw fail(source, `${kind} must be inside root: ${file}`);
25
- if (!(await stat(actual)).isFile()) throw new Error("not a file");
25
+ const info = await stat(actual);
26
+ if (!info.isFile() && !(directories && info.isDirectory())) throw new Error("not a file");
26
27
  return file;
27
28
  } catch (error) {
28
29
  if (error instanceof Error && error.message.startsWith("[storybook-addon-md]")) throw error;
@@ -85,12 +86,20 @@ async function resolveStoryAssociations(file, metadata, root) {
85
86
  if (matches.length !== 1) throw Object.assign(fail(file, matches.length ? "ambiguous sibling stories; set stories explicitly" : "missing sibling story for .metadata.md; set stories explicitly or rename the Markdown file"), { dependencies: storyExtensions.map((extension) => `${base}.stories.${extension}`) });
86
87
  return [await localFile(matches[0], root, file, "story reference")];
87
88
  }
88
- async function resolveAssets(body, file, root, standalone = false) {
89
+ const imageDefinitions = (tree) => {
90
+ const identifiers = /* @__PURE__ */ new Set();
91
+ visit(tree, "imageReference", (node) => {
92
+ identifiers.add(node.identifier);
93
+ });
94
+ return identifiers;
95
+ };
96
+ async function resolveAssets(body, file, root, standalone = false, resolveLink) {
89
97
  const tree = markdown.parse(body);
90
98
  const nodes = [];
91
99
  visit(tree, (node) => {
92
100
  if (node.type === "image" || node.type === "link" || node.type === "definition") nodes.push(node);
93
101
  });
102
+ const images = imageDefinitions(tree);
94
103
  const assets = [];
95
104
  for (const node of nodes) {
96
105
  const url = node.url;
@@ -102,7 +111,13 @@ async function resolveAssets(body, file, root, standalone = false) {
102
111
  } catch {
103
112
  throw fail(file, `invalid local URL: ${url}`);
104
113
  }
105
- const asset = await localFile(path.resolve(path.dirname(file), decoded), root, file, "local asset");
114
+ const target = path.resolve(path.dirname(file), decoded);
115
+ const link = node.type === "image" || node.type === "definition" && images.has(node.identifier) || !resolveLink ? void 0 : await resolveLink(target, file);
116
+ if (link) {
117
+ node.url = `${link}${suffix}`;
118
+ continue;
119
+ }
120
+ const asset = await localFile(target, root, file, "local asset");
106
121
  const token = `SBMDASSET${assets.length}END`;
107
122
  if (body.includes(token)) throw fail(file, `reserved asset token in content: ${token}`);
108
123
  assets.push({
@@ -124,7 +139,7 @@ async function resolveAssets(body, file, root, standalone = false) {
124
139
  heading
125
140
  };
126
141
  }
127
- async function discover({ root, patterns, output }) {
142
+ async function discover({ root, patterns, output, links, docsName }) {
128
143
  if (!Array.isArray(patterns) || !patterns.length || patterns.some((item) => typeof item !== "string" || !item || path.isAbsolute(item) || item.split("/").includes(".."))) throw fail(root, "patterns must be a non-empty array of globs relative to root");
129
144
  const files = await glob(patterns, {
130
145
  cwd: root,
@@ -139,9 +154,8 @@ async function discover({ root, patterns, output }) {
139
154
  ...path.relative(root, output).startsWith("..") ? [] : [`${slash(path.relative(root, output))}/**`]
140
155
  ]
141
156
  });
142
- return Promise.all(files.sort().filter((file) => file.endsWith(".md")).map(async (file) => {
157
+ const documents = await Promise.all(files.sort().filter((file) => file.endsWith(".md")).map(async (file) => {
143
158
  const { original, body, metadata, stories } = await readMarkdown(file, root);
144
- const content = await resolveAssets(body, file, root, !stories.length);
145
159
  return {
146
160
  file,
147
161
  original,
@@ -149,10 +163,34 @@ async function discover({ root, patterns, output }) {
149
163
  source: slash(path.relative(root, file)),
150
164
  title: metadata.title ?? `Documentation/${slash(path.relative(root, file)).replace(/\.md$/, "")}`,
151
165
  metadata,
152
- stories,
153
- ...content
166
+ stories
154
167
  };
155
168
  }));
169
+ const resolveLink = links ? await linkResolver(links, documents, root, docsName) : void 0;
170
+ return Promise.all(documents.map(async (document) => ({
171
+ ...document,
172
+ ...await resolveAssets(document.body, document.file, root, !document.stories.length, resolveLink)
173
+ })));
174
+ }
175
+ async function docsId(document, root, docsName, titles) {
176
+ const { toId } = await import("storybook/internal/csf");
177
+ if (!document.stories.length) return toId(document.title, docsName);
178
+ const [story] = document.stories;
179
+ if (!titles.has(story)) titles.set(story, import("storybook/internal/csf-tools").then(({ readCsf }) => readCsf(story, { makeTitle: (title) => title })).then((csf) => csf.parse().meta).catch(() => void 0));
180
+ const meta = await titles.get(story);
181
+ return meta?.title ? toId(meta.id ?? meta.title, docsName) : `story:${slash(path.relative(root, story))}`;
182
+ }
183
+ async function linkResolver(links, documents, root, docsName = "Docs") {
184
+ const paths = /* @__PURE__ */ new Map();
185
+ const titles = /* @__PURE__ */ new Map();
186
+ if (links.documents !== false) for (const document of documents) paths.set(document.file, `?path=/docs/${await docsId(document, root, docsName, titles)}`);
187
+ const repository = links.repository?.replace(/\/+$/, "");
188
+ return async (target, source) => {
189
+ const page = paths.get(target);
190
+ if (page || !repository) return page;
191
+ await localFile(target, root, source, "link target", true);
192
+ return `${repository}/${slash(path.relative(root, target))}`;
193
+ };
156
194
  }
157
195
  async function readMarkdown(file, root) {
158
196
  root = path.resolve(root);
@@ -1,4 +1,4 @@
1
- import { t as MarkdownOptions } from "./index-BzM0tjFM.js";
1
+ import { n as MarkdownOptions } from "./index-D3ynUvxB.js";
2
2
  //#region src/content.d.ts
3
3
  interface ContentOptions extends MarkdownOptions {
4
4
  root: string;
@@ -17,7 +17,7 @@ declare function parseMarkdown(text: string, source: string): {
17
17
  metadata: Frontmatter;
18
18
  };
19
19
  declare function resolveStoryAssociations(file: string, metadata: Frontmatter, root: string): Promise<string[]>;
20
- declare function discover({ root, patterns, output }: ContentOptions): Promise<{
20
+ declare function discover({ root, patterns, output, links, docsName }: ContentOptions): Promise<{
21
21
  markdown: string;
22
22
  assets: {
23
23
  file: string;
@@ -1,4 +1,8 @@
1
1
  //#region src/index.d.ts
2
+ interface LinkOptions {
3
+ documents?: boolean;
4
+ repository?: string;
5
+ }
2
6
  interface MarkdownOptions {
3
7
  tagFields?: string[];
4
8
  manifests?: boolean;
@@ -7,6 +11,7 @@ interface MarkdownOptions {
7
11
  stylesheet?: string;
8
12
  root?: string;
9
13
  presentation?: string;
14
+ links?: LinkOptions;
10
15
  }
11
16
  //#endregion
12
- export { MarkdownOptions as t };
17
+ export { MarkdownOptions as n, LinkOptions as t };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { t as MarkdownOptions } from "./index-BzM0tjFM.js";
2
- export { MarkdownOptions };
1
+ import { n as MarkdownOptions, t as LinkOptions } from "./index-D3ynUvxB.js";
2
+ export { LinkOptions, MarkdownOptions };
package/dist/node.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as readMarkdown, i as parseMarkdown, o as resolveStoryAssociations, r as Frontmatter } from "./content-CKQsa_HK.js";
1
+ import { a as readMarkdown, i as parseMarkdown, o as resolveStoryAssociations, r as Frontmatter } from "./content-CwjmOiRK.js";
2
2
  export { type Frontmatter, parseMarkdown, readMarkdown, resolveStoryAssociations };
package/dist/node.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as readMarkdown, i as parseMarkdown, o as resolveStoryAssociations } from "./content-CTcZAfoF.js";
1
+ import { a as readMarkdown, i as parseMarkdown, o as resolveStoryAssociations } from "./content-BrghNgvp.js";
2
2
  export { parseMarkdown, readMarkdown, resolveStoryAssociations };
package/dist/preset.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { t as MarkdownOptions } from "./index-BzM0tjFM.js";
2
- import { n as DiscoveredDocument, t as ContentOptions } from "./content-CKQsa_HK.js";
1
+ import { n as MarkdownOptions } from "./index-D3ynUvxB.js";
2
+ import { n as DiscoveredDocument, t as ContentOptions } from "./content-CwjmOiRK.js";
3
3
  import { StorybookConfigRaw } from "storybook/internal/types";
4
4
  import { UserConfig } from "vite";
5
5
  //#region src/preset.d.ts
package/dist/preset.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as fail, r as localFile, s as slash, t as discover } from "./content-CTcZAfoF.js";
1
+ import { n as fail, r as localFile, s as slash, t as discover } from "./content-BrghNgvp.js";
2
2
  import path from "node:path";
3
3
  import { createHash } from "node:crypto";
4
4
  import { watch } from "chokidar";
@@ -133,6 +133,8 @@ function settings(options) {
133
133
  const root = path.resolve(configDir, options.root ?? "..");
134
134
  const generatedDir = options.generatedDir ?? "storybook-markdown-generated";
135
135
  if (options.tagFields !== void 0 && (!Array.isArray(options.tagFields) || options.tagFields.some((field) => typeof field !== "string" || !/^[a-z][a-z0-9_-]*$/.test(field)))) throw fail(configDir, "tagFields must be an array of lowercase frontmatter field names");
136
+ const { links } = options;
137
+ if (links !== void 0 && (typeof links !== "object" || links === null || Array.isArray(links) || Object.keys(links).some((key) => !["documents", "repository"].includes(key)) || links.documents !== void 0 && typeof links.documents !== "boolean" || links.repository !== void 0 && (typeof links.repository !== "string" || !/^https?:\/\/\S+$/.test(links.repository)))) throw fail(configDir, "links must be an object with an optional documents boolean and an optional repository URL");
136
138
  if ("exclude" in options) throw fail(configDir, "exclude has been removed; use negative globs in patterns, such as !docs/private/**");
137
139
  if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(generatedDir) || ["node_modules", "storybook-static"].includes(generatedDir)) throw fail(configDir, "generatedDir must be a visible folder name containing only letters, digits, hyphens or underscores");
138
140
  return {
@@ -141,7 +143,11 @@ function settings(options) {
141
143
  patterns: options.patterns,
142
144
  tagFields: options.tagFields,
143
145
  stylesheet: options.stylesheet ? path.resolve(root, options.stylesheet) : void 0,
144
- presentation: options.presentation ? path.resolve(root, options.presentation) : void 0
146
+ presentation: options.presentation ? path.resolve(root, options.presentation) : void 0,
147
+ links: links ? {
148
+ documents: links.documents ?? true,
149
+ repository: links.repository?.replace(/\/+$/, "")
150
+ } : void 0
145
151
  };
146
152
  }
147
153
  async function stories(existing = [], options) {
package/dist/runtime.js CHANGED
@@ -34,6 +34,18 @@ function Blockquote({ children, ...props }) {
34
34
  }), content]
35
35
  });
36
36
  }
37
+ function Anchor({ href, target, ...props }) {
38
+ if (!href?.startsWith("?path=")) return /* @__PURE__ */ jsx("a", {
39
+ ...props,
40
+ href,
41
+ target
42
+ });
43
+ return /* @__PURE__ */ jsx("a", {
44
+ ...props,
45
+ href: new URL(href, new URL("./", window.location.href)).href,
46
+ target: target ?? "_top"
47
+ });
48
+ }
37
49
  function DefaultMarkdownRenderer({ markdown }) {
38
50
  return /* @__PURE__ */ jsx(Markdown, {
39
51
  options: {
@@ -41,7 +53,7 @@ function DefaultMarkdownRenderer({ markdown }) {
41
53
  overrides: {
42
54
  code: CodeOrSourceMdx,
43
55
  ...HeadersMdx,
44
- a: "a",
56
+ a: Anchor,
45
57
  blockquote: Blockquote
46
58
  }
47
59
  },
package/dist/styles.css CHANGED
@@ -1,6 +1,10 @@
1
1
  .storybook-addon-md-page {
2
2
  font-family: var(--sbmd-font-family, var(--sbmd-native-font-family));
3
3
  color: var(--sbmd-color, var(--sbmd-native-color));
4
+ max-width: var(--sbmd-page-max-width, none);
5
+ margin: var(--sbmd-page-margin, 0);
6
+ padding: var(--sbmd-page-padding, 0);
7
+ background: var(--sbmd-page-background, transparent);
4
8
  }
5
9
 
6
10
  .sbdocs-content .storybook-addon-md :is(p, li, td, th, blockquote) {
@@ -65,11 +69,16 @@
65
69
  }
66
70
 
67
71
  .sbdocs-content .storybook-addon-md a {
68
- color: var(--sbmd-link-color, var(--sbmd-native-link-color));
72
+ color: var(--sbmd-link-color, var(--sbmd-accent-color, var(--sbmd-native-link-color)));
69
73
  text-decoration: var(--sbmd-link-decoration, none);
74
+ text-underline-offset: var(--sbmd-link-underline-offset, auto);
70
75
  overflow-wrap: anywhere;
71
76
  }
72
77
 
78
+ .sbdocs-content .storybook-addon-md a:hover {
79
+ text-decoration: var(--sbmd-link-hover-decoration, var(--sbmd-link-decoration, none));
80
+ }
81
+
73
82
  .sbdocs-content .storybook-addon-md blockquote {
74
83
  margin: var(--sbmd-block-spacing, 1rem) 0;
75
84
  padding: var(--sbmd-quote-padding, 0 0.9375rem);
@@ -77,45 +86,42 @@
77
86
  --sbmd-quote-border,
78
87
  0.25rem solid var(--sbmd-border-color, var(--sbmd-native-border-color))
79
88
  );
89
+ border-radius: var(--sbmd-radius, 0);
90
+ background: var(--sbmd-quote-background, transparent);
80
91
  }
81
92
 
82
93
  .sbdocs-content .storybook-addon-md-callout {
83
94
  margin: var(--sbmd-block-spacing, 1rem) 0;
84
95
  padding: var(--sbmd-callout-padding, 0.5rem 1rem);
85
- border-inline-start: var(
86
- --sbmd-quote-border,
87
- 0.25rem solid var(--sbmd-border-color, var(--sbmd-native-border-color))
88
- );
89
- border-inline-start-color: var(--sbmd-native-callout-color);
96
+ border-inline-start: var(--sbmd-callout-border, 0.25rem solid var(--sbmd-callout-accent));
97
+ border-radius: var(--sbmd-radius, 0);
98
+ background: var(--sbmd-callout-background, transparent);
90
99
  }
91
100
 
92
101
  .sbdocs-content .storybook-addon-md-callout[data-callout='note'] {
93
- --sbmd-native-callout-color: var(
94
- --sbmd-callout-note-color,
95
- var(--sbmd-native-callout-note-color)
96
- );
102
+ --sbmd-callout-accent: var(--sbmd-callout-note-color, var(--sbmd-native-callout-note-color));
97
103
  }
98
104
 
99
105
  .sbdocs-content .storybook-addon-md-callout[data-callout='tip'] {
100
- --sbmd-native-callout-color: var(--sbmd-callout-tip-color, var(--sbmd-native-callout-tip-color));
106
+ --sbmd-callout-accent: var(--sbmd-callout-tip-color, var(--sbmd-native-callout-tip-color));
101
107
  }
102
108
 
103
109
  .sbdocs-content .storybook-addon-md-callout[data-callout='important'] {
104
- --sbmd-native-callout-color: var(
110
+ --sbmd-callout-accent: var(
105
111
  --sbmd-callout-important-color,
106
112
  var(--sbmd-native-callout-important-color)
107
113
  );
108
114
  }
109
115
 
110
116
  .sbdocs-content .storybook-addon-md-callout[data-callout='warning'] {
111
- --sbmd-native-callout-color: var(
117
+ --sbmd-callout-accent: var(
112
118
  --sbmd-callout-warning-color,
113
119
  var(--sbmd-native-callout-warning-color)
114
120
  );
115
121
  }
116
122
 
117
123
  .sbdocs-content .storybook-addon-md-callout[data-callout='caution'] {
118
- --sbmd-native-callout-color: var(
124
+ --sbmd-callout-accent: var(
119
125
  --sbmd-callout-caution-color,
120
126
  var(--sbmd-native-callout-caution-color)
121
127
  );
@@ -123,7 +129,7 @@
123
129
 
124
130
  .sbdocs-content .storybook-addon-md .storybook-addon-md-callout-label {
125
131
  margin: 0 0 0.25rem;
126
- color: var(--sbmd-native-callout-color);
132
+ color: var(--sbmd-callout-accent);
127
133
  font-weight: var(--sbmd-callout-label-weight, 600);
128
134
  }
129
135
 
@@ -143,7 +149,7 @@
143
149
  .sbdocs-content .storybook-addon-md :not(pre) > code {
144
150
  padding: var(--sbmd-inline-code-padding, 0.1875rem 0.3125rem);
145
151
  border: 0.0625rem solid var(--sbmd-border-color, var(--sbmd-native-border-color));
146
- border-radius: var(--sbmd-code-radius, 0.1875rem);
152
+ border-radius: var(--sbmd-code-radius, var(--sbmd-radius, 0.1875rem));
147
153
  background: var(--sbmd-inline-code-background, var(--sbmd-native-code-background));
148
154
  color: inherit;
149
155
  font-size: var(--sbmd-inline-code-size, 0.9em);
@@ -153,7 +159,7 @@
153
159
 
154
160
  .sbdocs-content .storybook-addon-md pre {
155
161
  padding: var(--sbmd-code-padding, 1.25rem);
156
- border-radius: var(--sbmd-code-radius, 0.1875rem);
162
+ border-radius: var(--sbmd-code-radius, var(--sbmd-radius, 0.1875rem));
157
163
  overflow-x: auto;
158
164
  }
159
165
 
@@ -174,10 +180,17 @@
174
180
 
175
181
  .sbdocs-content .storybook-addon-md :is(th, td) {
176
182
  padding: var(--sbmd-table-cell-padding, 0.375rem 0.8125rem);
177
- border: 0.0625rem solid var(--sbmd-border-color, var(--sbmd-native-border-color));
183
+ border: var(
184
+ --sbmd-table-border,
185
+ 0.0625rem solid var(--sbmd-border-color, var(--sbmd-native-border-color))
186
+ );
178
187
  text-align: start;
179
188
  }
180
189
 
190
+ .sbdocs-content .storybook-addon-md th {
191
+ background: var(--sbmd-table-heading-background, transparent);
192
+ }
193
+
181
194
  .sbdocs-content .storybook-addon-md tr {
182
195
  background: transparent;
183
196
  }
@@ -190,6 +203,8 @@
190
203
  display: block;
191
204
  max-width: 100%;
192
205
  height: auto;
206
+ border: var(--sbmd-image-border, 0);
207
+ border-radius: var(--sbmd-radius, 0);
193
208
  }
194
209
 
195
210
  .sbdocs-content .storybook-addon-md hr {
@@ -200,6 +215,10 @@
200
215
  background: var(--sbmd-border-color, var(--sbmd-native-border-color));
201
216
  }
202
217
 
218
+ .sbdocs-content .storybook-addon-md input[type='checkbox'] {
219
+ accent-color: var(--sbmd-accent-color, auto);
220
+ }
221
+
203
222
  .sbdocs-content .storybook-addon-md-tags {
204
223
  display: flex;
205
224
  flex-wrap: wrap;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storybook-addon-md",
3
- "version": "0.6.0",
3
+ "version": "0.8.0",
4
4
  "description": "Discover ordinary Markdown and render it in Storybook Docs.",
5
5
  "keywords": [
6
6
  "docs",