@umami/shiso 1.7.0 → 1.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.
@@ -0,0 +1,3 @@
1
+ import { At as Info, Dt as Callout, Et as CardGroup, Ft as Button, It as Badge, Mt as Tip, Nt as Warning, Ot as Check, Pt as WarningBanner, Rt as Accordion, Tt as Card, _ as Tooltip, at as Tabs, bt as Columns, ct as ResponseField, dt as ParamField, ft as Icon, ht as Expandable, it as Tab, jt as Note, kt as Danger, lt as PropertiesTable, n as ZoomableImage, ot as Step, pt as Frame, st as Steps, ut as Param, xt as CodeGroup, yt as Column, zt as AccordionGroup } from "./chunks/docs.js";
2
+
3
+ export { Accordion, AccordionGroup, Badge, Button, Callout, Card, CardGroup, Check, CodeGroup, Column, Columns, Danger, Expandable, Frame, Icon, Info, Note, Param, ParamField, PropertiesTable, ResponseField, Step, Steps, Tab, Tabs, Tip, Tooltip, Warning, WarningBanner, ZoomableImage };
@@ -1,4 +1,5 @@
1
- import { g as BrowserRouter, m as BASE_URL, t as App } from "./chunks/App.js";
1
+ import { ir as BrowserRouter } from "./chunks/docs.js";
2
+ import { m as BASE_URL, t as App } from "./chunks/App.js";
2
3
  import { jsx } from "react/jsx-runtime";
3
4
 
4
5
  //#region src/entry-client.tsx
@@ -1,4 +1,5 @@
1
- import { _ as Router, a as docsSite, b as ABSOLUTE_URL_REGEX, c as getSeo, d as getDocModule, f as getLastModified, h as toAbsoluteUrl, i as docsHomeUrl, l as siteName, m as BASE_URL, n as buildHead, o as getLocaleByPathname, p as getScopeForPage, r as renderHeadToString, s as getRedirects, t as App, u as standalonePages, v as createPath, y as parsePath } from "./chunks/App.js";
1
+ import { cr as Router, fr as createPath, mr as ABSOLUTE_URL_REGEX, pr as parsePath } from "./chunks/docs.js";
2
+ import { a as docsSite, c as getSeo, d as getDocModule, f as getLastModified, h as toAbsoluteUrl, i as docsHomeUrl, l as siteName, m as BASE_URL, n as buildHead, o as getLocaleByPathname, p as getScopeForPage, r as renderHeadToString, s as getRedirects, t as App, u as standalonePages } from "./chunks/App.js";
2
3
  import * as React$1 from "react";
3
4
  import { jsx } from "react/jsx-runtime";
4
5
  import { renderToString } from "react-dom/server";
@@ -91,16 +92,17 @@ function getRoutes() {
91
92
  return [...docsSite.pages.map((page) => page.url), ...standalonePages.map((page) => page.path)];
92
93
  }
93
94
  /**
94
- * Source file for every routed page, so the prerenderer can publish raw
95
+ * Source file for every Markdown/MDX page, so the prerenderer can publish raw
95
96
  * markdown next to each HTML page (used by the contextual menu's copy/view
96
- * options and by AI tools). `filePath` is a module key like
97
- * "/content/docs/index.mdx", resolved against the project root.
97
+ * options and by AI tools). TSX standalone pages have no raw Markdown copy.
98
+ * `filePath` is a module key like "/content/docs/index.mdx", resolved against
99
+ * the project root.
98
100
  */
99
101
  function getMarkdownPages() {
100
102
  return [...docsSite.pages.map((page) => ({
101
103
  route: page.url,
102
104
  filePath: page.filePath
103
- })), ...standalonePages.map((page) => ({
105
+ })), ...standalonePages.filter((page) => !page.filePath.endsWith(".tsx")).map((page) => ({
104
106
  route: page.path,
105
107
  filePath: page.filePath
106
108
  }))];
package/docs.schema.json CHANGED
@@ -99,7 +99,7 @@
99
99
  },
100
100
  "page": {
101
101
  "allOf": [{ "$ref": "#/definitions/nonEmptyStringValue" }],
102
- "description": "File slug under content/pages, e.g. \"home\" for content/pages/home.mdx."
102
+ "description": "File slug under content/pages, e.g. \"home\" for content/pages/home.tsx, home.mdx, or home.md."
103
103
  },
104
104
  "title": {
105
105
  "allOf": [{ "$ref": "#/definitions/stringValue" }],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umami/shiso",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Open-source documentation framework for Markdown and MDX sites.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,6 +15,10 @@
15
15
  "types": "./types/client.d.ts",
16
16
  "default": "./dist/entry-client.js"
17
17
  },
18
+ "./components": {
19
+ "types": "./types/components.d.ts",
20
+ "default": "./dist/components.js"
21
+ },
18
22
  "./search": {
19
23
  "types": "./types/search.d.ts",
20
24
  "default": "./dist/search.js"
@@ -66,6 +66,7 @@ const bundle = await rolldown({
66
66
  input: {
67
67
  'entry-client': path.join(sourceRoot, 'entry-client.tsx'),
68
68
  'entry-server': path.join(sourceRoot, 'entry-server.tsx'),
69
+ components: path.join(sourceRoot, 'components/docs/index.ts'),
69
70
  search: path.join(sourceRoot, 'lib/search/provider.ts'),
70
71
  },
71
72
  external: isExternal,
@@ -7,6 +7,7 @@ for (const relativeFile of [
7
7
  'bin/shiso.mjs',
8
8
  'dist/entry-client.js',
9
9
  'dist/entry-server.js',
10
+ 'dist/components.js',
10
11
  'dist/search.js',
11
12
  'docs.schema.json',
12
13
  'scripts/lib/mdast.mjs',
@@ -14,6 +15,7 @@ for (const relativeFile of [
14
15
  'scripts/lib/slug.mjs',
15
16
  'src/styles/global.css',
16
17
  'types/client.d.ts',
18
+ 'types/components.d.ts',
17
19
  'types/search.d.ts',
18
20
  'vite.config.ts',
19
21
  ]) {
@@ -18,7 +18,7 @@ import { promisify } from 'node:util';
18
18
  const execFileAsync = promisify(execFile);
19
19
 
20
20
  const DEFAULT_ROOT = process.cwd();
21
- const CONTENT_EXTENSIONS = new Set(['.md', '.mdx']);
21
+ const CONTENT_EXTENSIONS = new Set(['.md', '.mdx', '.tsx']);
22
22
 
23
23
  async function collectContentFiles(dir, files = []) {
24
24
  let entries;
@@ -115,7 +115,7 @@ export function shisoLastModified(options = {}) {
115
115
  await generateLastModified(options);
116
116
  },
117
117
  async handleHotUpdate({ file }) {
118
- if (/\.(md|mdx)$/.test(file)) {
118
+ if (/\.(md|mdx|tsx)$/.test(file)) {
119
119
  await generateLastModified(options);
120
120
  }
121
121
  },
@@ -6,7 +6,12 @@ import { ThemeToggle } from '@/components/ThemeToggle';
6
6
  import { TopNav } from '@/components/TopNav';
7
7
  import { VersionSwitcher } from '@/components/VersionSwitcher';
8
8
  import { isExternalHref } from '@/lib/paths';
9
- import { docsHomeUrl, getScopeByPathname, hasRootStandalonePage } from '@/lib/site-config';
9
+ import {
10
+ docsHomeUrl,
11
+ getPageFrontmatter,
12
+ getScopeByPathname,
13
+ hasRootStandalonePage,
14
+ } from '@/lib/site-config';
10
15
  import type { NormalizedLink, SiteModel } from '@/lib/types';
11
16
 
12
17
  /**
@@ -62,6 +67,7 @@ export function Header({ site }: { site: SiteModel }) {
62
67
  const { pathname } = useLocation();
63
68
  // The header renders the navigation of whichever scope owns the current page.
64
69
  const docs = getScopeByPathname(pathname).docs;
70
+ const showSearch = getPageFrontmatter(pathname)?.search !== false;
65
71
  // The brand links to the standalone home page when one owns "/".
66
72
  const brandHref = logo?.href || (hasRootStandalonePage ? '/' : docsHomeUrl);
67
73
  const hasBrand = !!name || !!logo?.light || !!logo?.dark;
@@ -114,7 +120,7 @@ export function Header({ site }: { site: SiteModel }) {
114
120
  {docs.showTabs ? <TopNav docs={docs} label={labels.sections} /> : null}
115
121
  </div>
116
122
  <div className="flex min-w-0 items-center gap-2 justify-self-end">
117
- <Search config={search} labels={labels} />
123
+ {showSearch ? <Search config={search} labels={labels} /> : null}
118
124
  {navbar?.links.map(link => (
119
125
  <NavbarLinkItem key={link.href} link={link} />
120
126
  ))}
@@ -36,15 +36,18 @@ export function getRoutes(): string[] {
36
36
  export { getRedirects };
37
37
 
38
38
  /**
39
- * Source file for every routed page, so the prerenderer can publish raw
39
+ * Source file for every Markdown/MDX page, so the prerenderer can publish raw
40
40
  * markdown next to each HTML page (used by the contextual menu's copy/view
41
- * options and by AI tools). `filePath` is a module key like
42
- * "/content/docs/index.mdx", resolved against the project root.
41
+ * options and by AI tools). TSX standalone pages have no raw Markdown copy.
42
+ * `filePath` is a module key like "/content/docs/index.mdx", resolved against
43
+ * the project root.
43
44
  */
44
45
  export function getMarkdownPages(): { route: string; filePath: string }[] {
45
46
  return [
46
47
  ...docsSite.pages.map(page => ({ route: page.url, filePath: page.filePath })),
47
- ...standalonePages.map(page => ({ route: page.path, filePath: page.filePath })),
48
+ ...standalonePages
49
+ .filter(page => !page.filePath.endsWith('.tsx'))
50
+ .map(page => ({ route: page.path, filePath: page.filePath })),
48
51
  ];
49
52
  }
50
53
 
@@ -3,10 +3,9 @@ import { CONTENT_DIR, PAGES_DIR } from '@/lib/paths';
3
3
  import type { DocModule } from '@/lib/types';
4
4
 
5
5
  /**
6
- * Eagerly imports every content file at build time. Each module exports:
7
- * - default: the compiled MDX component
8
- * - frontmatter: parsed YAML frontmatter
9
- * - toc: heading anchors injected by the remark-toc plugin
6
+ * Eagerly imports every content file at build time. Markdown/MDX modules
7
+ * export a compiled component plus generated frontmatter and TOC values. TSX
8
+ * standalone pages export their component and may export frontmatter directly.
10
9
  *
11
10
  * Eager loading keeps server prerendering and client hydration in sync
12
11
  * without Suspense, at the cost of bundling all pages together.
@@ -16,7 +15,7 @@ import type { DocModule } from '@/lib/types';
16
15
  * lookup time instead. That also lets later versioned/localized content roots
17
16
  * (`content/v2`, `content/es`) work without touching this glob.
18
17
  */
19
- export const docModules = import.meta.glob('/content/**/*.{md,mdx}', {
18
+ export const docModules = import.meta.glob('/content/**/*.{md,mdx,tsx}', {
20
19
  eager: true,
21
20
  }) as Record<string, DocModule>;
22
21
 
@@ -35,7 +34,12 @@ export function resolveDocFile(fileSlug: string, contentDir = CONTENT_DIR): stri
35
34
  * under the fixed content/pages root.
36
35
  */
37
36
  export function resolvePageFile(fileSlug: string): string | undefined {
38
- return resolveDocFile(fileSlug, PAGES_DIR);
37
+ const candidates = [
38
+ `/${PAGES_DIR}/${fileSlug}.tsx`,
39
+ `/${PAGES_DIR}/${fileSlug}.mdx`,
40
+ `/${PAGES_DIR}/${fileSlug}.md`,
41
+ ];
42
+ return candidates.find(candidate => candidate in docModules);
39
43
  }
40
44
 
41
45
  export function getDocModule(filePath: string): DocModule | undefined {
@@ -1,6 +1,6 @@
1
1
  import shisoConfig from 'virtual:shiso-config';
2
2
  import rawConfig from 'virtual:shiso-docs-config';
3
- import { resolveDocFile, resolvePageFile } from '@/lib/content';
3
+ import { getDocModule, resolveDocFile, resolvePageFile } from '@/lib/content';
4
4
  import {
5
5
  assertDocsConfig,
6
6
  getDefaultScope,
@@ -126,6 +126,12 @@ export function getPageByPathname(pathname: string): NormalizedDocsPage | null {
126
126
  return getSitePageByPathname(docsSite, stripBase(pathname));
127
127
  }
128
128
 
129
+ /** Frontmatter for the docs or standalone page that owns a pathname. */
130
+ export function getPageFrontmatter(pathname: string) {
131
+ const page = getStandalonePage(pathname) || getPageByPathname(pathname);
132
+ return page ? getDocModule(page.filePath)?.frontmatter : undefined;
133
+ }
134
+
129
135
  export function getPageTitle(pageTitle?: string): string {
130
136
  if (pageTitle && siteName) {
131
137
  return `${pageTitle} – ${siteName}`;
@@ -28,7 +28,7 @@ function normalizePath(rawPath: unknown): string {
28
28
  throw invalid(`standalone page path "${value}" must not use wildcard patterns.`);
29
29
  }
30
30
 
31
- if (/\.mdx?$/i.test(value)) {
31
+ if (/\.(?:mdx?|tsx)$/i.test(value)) {
32
32
  throw invalid(
33
33
  `standalone page path "${value}" must be a route, not a file — drop the extension.`,
34
34
  );
@@ -48,7 +48,7 @@ function normalizePageSlug(rawSlug: unknown): string {
48
48
  .replace(/\\/g, '/')
49
49
  .replace(/^\/+/, '')
50
50
  .replace(/^pages\//, '')
51
- .replace(/\.mdx?$/, '')
51
+ .replace(/\.(?:mdx?|tsx)$/, '')
52
52
  .replace(/\/+$/, '') || 'index'
53
53
  );
54
54
  }
@@ -104,7 +104,7 @@ export function normalizeStandalonePages(
104
104
  if (!filePath) {
105
105
  throw new Error(
106
106
  `Missing standalone page file for "${fileSlug}": expected ` +
107
- `"content/pages/${fileSlug}.mdx" or ".md".`,
107
+ `"content/pages/${fileSlug}.tsx", ".mdx", or ".md".`,
108
108
  );
109
109
  }
110
110
 
package/src/lib/types.ts CHANGED
@@ -189,7 +189,7 @@ export interface RedirectRule {
189
189
  export interface StandalonePageItem {
190
190
  /** Route path, starting with "/". "/" replaces the root redirect to docs. */
191
191
  path: string;
192
- /** File slug under content/pages, e.g. "home" for content/pages/home.mdx. */
192
+ /** File slug under content/pages, e.g. "home" for content/pages/home.tsx. */
193
193
  page: string;
194
194
  /** Page title used in the document head. Frontmatter title wins. */
195
195
  title?: string;
@@ -199,7 +199,7 @@ export interface StandalonePageItem {
199
199
  export interface StandalonePage {
200
200
  /** Base-relative route, e.g. "/" or "/about". */
201
201
  path: string;
202
- /** Module key of the MDX file, e.g. "/content/pages/home.mdx". */
202
+ /** Module key of the TSX, MDX, or Markdown file. */
203
203
  filePath: string;
204
204
  /** Config-level head-title override. */
205
205
  title?: string;
@@ -555,6 +555,8 @@ export interface DocFrontmatter {
555
555
  title?: string;
556
556
  description?: string;
557
557
  noindex?: boolean;
558
+ /** Hide the header search control and disable its shortcut on this page. */
559
+ search?: false;
558
560
  /** Overrides the site-wide `metadata.timestamp` setting for this page. */
559
561
  timestamp?: boolean;
560
562
  /** Related pages rendered above the prev/next pager. */
@@ -7,8 +7,8 @@ import type { SiteModel, StandalonePage } from '@/lib/types';
7
7
 
8
8
  /**
9
9
  * A standalone (non-docs) page: site chrome from Layout (banner, header),
10
- * the MDX content at full container width — no sidebar, TOC, or pager — and
11
- * the footer. MDX components come from the app-level MDXProvider.
10
+ * content at full container width — no sidebar, TOC, or pager — and the
11
+ * footer. Markdown/MDX gets docs typography; TSX owns its presentation.
12
12
  */
13
13
  export function StandalonePageView({ page, site }: { page: StandalonePage; site: SiteModel }) {
14
14
  const { pathname } = useLocation();
@@ -28,14 +28,21 @@ export function StandalonePageView({ page, site }: { page: StandalonePage; site:
28
28
  }
29
29
 
30
30
  const Content = doc.default;
31
+ const isComponentPage = page.filePath.endsWith('.tsx');
31
32
 
32
33
  return (
33
34
  <div className="flex min-h-full flex-col">
34
- <article className="grow py-8">
35
- <div className="docs-markdown">
35
+ {isComponentPage ? (
36
+ <div className="grow">
36
37
  <Content />
37
38
  </div>
38
- </article>
39
+ ) : (
40
+ <article className="grow py-8">
41
+ <div className="docs-markdown">
42
+ <Content />
43
+ </div>
44
+ </article>
45
+ )}
39
46
  <Footer footer={site.footer} />
40
47
  </div>
41
48
  );
@@ -99,11 +99,11 @@
99
99
  color: color-mix(in srgb, var(--foreground) 25%, var(--muted-foreground));
100
100
  }
101
101
 
102
- .docs-markdown > * + * {
102
+ .docs-markdown > * + *:not(:where(.not-prose, .not-prose *)) {
103
103
  margin-top: 1rem;
104
104
  }
105
105
 
106
- .docs-markdown :where(h1, h2, h3, h4, h5, h6) {
106
+ .docs-markdown :where(h1, h2, h3, h4, h5, h6):not(:where(.not-prose, .not-prose *)) {
107
107
  font-family: var(--font-heading);
108
108
  font-weight: var(--font-heading-weight, 600);
109
109
  letter-spacing: -0.02em;
@@ -128,57 +128,57 @@
128
128
  opacity: 1;
129
129
  }
130
130
 
131
- .docs-markdown h2 {
131
+ .docs-markdown h2:not(:where(.not-prose, .not-prose *)) {
132
132
  margin-top: 2.25rem;
133
133
  margin-bottom: 1rem;
134
134
  font-size: 1.5rem;
135
135
  }
136
136
 
137
- .docs-markdown h3 {
137
+ .docs-markdown h3:not(:where(.not-prose, .not-prose *)) {
138
138
  margin-top: 1.75rem;
139
139
  margin-bottom: 0.75rem;
140
140
  font-size: 1.2rem;
141
141
  }
142
142
 
143
- .docs-markdown p {
143
+ .docs-markdown p:not(:where(.not-prose, .not-prose *)) {
144
144
  margin: 0.75rem 0;
145
145
  }
146
146
 
147
- .docs-markdown ul,
148
- .docs-markdown ol {
147
+ .docs-markdown ul:not(:where(.not-prose, .not-prose *)),
148
+ .docs-markdown ol:not(:where(.not-prose, .not-prose *)) {
149
149
  margin: 0.75rem 0;
150
150
  padding-left: 1.25rem;
151
151
  }
152
152
 
153
- .docs-markdown ul {
153
+ .docs-markdown ul:not(:where(.not-prose, .not-prose *)) {
154
154
  list-style: disc;
155
155
  }
156
156
 
157
- .docs-markdown ol {
157
+ .docs-markdown ol:not(:where(.not-prose, .not-prose *)) {
158
158
  list-style: decimal;
159
159
  }
160
160
 
161
- .docs-markdown li {
161
+ .docs-markdown li:not(:where(.not-prose, .not-prose *)) {
162
162
  margin: 0.35rem 0;
163
163
  }
164
164
 
165
- .docs-markdown :where(strong, b) {
165
+ .docs-markdown :where(strong, b):not(:where(.not-prose, .not-prose *)) {
166
166
  color: var(--foreground);
167
167
  font-weight: 600;
168
168
  }
169
169
 
170
- .docs-markdown a {
170
+ .docs-markdown a:not(:where(.not-prose, .not-prose *)) {
171
171
  color: var(--foreground);
172
172
  text-decoration: underline;
173
173
  text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
174
174
  text-underline-offset: 0.2em;
175
175
  }
176
176
 
177
- .docs-markdown a:hover {
177
+ .docs-markdown a:hover:not(:where(.not-prose, .not-prose *)) {
178
178
  text-decoration-color: currentColor;
179
179
  }
180
180
 
181
- .docs-markdown code:not(pre code) {
181
+ .docs-markdown code:not(pre code):not(:where(.not-prose, .not-prose *)) {
182
182
  color: var(--foreground);
183
183
  border-radius: var(--radius-sm);
184
184
  background: color-mix(in srgb, currentColor 4%, transparent);
@@ -188,7 +188,7 @@
188
188
 
189
189
  /* Styled to match the PropertiesTable component: rounded outer border,
190
190
  horizontal row separators only, and no header fill. */
191
- .docs-markdown table {
191
+ .docs-markdown table:not(:where(.not-prose, .not-prose *)) {
192
192
  width: 100%;
193
193
  margin: 1rem 0;
194
194
  font-size: 0.875rem;
@@ -198,12 +198,12 @@
198
198
  border-radius: var(--radius-lg);
199
199
  }
200
200
 
201
- .docs-markdown th {
201
+ .docs-markdown th:not(:where(.not-prose, .not-prose *)) {
202
202
  color: var(--foreground);
203
203
  }
204
204
 
205
- .docs-markdown th,
206
- .docs-markdown td {
205
+ .docs-markdown th:not(:where(.not-prose, .not-prose *)),
206
+ .docs-markdown td:not(:where(.not-prose, .not-prose *)) {
207
207
  border: none;
208
208
  border-bottom: 1px solid var(--border);
209
209
  padding: 0.6rem 0.75rem;
@@ -211,36 +211,36 @@
211
211
  vertical-align: top;
212
212
  }
213
213
 
214
- .docs-markdown th:first-child,
215
- .docs-markdown td:first-child {
214
+ .docs-markdown th:first-child:not(:where(.not-prose, .not-prose *)),
215
+ .docs-markdown td:first-child:not(:where(.not-prose, .not-prose *)) {
216
216
  padding-left: 1.5rem;
217
217
  }
218
218
 
219
- .docs-markdown th:last-child,
220
- .docs-markdown td:last-child {
219
+ .docs-markdown th:last-child:not(:where(.not-prose, .not-prose *)),
220
+ .docs-markdown td:last-child:not(:where(.not-prose, .not-prose *)) {
221
221
  padding-right: 1.5rem;
222
222
  }
223
223
 
224
- .docs-markdown th {
224
+ .docs-markdown th:not(:where(.not-prose, .not-prose *)) {
225
225
  background: transparent;
226
226
  font-weight: 600;
227
227
  }
228
228
 
229
- .docs-markdown tbody tr:last-child td {
229
+ .docs-markdown tbody tr:last-child td:not(:where(.not-prose, .not-prose *)) {
230
230
  border-bottom: none;
231
231
  }
232
232
 
233
- .docs-markdown table code:not(pre code) {
233
+ .docs-markdown table code:not(pre code):not(:where(.not-prose, .not-prose *)) {
234
234
  font-size: 0.75rem;
235
235
  }
236
236
 
237
- .docs-markdown blockquote {
237
+ .docs-markdown blockquote:not(:where(.not-prose, .not-prose *)) {
238
238
  border-left: 3px solid var(--input);
239
239
  padding-left: 1rem;
240
240
  color: var(--muted-foreground);
241
241
  }
242
242
 
243
- .docs-markdown hr {
243
+ .docs-markdown hr:not(:where(.not-prose, .not-prose *)) {
244
244
  border: none;
245
245
  border-top: 1px solid var(--border);
246
246
  margin: 2rem 0;