@umami/shiso 1.6.0 → 1.7.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 +9 -0
- package/dist/chunks/App.js +48 -35
- package/docs.schema.json +7 -1
- package/package.json +1 -1
- package/src/components/Header.tsx +12 -2
- 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/lib/docs-config.ts +12 -2
- package/src/lib/site-model.ts +1 -0
- package/src/lib/types.ts +20 -2
- package/src/styles/global.css +27 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
All notable changes to `@umami/shiso` are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## Unreleased
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Link tabs: a navigation tab may define `href` instead of `groups`/`pages`/
|
|
11
|
+
`dropdowns` to link to a standalone page or external URL from the top
|
|
12
|
+
navigation. Internal hrefs route client-side and highlight when active;
|
|
13
|
+
external URLs open in a new browser tab.
|
|
14
|
+
|
|
6
15
|
## 2.0.0 - 2026-08-21
|
|
7
16
|
|
|
8
17
|
### Breaking
|
package/dist/chunks/App.js
CHANGED
|
@@ -9532,7 +9532,7 @@ function ScrollArea({ className, children, scrollbars = "vertical", ...props })
|
|
|
9532
9532
|
children: [
|
|
9533
9533
|
/* @__PURE__ */ jsx(ScrollAreaViewport, {
|
|
9534
9534
|
"data-slot": "scroll-area-viewport",
|
|
9535
|
-
className: "box-border h-full w-full max-w-full rounded-[inherit]
|
|
9535
|
+
className: cn("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", scrollbars === "both" ? "overscroll-contain" : hasHorizontalScrollbar ? "overscroll-x-contain" : "overscroll-y-contain"),
|
|
9536
9536
|
children: /* @__PURE__ */ jsx(ScrollAreaContent, {
|
|
9537
9537
|
"data-slot": "scroll-area-content",
|
|
9538
9538
|
className: cn("box-border min-w-0", hasHorizontalScrollbar ? "w-max min-w-full max-w-none" : "w-full max-w-full"),
|
|
@@ -10969,7 +10969,7 @@ function AccordionItem({ className, ...props }) {
|
|
|
10969
10969
|
}
|
|
10970
10970
|
function AccordionTrigger({ className, children, ...props }) {
|
|
10971
10971
|
return /* @__PURE__ */ jsx(AccordionHeader, {
|
|
10972
|
-
className: "
|
|
10972
|
+
className: "flex",
|
|
10973
10973
|
children: /* @__PURE__ */ jsxs(AccordionTrigger$1, {
|
|
10974
10974
|
"data-slot": "accordion-trigger",
|
|
10975
10975
|
className: cn("group/accordion-trigger relative flex flex-1 items-start justify-between rounded-lg border border-transparent py-2.5 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:after:border-ring aria-disabled:pointer-events-none aria-disabled:opacity-50 **:data-[slot=accordion-trigger-icon]:ml-auto **:data-[slot=accordion-trigger-icon]:size-4 **:data-[slot=accordion-trigger-icon]:text-muted-foreground", className),
|
|
@@ -11004,11 +11004,11 @@ function AccordionContent({ className, children, ...props }) {
|
|
|
11004
11004
|
//#region src/components/docs/styles.ts
|
|
11005
11005
|
/** Shared Tailwind utility groups for the built-in MDX components. */
|
|
11006
11006
|
const styles = {
|
|
11007
|
-
accordion: "my-3",
|
|
11008
|
-
accordionGroup: "my-4
|
|
11009
|
-
accordionItem: "
|
|
11010
|
-
accordionTrigger: "
|
|
11011
|
-
accordionContent: "
|
|
11007
|
+
accordion: "my-3 [&_h3:has(>[data-slot=accordion-trigger])]:m-0",
|
|
11008
|
+
accordionGroup: "my-4 [&_h3:has(>[data-slot=accordion-trigger])]:m-0",
|
|
11009
|
+
accordionItem: "",
|
|
11010
|
+
accordionTrigger: "text-foreground",
|
|
11011
|
+
accordionContent: "text-muted-foreground [&_p]:mt-0 [&_p:last-child]:mb-0",
|
|
11012
11012
|
expandableTrigger: "items-center px-4 py-3 text-sm font-normal text-foreground hover:no-underline",
|
|
11013
11013
|
callout: "my-4 flex items-start gap-3 rounded-lg border px-3 py-2.5 text-sm leading-6",
|
|
11014
11014
|
calloutIcon: "flex h-6 w-5 shrink-0 items-center justify-center [&_svg]:block [&_svg]:size-4",
|
|
@@ -11021,15 +11021,15 @@ const styles = {
|
|
|
11021
11021
|
tip: "border-green-300 bg-green-50 text-green-900 dark:border-green-400/40 dark:bg-green-400/10 dark:text-green-300",
|
|
11022
11022
|
check: "border-emerald-300 bg-emerald-50 text-emerald-900 dark:border-emerald-400/40 dark:bg-emerald-400/10 dark:text-emerald-300",
|
|
11023
11023
|
danger: "border-destructive/40 bg-destructive/10 text-destructive",
|
|
11024
|
-
card: "block h-full gap-0 rounded-lg border border-border bg-transparent p-4 text-base ring-0 hover:border-
|
|
11025
|
-
cardHorizontal: "[&_[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",
|
|
11024
|
+
card: "block h-full gap-0 rounded-lg border border-border bg-transparent p-4 text-base ring-0 hover:border-primary",
|
|
11025
|
+
cardHorizontal: "[&_[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",
|
|
11026
11026
|
cardTyped: "[&_[data-slot=card-title]]:text-inherit [&_[data-slot=card-body]]:text-inherit [&_[data-slot=card-cta]]:text-inherit [&_[data-slot=card-arrow]]:text-inherit",
|
|
11027
11027
|
cardImageLayout: "p-0 [&_[data-slot=card-inner]]:p-6",
|
|
11028
11028
|
cardImage: "block aspect-video w-full border-border border-b object-cover",
|
|
11029
11029
|
cardInner: "flex items-start justify-between gap-3",
|
|
11030
11030
|
cardMain: "flex min-w-0 flex-col gap-2",
|
|
11031
|
-
cardHeader: "flex items-
|
|
11032
|
-
cardIcon: "inline-flex shrink-0 items-center justify-center [&_img]:size-
|
|
11031
|
+
cardHeader: "flex flex-col items-start gap-3",
|
|
11032
|
+
cardIcon: "inline-flex shrink-0 items-center justify-center [&_img]:size-6 [&_svg]:size-6",
|
|
11033
11033
|
cardTitle: "text-base font-semibold text-foreground",
|
|
11034
11034
|
cardBody: "text-muted-foreground [&>:first-child]:mt-0 [&>:last-child]:mb-0",
|
|
11035
11035
|
cardCta: "flex shrink-0 items-center gap-[0.35rem] text-[0.9rem] font-medium text-muted-foreground",
|
|
@@ -11493,12 +11493,12 @@ function CardContent({ className, ...props }) {
|
|
|
11493
11493
|
//#endregion
|
|
11494
11494
|
//#region src/components/docs/Card.tsx
|
|
11495
11495
|
const CARD_TYPE_ICONS = {
|
|
11496
|
-
note: /* @__PURE__ */ jsx(Info$1, { size:
|
|
11497
|
-
info: /* @__PURE__ */ jsx(Info$1, { size:
|
|
11498
|
-
warning: /* @__PURE__ */ jsx(TriangleAlert, { size:
|
|
11499
|
-
tip: /* @__PURE__ */ jsx(Lightbulb, { size:
|
|
11500
|
-
check: /* @__PURE__ */ jsx(Check$1, { size:
|
|
11501
|
-
danger: /* @__PURE__ */ jsx(CircleAlert, { size:
|
|
11496
|
+
note: /* @__PURE__ */ jsx(Info$1, { size: 24 }),
|
|
11497
|
+
info: /* @__PURE__ */ jsx(Info$1, { size: 24 }),
|
|
11498
|
+
warning: /* @__PURE__ */ jsx(TriangleAlert, { size: 24 }),
|
|
11499
|
+
tip: /* @__PURE__ */ jsx(Lightbulb, { size: 24 }),
|
|
11500
|
+
check: /* @__PURE__ */ jsx(Check$1, { size: 24 }),
|
|
11501
|
+
danger: /* @__PURE__ */ jsx(CircleAlert, { size: 24 })
|
|
11502
11502
|
};
|
|
11503
11503
|
const CARD_TYPE_HOVER = {
|
|
11504
11504
|
note: "hover:border-border",
|
|
@@ -11509,7 +11509,7 @@ const CARD_TYPE_HOVER = {
|
|
|
11509
11509
|
danger: "hover:border-destructive/40"
|
|
11510
11510
|
};
|
|
11511
11511
|
function DocsCardContent({ title, icon, color, img, cta, arrow, type, children }) {
|
|
11512
|
-
const resolvedIcon = resolveIcon(icon,
|
|
11512
|
+
const resolvedIcon = resolveIcon(icon, 24) || (type ? CARD_TYPE_ICONS[type] : null);
|
|
11513
11513
|
return /* @__PURE__ */ jsxs(CardContent, {
|
|
11514
11514
|
className: "p-0",
|
|
11515
11515
|
children: [img ? /* @__PURE__ */ jsx("img", {
|
|
@@ -11524,6 +11524,7 @@ function DocsCardContent({ title, icon, color, img, cta, arrow, type, children }
|
|
|
11524
11524
|
"data-slot": "card-main",
|
|
11525
11525
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
11526
11526
|
className: styles.cardHeader,
|
|
11527
|
+
"data-slot": "card-header",
|
|
11527
11528
|
children: [resolvedIcon ? /* @__PURE__ */ jsx("span", {
|
|
11528
11529
|
className: `${styles.cardIcon} ${type ? "text-inherit" : "text-primary"}`,
|
|
11529
11530
|
style: color ? { color } : void 0,
|
|
@@ -22650,7 +22651,9 @@ function normalizeScope(name, source, anchors, resolveDocFile, docsPrefix) {
|
|
|
22650
22651
|
});
|
|
22651
22652
|
});
|
|
22652
22653
|
if (Array.isArray(tab.pages) && tab.pages.length) nodes.push(...collectPages(tab.pages, context, state));
|
|
22653
|
-
|
|
22654
|
+
const isLinkTab = typeof tab.href === "string" && !!tab.href;
|
|
22655
|
+
if (isLinkTab && state.pages.length !== pagesBefore) throw new Error(`Invalid docs config: tab "${tabLabel}" cannot define both "href" and page entries.`);
|
|
22656
|
+
if (!isLinkTab && state.pages.length === pagesBefore) throw new Error(`Invalid docs config: tab "${tabLabel}" does not contain any supported page entries.`);
|
|
22654
22657
|
treeByTab.set(tabId, nodes);
|
|
22655
22658
|
});
|
|
22656
22659
|
const pending = state.pages;
|
|
@@ -22723,13 +22726,15 @@ function normalizeScope(name, source, anchors, resolveDocFile, docsPrefix) {
|
|
|
22723
22726
|
tabs: tabs.map((tab, index) => {
|
|
22724
22727
|
const tabId = tabIds[index];
|
|
22725
22728
|
const firstPage = firstVisiblePageByTab.get(tabId);
|
|
22729
|
+
const href = typeof tab.href === "string" && tab.href ? tab.href : void 0;
|
|
22726
22730
|
return {
|
|
22727
22731
|
id: tabId,
|
|
22728
22732
|
label: tab.tab?.trim() || "",
|
|
22729
|
-
url: firstPage?.url || pageToUrl("index", docsPrefix),
|
|
22733
|
+
url: href || firstPage?.url || pageToUrl("index", docsPrefix),
|
|
22730
22734
|
icon: tab.icon,
|
|
22731
22735
|
presentation: tab.presentation || "tab",
|
|
22732
|
-
hidden: tab.hidden || void 0
|
|
22736
|
+
hidden: tab.hidden || void 0,
|
|
22737
|
+
link: href ? true : void 0
|
|
22733
22738
|
};
|
|
22734
22739
|
}),
|
|
22735
22740
|
showTabs,
|
|
@@ -23031,7 +23036,8 @@ function resolveSiteModel(config, docs, shiso) {
|
|
|
23031
23036
|
light: config.logo.light || config.logo.dark,
|
|
23032
23037
|
dark: config.logo.dark || config.logo.light,
|
|
23033
23038
|
href: config.logo.href,
|
|
23034
|
-
target: config.logo.href ? resolveLinkTarget(config.logo.href, config.logo.target) : void 0
|
|
23039
|
+
target: config.logo.href ? resolveLinkTarget(config.logo.href, config.logo.target) : void 0,
|
|
23040
|
+
invert: config.logo.invert === true
|
|
23035
23041
|
} : null;
|
|
23036
23042
|
return {
|
|
23037
23043
|
name: config.name?.trim() || void 0,
|
|
@@ -26344,6 +26350,13 @@ function TopNav({ docs, label }) {
|
|
|
26344
26350
|
}, item.href))
|
|
26345
26351
|
})] }, tab.id);
|
|
26346
26352
|
}
|
|
26353
|
+
if (tab.link && isExternalHref(tab.url)) return /* @__PURE__ */ jsxs("a", {
|
|
26354
|
+
href: tab.url,
|
|
26355
|
+
className: tabClass(tab),
|
|
26356
|
+
target: "_blank",
|
|
26357
|
+
rel: "noreferrer",
|
|
26358
|
+
children: [/* @__PURE__ */ jsx(ConfiguredIcon, { icon: tab.icon }), tab.label]
|
|
26359
|
+
}, tab.id);
|
|
26347
26360
|
return /* @__PURE__ */ jsxs(Link, {
|
|
26348
26361
|
to: tab.url,
|
|
26349
26362
|
className: tabClass(tab),
|
|
@@ -26426,19 +26439,19 @@ function Header({ site }) {
|
|
|
26426
26439
|
const brandHref = logo?.href || (hasRootStandalonePage ? "/" : docsHomeUrl);
|
|
26427
26440
|
const hasBrand = !!name || !!logo?.light || !!logo?.dark;
|
|
26428
26441
|
const brandClassName = "inline-flex items-center gap-2 text-xl font-bold text-foreground tracking-[-0.03em]";
|
|
26429
|
-
const brandContent = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
26430
|
-
logo
|
|
26431
|
-
|
|
26432
|
-
|
|
26433
|
-
|
|
26434
|
-
|
|
26435
|
-
|
|
26436
|
-
|
|
26437
|
-
|
|
26438
|
-
|
|
26439
|
-
|
|
26440
|
-
|
|
26441
|
-
] });
|
|
26442
|
+
const brandContent = /* @__PURE__ */ jsxs(Fragment, { children: [logo?.invert && logo.light ? /* @__PURE__ */ jsx("img", {
|
|
26443
|
+
src: logo.light,
|
|
26444
|
+
alt: "",
|
|
26445
|
+
className: "h-6 w-auto dark:brightness-0 dark:invert"
|
|
26446
|
+
}) : /* @__PURE__ */ jsxs(Fragment, { children: [logo?.light ? /* @__PURE__ */ jsx("img", {
|
|
26447
|
+
src: logo.light,
|
|
26448
|
+
alt: "",
|
|
26449
|
+
className: "h-6 w-auto dark:hidden"
|
|
26450
|
+
}) : null, logo?.dark ? /* @__PURE__ */ jsx("img", {
|
|
26451
|
+
src: logo.dark,
|
|
26452
|
+
alt: "",
|
|
26453
|
+
className: "hidden h-6 w-auto dark:block"
|
|
26454
|
+
}) : null] }), name ? /* @__PURE__ */ jsx("span", { children: name }) : null] });
|
|
26442
26455
|
return /* @__PURE__ */ jsx("header", {
|
|
26443
26456
|
className: "sticky top-0 z-50 h-[var(--header-height)] shrink-0 border-border border-b bg-[color-mix(in_srgb,var(--background)_92%,transparent)] backdrop-blur-md",
|
|
26444
26457
|
children: /* @__PURE__ */ jsxs("div", {
|
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
|
}
|
|
@@ -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
|
@@ -69,8 +69,18 @@ export function Header({ site }: { site: SiteModel }) {
|
|
|
69
69
|
'inline-flex items-center gap-2 text-xl font-bold text-foreground tracking-[-0.03em]';
|
|
70
70
|
const brandContent = (
|
|
71
71
|
<>
|
|
72
|
-
{
|
|
73
|
-
|
|
72
|
+
{/* With `invert`, one logo serves both modes: a CSS filter forces it to
|
|
73
|
+
white in dark mode instead of swapping to a dark variant. */}
|
|
74
|
+
{logo?.invert && logo.light ? (
|
|
75
|
+
<img src={logo.light} alt="" className="h-6 w-auto dark:brightness-0 dark:invert" />
|
|
76
|
+
) : (
|
|
77
|
+
<>
|
|
78
|
+
{logo?.light ? <img src={logo.light} alt="" className="h-6 w-auto dark:hidden" /> : null}
|
|
79
|
+
{logo?.dark ? (
|
|
80
|
+
<img src={logo.dark} alt="" className="hidden h-6 w-auto dark:block" />
|
|
81
|
+
) : null}
|
|
82
|
+
</>
|
|
83
|
+
)}
|
|
74
84
|
{name ? <span>{name}</span> : null}
|
|
75
85
|
</>
|
|
76
86
|
);
|
|
@@ -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/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-model.ts
CHANGED
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". */
|
|
@@ -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;
|
package/src/styles/global.css
CHANGED
|
@@ -5,6 +5,32 @@
|
|
|
5
5
|
|
|
6
6
|
@custom-variant dark (&:where([data-theme="dark"], [data-theme="dark"] *));
|
|
7
7
|
|
|
8
|
+
/* tw-animate-css defines these keyframes against Radix-style height variables
|
|
9
|
+
(--radix-accordion-content-height etc.). Base UI exposes the panel height as
|
|
10
|
+
--accordion-panel-height instead, so the originals resolve to `auto` and the
|
|
11
|
+
open/close animation jumps. Redefining them inside @theme replaces the
|
|
12
|
+
originals in Tailwind's emitted output (a plain top-level @keyframes rule
|
|
13
|
+
would lose: Tailwind emits theme keyframes at the end of the stylesheet). */
|
|
14
|
+
@theme inline {
|
|
15
|
+
@keyframes accordion-down {
|
|
16
|
+
from {
|
|
17
|
+
height: 0;
|
|
18
|
+
}
|
|
19
|
+
to {
|
|
20
|
+
height: var(--accordion-panel-height);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@keyframes accordion-up {
|
|
25
|
+
from {
|
|
26
|
+
height: var(--accordion-panel-height);
|
|
27
|
+
}
|
|
28
|
+
to {
|
|
29
|
+
height: 0;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
8
34
|
@layer base {
|
|
9
35
|
*,
|
|
10
36
|
*::before,
|
|
@@ -77,7 +103,7 @@
|
|
|
77
103
|
margin-top: 1rem;
|
|
78
104
|
}
|
|
79
105
|
|
|
80
|
-
.docs-markdown :where(h2, h3, h4, h5, h6) {
|
|
106
|
+
.docs-markdown :where(h1, h2, h3, h4, h5, h6) {
|
|
81
107
|
font-family: var(--font-heading);
|
|
82
108
|
font-weight: var(--font-heading-weight, 600);
|
|
83
109
|
letter-spacing: -0.02em;
|