@umami/shiso 1.6.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.
- package/CHANGELOG.md +29 -0
- package/dist/chunks/App.js +1856 -20744
- package/dist/chunks/docs.js +18921 -0
- package/dist/components.js +3 -0
- package/dist/entry-client.js +2 -1
- package/dist/entry-server.js +7 -5
- package/docs.schema.json +8 -2
- package/package.json +5 -1
- package/scripts/build-runtime.mjs +1 -0
- package/scripts/check-package.mjs +2 -0
- package/scripts/generate-last-modified.mjs +2 -2
- package/src/components/Header.tsx +20 -4
- package/src/components/TopNav.tsx +18 -0
- package/src/components/docs/Card.tsx +8 -8
- package/src/components/docs/styles.ts +13 -11
- package/src/components/ui/accordion.tsx +1 -1
- package/src/components/ui/scroll-area.tsx +11 -1
- package/src/entry-server.tsx +7 -4
- package/src/lib/content.ts +10 -6
- package/src/lib/docs-config.ts +12 -2
- package/src/lib/site-config.ts +7 -1
- package/src/lib/site-model.ts +1 -0
- package/src/lib/standalone-pages.ts +3 -3
- package/src/lib/types.ts +24 -4
- package/src/pages/StandalonePage.tsx +12 -5
- package/src/styles/global.css +53 -27
- package/types/components.d.ts +217 -0
- package/vite.config.ts +30 -6
|
@@ -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 };
|
package/dist/entry-client.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
package/dist/entry-server.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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).
|
|
97
|
-
* "/content/docs/index.mdx", resolved against
|
|
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
|
@@ -66,6 +66,10 @@
|
|
|
66
66
|
"target": {
|
|
67
67
|
"allOf": [{ "$ref": "#/definitions/targetValue" }],
|
|
68
68
|
"description": "Browsing context for the logo link. Defaults from href."
|
|
69
|
+
},
|
|
70
|
+
"invert": {
|
|
71
|
+
"allOf": [{ "$ref": "#/definitions/booleanValue" }],
|
|
72
|
+
"description": "Render one logo and force it to white in dark mode with a CSS filter, instead of swapping to a dark variant. Best for monochrome logos."
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
}
|
|
@@ -95,7 +99,7 @@
|
|
|
95
99
|
},
|
|
96
100
|
"page": {
|
|
97
101
|
"allOf": [{ "$ref": "#/definitions/nonEmptyStringValue" }],
|
|
98
|
-
"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."
|
|
99
103
|
},
|
|
100
104
|
"title": {
|
|
101
105
|
"allOf": [{ "$ref": "#/definitions/stringValue" }],
|
|
@@ -814,7 +818,8 @@
|
|
|
814
818
|
"type": "object",
|
|
815
819
|
"allOf": [
|
|
816
820
|
{ "anyOf": [{ "required": ["$ref"] }, { "required": ["tab"] }] },
|
|
817
|
-
{ "anyOf": [{ "required": ["$ref"] }, { "required": ["groups"] }, { "required": ["pages"] }, { "required": ["dropdowns"] }] }
|
|
821
|
+
{ "anyOf": [{ "required": ["$ref"] }, { "required": ["groups"] }, { "required": ["pages"] }, { "required": ["dropdowns"] }, { "required": ["href"] }] },
|
|
822
|
+
{ "not": { "anyOf": [{ "required": ["href", "groups"] }, { "required": ["href", "pages"] }, { "required": ["href", "dropdowns"] }] } }
|
|
818
823
|
],
|
|
819
824
|
"additionalProperties": false,
|
|
820
825
|
"properties": {
|
|
@@ -823,6 +828,7 @@
|
|
|
823
828
|
"groups": { "$ref": "#/definitions/groupList" },
|
|
824
829
|
"pages": { "$ref": "#/definitions/pageItemList" },
|
|
825
830
|
"dropdowns": { "$ref": "#/definitions/dropdownList" },
|
|
831
|
+
"href": { "allOf": [{ "$ref": "#/definitions/nonEmptyStringValue" }], "description": "Link tab: navigates to this URL (a standalone page or external site) instead of owning docs pages. Cannot be combined with groups, pages, or dropdowns." },
|
|
826
832
|
"icon": { "allOf": [{ "$ref": "#/definitions/stringValue" }], "description": "Icon name shown beside the tab label." },
|
|
827
833
|
"hidden": { "allOf": [{ "$ref": "#/definitions/booleanValue" }], "description": "Hide the tab from the top navigation." }
|
|
828
834
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umami/shiso",
|
|
3
|
-
"version": "1.
|
|
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 {
|
|
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;
|
|
@@ -69,8 +75,18 @@ export function Header({ site }: { site: SiteModel }) {
|
|
|
69
75
|
'inline-flex items-center gap-2 text-xl font-bold text-foreground tracking-[-0.03em]';
|
|
70
76
|
const brandContent = (
|
|
71
77
|
<>
|
|
72
|
-
{
|
|
73
|
-
|
|
78
|
+
{/* With `invert`, one logo serves both modes: a CSS filter forces it to
|
|
79
|
+
white in dark mode instead of swapping to a dark variant. */}
|
|
80
|
+
{logo?.invert && logo.light ? (
|
|
81
|
+
<img src={logo.light} alt="" className="h-6 w-auto dark:brightness-0 dark:invert" />
|
|
82
|
+
) : (
|
|
83
|
+
<>
|
|
84
|
+
{logo?.light ? <img src={logo.light} alt="" className="h-6 w-auto dark:hidden" /> : null}
|
|
85
|
+
{logo?.dark ? (
|
|
86
|
+
<img src={logo.dark} alt="" className="hidden h-6 w-auto dark:block" />
|
|
87
|
+
) : null}
|
|
88
|
+
</>
|
|
89
|
+
)}
|
|
74
90
|
{name ? <span>{name}</span> : null}
|
|
75
91
|
</>
|
|
76
92
|
);
|
|
@@ -104,7 +120,7 @@ export function Header({ site }: { site: SiteModel }) {
|
|
|
104
120
|
{docs.showTabs ? <TopNav docs={docs} label={labels.sections} /> : null}
|
|
105
121
|
</div>
|
|
106
122
|
<div className="flex min-w-0 items-center gap-2 justify-self-end">
|
|
107
|
-
<Search config={search} labels={labels} />
|
|
123
|
+
{showSearch ? <Search config={search} labels={labels} /> : null}
|
|
108
124
|
{navbar?.links.map(link => (
|
|
109
125
|
<NavbarLinkItem key={link.href} link={link} />
|
|
110
126
|
))}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
DropdownMenuItem,
|
|
8
8
|
DropdownMenuTrigger,
|
|
9
9
|
} from '@/components/ui/dropdown-menu';
|
|
10
|
+
import { isExternalHref } from '@/lib/paths';
|
|
10
11
|
import { getStandalonePage } from '@/lib/site-config';
|
|
11
12
|
import type { DocsTab, LinkTarget, NavNode, NormalizedDocsConfig } from '@/lib/types';
|
|
12
13
|
import { cn } from '@/lib/utils';
|
|
@@ -112,6 +113,23 @@ export function TopNav({ docs, label }: { docs: NormalizedDocsConfig; label: str
|
|
|
112
113
|
);
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
// Link tabs may point outside the app; render external URLs as plain
|
|
117
|
+
// anchors so the browser handles them.
|
|
118
|
+
if (tab.link && isExternalHref(tab.url)) {
|
|
119
|
+
return (
|
|
120
|
+
<a
|
|
121
|
+
key={tab.id}
|
|
122
|
+
href={tab.url}
|
|
123
|
+
className={tabClass(tab)}
|
|
124
|
+
target="_blank"
|
|
125
|
+
rel="noreferrer"
|
|
126
|
+
>
|
|
127
|
+
<ConfiguredIcon icon={tab.icon} />
|
|
128
|
+
{tab.label}
|
|
129
|
+
</a>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
return (
|
|
116
134
|
<Link
|
|
117
135
|
key={tab.id}
|
|
@@ -28,12 +28,12 @@ export interface CardProps {
|
|
|
28
28
|
export type CardType = 'note' | 'info' | 'warning' | 'tip' | 'check' | 'danger';
|
|
29
29
|
|
|
30
30
|
const CARD_TYPE_ICONS: Record<CardType, ReactNode> = {
|
|
31
|
-
note: <InfoIcon size={
|
|
32
|
-
info: <InfoIcon size={
|
|
33
|
-
warning: <TriangleAlert size={
|
|
34
|
-
tip: <Lightbulb size={
|
|
35
|
-
check: <CheckIcon size={
|
|
36
|
-
danger: <CircleAlert size={
|
|
31
|
+
note: <InfoIcon size={24} />,
|
|
32
|
+
info: <InfoIcon size={24} />,
|
|
33
|
+
warning: <TriangleAlert size={24} />,
|
|
34
|
+
tip: <Lightbulb size={24} />,
|
|
35
|
+
check: <CheckIcon size={24} />,
|
|
36
|
+
danger: <CircleAlert size={24} />,
|
|
37
37
|
};
|
|
38
38
|
|
|
39
39
|
const CARD_TYPE_HOVER: Record<CardType, string> = {
|
|
@@ -55,14 +55,14 @@ function DocsCardContent({
|
|
|
55
55
|
type,
|
|
56
56
|
children,
|
|
57
57
|
}: Omit<CardProps, 'href'>) {
|
|
58
|
-
const resolvedIcon = resolveIcon(icon,
|
|
58
|
+
const resolvedIcon = resolveIcon(icon, 24) || (type ? CARD_TYPE_ICONS[type] : null);
|
|
59
59
|
|
|
60
60
|
return (
|
|
61
61
|
<CardContent className="p-0">
|
|
62
62
|
{img ? <img src={img} alt="" className={styles.cardImage} /> : null}
|
|
63
63
|
<div className={styles.cardInner} data-slot="card-inner">
|
|
64
64
|
<div className={styles.cardMain} data-slot="card-main">
|
|
65
|
-
<div className={styles.cardHeader}>
|
|
65
|
+
<div className={styles.cardHeader} data-slot="card-header">
|
|
66
66
|
{resolvedIcon ? (
|
|
67
67
|
<span
|
|
68
68
|
className={`${styles.cardIcon} ${type ? 'text-inherit' : 'text-primary'}`}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/** Shared Tailwind utility groups for the built-in MDX components. */
|
|
2
2
|
export const styles = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
/* Accordions keep the native shadcn look: no outer box, hairline separators,
|
|
4
|
+
small medium-weight triggers with hover underline. Only margins are added.
|
|
5
|
+
The trigger header is an <h3> and panel text renders as <p>, so the
|
|
6
|
+
.docs-markdown typography margins must be cancelled for those elements. */
|
|
7
|
+
accordion: 'my-3 [&_h3:has(>[data-slot=accordion-trigger])]:m-0',
|
|
8
|
+
accordionGroup: 'my-4 [&_h3:has(>[data-slot=accordion-trigger])]:m-0',
|
|
9
|
+
accordionItem: '',
|
|
10
|
+
accordionTrigger: 'text-foreground',
|
|
11
|
+
accordionContent: 'text-muted-foreground [&_p]:mt-0 [&_p:last-child]:mb-0',
|
|
10
12
|
expandableTrigger:
|
|
11
13
|
'items-center px-4 py-3 text-sm font-normal text-foreground hover:no-underline',
|
|
12
14
|
|
|
@@ -25,17 +27,17 @@ export const styles = {
|
|
|
25
27
|
'border-emerald-300 bg-emerald-50 text-emerald-900 dark:border-emerald-400/40 dark:bg-emerald-400/10 dark:text-emerald-300',
|
|
26
28
|
danger: 'border-destructive/40 bg-destructive/10 text-destructive',
|
|
27
29
|
|
|
28
|
-
card: 'block h-full gap-0 rounded-lg border border-border bg-transparent p-4 text-base ring-0 hover:border-
|
|
30
|
+
card: 'block h-full gap-0 rounded-lg border border-border bg-transparent p-4 text-base ring-0 hover:border-primary',
|
|
29
31
|
cardHorizontal:
|
|
30
|
-
'[&_[data-slot=card-inner]]:items-center [&_[data-slot=card-main]]:flex-row [&_[data-slot=card-main]]:items-center [&_[data-slot=card-main]]:gap-3 [&_[data-slot=card-body]]:m-0',
|
|
32
|
+
'[&_[data-slot=card-inner]]:items-center [&_[data-slot=card-main]]:flex-row [&_[data-slot=card-main]]:items-center [&_[data-slot=card-main]]:gap-3 [&_[data-slot=card-body]]:m-0 [&_[data-slot=card-header]]:flex-row [&_[data-slot=card-header]]:items-center [&_[data-slot=card-header]]:gap-2',
|
|
31
33
|
cardTyped:
|
|
32
34
|
'[&_[data-slot=card-title]]:text-inherit [&_[data-slot=card-body]]:text-inherit [&_[data-slot=card-cta]]:text-inherit [&_[data-slot=card-arrow]]:text-inherit',
|
|
33
35
|
cardImageLayout: 'p-0 [&_[data-slot=card-inner]]:p-6',
|
|
34
36
|
cardImage: 'block aspect-video w-full border-border border-b object-cover',
|
|
35
37
|
cardInner: 'flex items-start justify-between gap-3',
|
|
36
38
|
cardMain: 'flex min-w-0 flex-col gap-2',
|
|
37
|
-
cardHeader: 'flex items-
|
|
38
|
-
cardIcon: 'inline-flex shrink-0 items-center justify-center [&_img]:size-
|
|
39
|
+
cardHeader: 'flex flex-col items-start gap-3',
|
|
40
|
+
cardIcon: 'inline-flex shrink-0 items-center justify-center [&_img]:size-6 [&_svg]:size-6',
|
|
39
41
|
cardTitle: 'text-base font-semibold text-foreground',
|
|
40
42
|
cardBody: 'text-muted-foreground [&>:first-child]:mt-0 [&>:last-child]:mb-0',
|
|
41
43
|
cardCta:
|
|
@@ -24,7 +24,7 @@ function AccordionItem({ className, ...props }: AccordionPrimitive.Item.Props) {
|
|
|
24
24
|
|
|
25
25
|
function AccordionTrigger({ className, children, ...props }: AccordionPrimitive.Trigger.Props) {
|
|
26
26
|
return (
|
|
27
|
-
<AccordionPrimitive.Header className="
|
|
27
|
+
<AccordionPrimitive.Header className="flex">
|
|
28
28
|
<AccordionPrimitive.Trigger
|
|
29
29
|
data-slot="accordion-trigger"
|
|
30
30
|
className={cn(
|
|
@@ -22,7 +22,17 @@ function ScrollArea({ className, children, scrollbars = 'vertical', ...props }:
|
|
|
22
22
|
>
|
|
23
23
|
<ScrollAreaPrimitive.Viewport
|
|
24
24
|
data-slot="scroll-area-viewport"
|
|
25
|
-
className=
|
|
25
|
+
className={cn(
|
|
26
|
+
'box-border h-full w-full max-w-full rounded-[inherit] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1',
|
|
27
|
+
// Contain overscroll only on the managed axis; containing both axes
|
|
28
|
+
// would swallow vertical wheel events over horizontal-only scroll
|
|
29
|
+
// areas (e.g. code blocks) and block page scrolling.
|
|
30
|
+
scrollbars === 'both'
|
|
31
|
+
? 'overscroll-contain'
|
|
32
|
+
: hasHorizontalScrollbar
|
|
33
|
+
? 'overscroll-x-contain'
|
|
34
|
+
: 'overscroll-y-contain',
|
|
35
|
+
)}
|
|
26
36
|
>
|
|
27
37
|
<ScrollAreaPrimitive.Content
|
|
28
38
|
data-slot="scroll-area-content"
|
package/src/entry-server.tsx
CHANGED
|
@@ -36,15 +36,18 @@ export function getRoutes(): string[] {
|
|
|
36
36
|
export { getRedirects };
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* Source file for every
|
|
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).
|
|
42
|
-
* "/content/docs/index.mdx", resolved against
|
|
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
|
|
48
|
+
...standalonePages
|
|
49
|
+
.filter(page => !page.filePath.endsWith('.tsx'))
|
|
50
|
+
.map(page => ({ route: page.path, filePath: page.filePath })),
|
|
48
51
|
];
|
|
49
52
|
}
|
|
50
53
|
|
package/src/lib/content.ts
CHANGED
|
@@ -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.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
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 {
|
package/src/lib/docs-config.ts
CHANGED
|
@@ -665,7 +665,15 @@ function normalizeScope(
|
|
|
665
665
|
nodes.push(...collectPages(tab.pages, context, state));
|
|
666
666
|
}
|
|
667
667
|
|
|
668
|
-
|
|
668
|
+
const isLinkTab = typeof tab.href === 'string' && !!tab.href;
|
|
669
|
+
|
|
670
|
+
if (isLinkTab && state.pages.length !== pagesBefore) {
|
|
671
|
+
throw new Error(
|
|
672
|
+
`Invalid docs config: tab "${tabLabel}" cannot define both "href" and page entries.`,
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
if (!isLinkTab && state.pages.length === pagesBefore) {
|
|
669
677
|
throw new Error(
|
|
670
678
|
`Invalid docs config: tab "${tabLabel}" does not contain any supported page entries.`,
|
|
671
679
|
);
|
|
@@ -781,14 +789,16 @@ function normalizeScope(
|
|
|
781
789
|
const normalizedTabs = tabs.map((tab, index) => {
|
|
782
790
|
const tabId = tabIds[index];
|
|
783
791
|
const firstPage = firstVisiblePageByTab.get(tabId);
|
|
792
|
+
const href = typeof tab.href === 'string' && tab.href ? tab.href : undefined;
|
|
784
793
|
|
|
785
794
|
return {
|
|
786
795
|
id: tabId,
|
|
787
796
|
label: tab.tab?.trim() || '',
|
|
788
|
-
url: firstPage?.url || pageToUrl('index', docsPrefix),
|
|
797
|
+
url: href || firstPage?.url || pageToUrl('index', docsPrefix),
|
|
789
798
|
icon: tab.icon,
|
|
790
799
|
presentation: tab.presentation || 'tab',
|
|
791
800
|
hidden: tab.hidden || undefined,
|
|
801
|
+
link: href ? true : undefined,
|
|
792
802
|
};
|
|
793
803
|
});
|
|
794
804
|
|
package/src/lib/site-config.ts
CHANGED
|
@@ -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}`;
|
package/src/lib/site-model.ts
CHANGED
|
@@ -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
|
|
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
|
@@ -46,6 +46,8 @@ export interface TabItem {
|
|
|
46
46
|
groups?: GroupItem[];
|
|
47
47
|
pages?: PageItem[];
|
|
48
48
|
dropdowns?: DropdownItem[];
|
|
49
|
+
/** Link tab: navigates to this URL instead of owning docs pages. */
|
|
50
|
+
href?: string;
|
|
49
51
|
icon?: string;
|
|
50
52
|
hidden?: boolean;
|
|
51
53
|
/** Internal normalized presentation; not a docs.json field. */
|
|
@@ -99,7 +101,15 @@ export interface ThemeColors {
|
|
|
99
101
|
|
|
100
102
|
export type LogoOption =
|
|
101
103
|
| string
|
|
102
|
-
| {
|
|
104
|
+
| {
|
|
105
|
+
light?: string;
|
|
106
|
+
dark?: string;
|
|
107
|
+
href?: string;
|
|
108
|
+
target?: LinkTarget;
|
|
109
|
+
/** Force the logo to white in dark mode with a CSS filter instead of
|
|
110
|
+
* swapping to a dark variant. Best for monochrome logos. */
|
|
111
|
+
invert?: boolean;
|
|
112
|
+
};
|
|
103
113
|
|
|
104
114
|
/** Project-level settings supplied by shiso.config.ts. Mirrors the public
|
|
105
115
|
* shape exported from "@umami/shiso/config". */
|
|
@@ -179,7 +189,7 @@ export interface RedirectRule {
|
|
|
179
189
|
export interface StandalonePageItem {
|
|
180
190
|
/** Route path, starting with "/". "/" replaces the root redirect to docs. */
|
|
181
191
|
path: string;
|
|
182
|
-
/** File slug under content/pages, e.g. "home" for content/pages/home.
|
|
192
|
+
/** File slug under content/pages, e.g. "home" for content/pages/home.tsx. */
|
|
183
193
|
page: string;
|
|
184
194
|
/** Page title used in the document head. Frontmatter title wins. */
|
|
185
195
|
title?: string;
|
|
@@ -189,7 +199,7 @@ export interface StandalonePageItem {
|
|
|
189
199
|
export interface StandalonePage {
|
|
190
200
|
/** Base-relative route, e.g. "/" or "/about". */
|
|
191
201
|
path: string;
|
|
192
|
-
/** Module key of the MDX
|
|
202
|
+
/** Module key of the TSX, MDX, or Markdown file. */
|
|
193
203
|
filePath: string;
|
|
194
204
|
/** Config-level head-title override. */
|
|
195
205
|
title?: string;
|
|
@@ -337,6 +347,8 @@ export interface DocsTab {
|
|
|
337
347
|
icon?: string;
|
|
338
348
|
presentation: 'tab' | 'dropdown';
|
|
339
349
|
hidden?: boolean;
|
|
350
|
+
/** True for link tabs configured with `href` instead of docs pages. */
|
|
351
|
+
link?: boolean;
|
|
340
352
|
}
|
|
341
353
|
|
|
342
354
|
export interface NormalizedDocsPage {
|
|
@@ -494,7 +506,13 @@ export interface ThemeLabels {
|
|
|
494
506
|
|
|
495
507
|
export interface SiteModel {
|
|
496
508
|
name?: string;
|
|
497
|
-
logo: {
|
|
509
|
+
logo: {
|
|
510
|
+
light?: string;
|
|
511
|
+
dark?: string;
|
|
512
|
+
href?: string;
|
|
513
|
+
target?: LinkTarget;
|
|
514
|
+
invert?: boolean;
|
|
515
|
+
} | null;
|
|
498
516
|
navbar: NormalizedNavbar | null;
|
|
499
517
|
footer: NormalizedFooter | null;
|
|
500
518
|
banner: BannerConfig | null;
|
|
@@ -537,6 +555,8 @@ export interface DocFrontmatter {
|
|
|
537
555
|
title?: string;
|
|
538
556
|
description?: string;
|
|
539
557
|
noindex?: boolean;
|
|
558
|
+
/** Hide the header search control and disable its shortcut on this page. */
|
|
559
|
+
search?: false;
|
|
540
560
|
/** Overrides the site-wide `metadata.timestamp` setting for this page. */
|
|
541
561
|
timestamp?: boolean;
|
|
542
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
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
35
|
-
<div className="
|
|
35
|
+
{isComponentPage ? (
|
|
36
|
+
<div className="grow">
|
|
36
37
|
<Content />
|
|
37
38
|
</div>
|
|
38
|
-
|
|
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
|
);
|