@umami/shiso 1.5.0 → 1.6.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.
@@ -1,9 +1,8 @@
1
+ import { n as highlightTerms } from "./search.js";
1
2
  import { resolveSearchProvider } from "../search.js";
2
3
  import * as React$1 from "react";
3
4
  import { Children, cloneElement, createContext, createElement, forwardRef, isValidElement, useCallback, useContext, useDebugValue, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore, useSyncExternalStore as useSyncExternalStore$1 } from "react";
4
- import "@fontsource/inter/400.css";
5
- import "@fontsource/inter/700.css";
6
- import "@fontsource/inter/800.css";
5
+ import "@fontsource-variable/inter/index.css";
7
6
  import "@fontsource/jetbrains-mono/400.css";
8
7
  import "highlight.js/styles/github.css";
9
8
  import "@umami/shiso/styles.css";
@@ -12892,18 +12891,18 @@ function PropertiesTable({ children }) {
12892
12891
  return /* @__PURE__ */ jsx("div", {
12893
12892
  className: "my-4 overflow-x-auto rounded-lg border border-border px-6 py-2",
12894
12893
  children: /* @__PURE__ */ jsxs("table", {
12895
- className: "m-0 min-w-[40rem] table-fixed border-collapse",
12894
+ className: "m-0 min-w-[40rem] table-fixed border-collapse border-0",
12896
12895
  children: [/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
12897
12896
  /* @__PURE__ */ jsx("th", {
12898
- className: "w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 pr-6 text-left text-sm font-semibold text-foreground",
12897
+ className: "w-1/5 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 pr-6 text-left text-sm font-semibold text-foreground",
12899
12898
  children: "Name"
12900
12899
  }),
12901
12900
  /* @__PURE__ */ jsx("th", {
12902
- className: "w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 pr-6 text-left text-sm font-semibold text-foreground",
12901
+ className: "w-1/4 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 pr-6 text-left text-sm font-semibold text-foreground",
12903
12902
  children: "Type"
12904
12903
  }),
12905
12904
  /* @__PURE__ */ jsx("th", {
12906
- className: "w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 text-left text-sm font-semibold text-foreground",
12905
+ className: "border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 text-left text-sm font-semibold text-foreground",
12907
12906
  children: "Description"
12908
12907
  })
12909
12908
  ] }) }), /* @__PURE__ */ jsx("tbody", { children: rows.map((row, index) => {
@@ -26097,6 +26096,11 @@ function renderSnippet(snippet) {
26097
26096
  children: part
26098
26097
  }, index) : part);
26099
26098
  }
26099
+ function renderWithQueryHighlight(text, query) {
26100
+ const terms = query.trim().split(/\s+/).filter(Boolean);
26101
+ if (!terms.length) return text;
26102
+ return renderSnippet(highlightTerms(text, terms));
26103
+ }
26100
26104
  /**
26101
26105
  * Provider-neutral search dialog. The selected provider and its index or
26102
26106
  * client are loaded on demand, so search stays out of the initial bundle.
@@ -26232,7 +26236,7 @@ function Search({ config, labels }) {
26232
26236
  onSelect: () => select(result),
26233
26237
  children: [/* @__PURE__ */ jsxs("div", {
26234
26238
  className: "text-[0.9rem] font-semibold text-foreground",
26235
- children: [result.page, result.heading ? ` › ${result.heading}` : ""]
26239
+ children: [renderWithQueryHighlight(result.page, query), result.heading ? /* @__PURE__ */ jsxs(Fragment, { children: [" › ", renderWithQueryHighlight(result.heading, query)] }) : null]
26236
26240
  }), result.snippet && /* @__PURE__ */ jsx("div", {
26237
26241
  className: "mt-[0.15rem] line-clamp-2 text-[0.8rem] text-muted-foreground",
26238
26242
  children: renderSnippet(result.snippet)
@@ -26828,13 +26832,13 @@ function DocContent({ page, doc, site }) {
26828
26832
  ...pagefindAttrs,
26829
26833
  children: [
26830
26834
  eyebrow && /* @__PURE__ */ jsx("div", {
26831
- className: "text-sm font-bold text-primary",
26835
+ className: "text-sm font-medium text-primary",
26832
26836
  children: eyebrow
26833
26837
  }),
26834
26838
  /* @__PURE__ */ jsxs("div", {
26835
26839
  className: "flex items-start justify-between gap-4",
26836
26840
  children: [title && /* @__PURE__ */ jsx("h1", {
26837
- className: "mt-2 text-4xl text-foreground leading-[1.2] tracking-[-0.03em] [font-family:var(--font-heading)] [font-weight:var(--font-heading-weight,800)]",
26841
+ className: "mt-2 text-4xl text-foreground leading-[1.2] tracking-[-0.03em] [font-family:var(--font-heading)] [font-weight:var(--font-heading-weight,700)]",
26838
26842
  children: title
26839
26843
  }), /* @__PURE__ */ jsx(ContextualMenu, {
26840
26844
  options: contextualOptions,
@@ -26842,7 +26846,7 @@ function DocContent({ page, doc, site }) {
26842
26846
  })]
26843
26847
  }),
26844
26848
  description && /* @__PURE__ */ jsx("p", {
26845
- className: "mt-1 mb-6 text-base text-muted-foreground",
26849
+ className: "mt-3 mb-8 text-lg text-muted-foreground leading-relaxed",
26846
26850
  children: description
26847
26851
  }),
26848
26852
  /* @__PURE__ */ jsx("div", {
@@ -1,66 +1,5 @@
1
- //#region src/lib/search.ts
2
- /**
3
- * Restricts records to the active scope. Records without a scope id (from
4
- * single-scope indexes or older caches) always pass.
5
- */
6
- function filterRecordsByScope(records, context) {
7
- if (!context?.scopeId) return records;
8
- return records.filter((record) => !record.scopeId || record.scopeId === context.scopeId);
9
- }
10
- const SNIPPET_RADIUS = 60;
11
- function escapeRegExp(value) {
12
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
- }
14
- /** Wraps every term occurrence in `<mark>` so the dialog can highlight it. */
15
- function highlightTerms(snippet, terms) {
16
- if (!terms.length) return snippet;
17
- const pattern = new RegExp([...terms].sort((a, b) => b.length - a.length).map(escapeRegExp).join("|"), "gi");
18
- return snippet.replace(pattern, "<mark>$&</mark>");
19
- }
20
- function makeSnippet(text, index, length, terms) {
21
- const start = Math.max(0, index - SNIPPET_RADIUS);
22
- const end = Math.min(text.length, index + length + SNIPPET_RADIUS);
23
- return highlightTerms(`${start > 0 ? "…" : ""}${text.slice(start, end).trim()}${end < text.length ? "…" : ""}`, terms);
24
- }
25
- function searchIndex(records, query, limit = 10) {
26
- const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
27
- if (!terms.length) return [];
28
- const results = [];
29
- for (const record of records) {
30
- const page = record.page.toLowerCase();
31
- const heading = (record.heading || "").toLowerCase();
32
- const text = record.text.toLowerCase();
33
- let score = 0;
34
- let snippetAt = -1;
35
- let snippetLength = 0;
36
- let matched = true;
37
- for (const term of terms) if (page.includes(term)) score += page === term ? 40 : 20;
38
- else if (heading.includes(term)) score += heading === term ? 30 : 15;
39
- else {
40
- const index = text.indexOf(term);
41
- if (index === -1) {
42
- matched = false;
43
- break;
44
- }
45
- score += 5;
46
- if (snippetAt === -1) {
47
- snippetAt = index;
48
- snippetLength = term.length;
49
- }
50
- }
51
- if (!matched || !score) continue;
52
- results.push({
53
- url: record.id ? `${record.url}#${record.id}` : record.url,
54
- page: record.page,
55
- heading: record.heading,
56
- snippet: snippetAt >= 0 ? makeSnippet(record.text, snippetAt, snippetLength, terms) : void 0,
57
- score
58
- });
59
- }
60
- return results.sort((a, b) => b.score - a.score).slice(0, limit);
61
- }
1
+ import { r as searchIndex, t as filterRecordsByScope } from "./search.js";
62
2
 
63
- //#endregion
64
3
  //#region src/lib/search/providers/local.ts
65
4
  /** Built-in provider backed by the section index generated during the build. */
66
5
  function createLocalSearchProvider(_options = {}) {
@@ -0,0 +1,64 @@
1
+ //#region src/lib/search.ts
2
+ /**
3
+ * Restricts records to the active scope. Records without a scope id (from
4
+ * single-scope indexes or older caches) always pass.
5
+ */
6
+ function filterRecordsByScope(records, context) {
7
+ if (!context?.scopeId) return records;
8
+ return records.filter((record) => !record.scopeId || record.scopeId === context.scopeId);
9
+ }
10
+ const SNIPPET_RADIUS = 60;
11
+ function escapeRegExp(value) {
12
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13
+ }
14
+ /** Wraps every term occurrence in `<mark>` so the dialog can highlight it. */
15
+ function highlightTerms(snippet, terms) {
16
+ if (!terms.length) return snippet;
17
+ const pattern = new RegExp([...terms].sort((a, b) => b.length - a.length).map(escapeRegExp).join("|"), "gi");
18
+ return snippet.replace(pattern, "<mark>$&</mark>");
19
+ }
20
+ function makeSnippet(text, index, length, terms) {
21
+ const start = Math.max(0, index - SNIPPET_RADIUS);
22
+ const end = Math.min(text.length, index + length + SNIPPET_RADIUS);
23
+ return highlightTerms(`${start > 0 ? "…" : ""}${text.slice(start, end).trim()}${end < text.length ? "…" : ""}`, terms);
24
+ }
25
+ function searchIndex(records, query, limit = 10) {
26
+ const terms = query.toLowerCase().split(/\s+/).filter(Boolean);
27
+ if (!terms.length) return [];
28
+ const results = [];
29
+ for (const record of records) {
30
+ const page = record.page.toLowerCase();
31
+ const heading = (record.heading || "").toLowerCase();
32
+ const text = record.text.toLowerCase();
33
+ let score = 0;
34
+ let snippetAt = -1;
35
+ let snippetLength = 0;
36
+ let matched = true;
37
+ for (const term of terms) if (page.includes(term)) score += page === term ? 40 : 20;
38
+ else if (heading.includes(term)) score += heading === term ? 30 : 15;
39
+ else {
40
+ const index = text.indexOf(term);
41
+ if (index === -1) {
42
+ matched = false;
43
+ break;
44
+ }
45
+ score += 5;
46
+ if (snippetAt === -1) {
47
+ snippetAt = index;
48
+ snippetLength = term.length;
49
+ }
50
+ }
51
+ if (!matched || !score) continue;
52
+ results.push({
53
+ url: record.id ? `${record.url}#${record.id}` : record.url,
54
+ page: record.page,
55
+ heading: record.heading,
56
+ snippet: snippetAt >= 0 ? makeSnippet(record.text, snippetAt, snippetLength, terms) : void 0,
57
+ score
58
+ });
59
+ }
60
+ return results.sort((a, b) => b.score - a.score).slice(0, limit);
61
+ }
62
+
63
+ //#endregion
64
+ export { highlightTerms as n, searchIndex as r, filterRecordsByScope as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umami/shiso",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Open-source documentation framework for Markdown and MDX sites.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,7 +64,7 @@
64
64
  },
65
65
  "dependencies": {
66
66
  "@base-ui/react": "^1.7.0",
67
- "@fontsource/inter": "^5.2.5",
67
+ "@fontsource-variable/inter": "^5.3.0",
68
68
  "@fontsource/jetbrains-mono": "^5.2.5",
69
69
  "@mdx-js/react": "^3.1.1",
70
70
  "@mdx-js/rollup": "^3.1.1",
package/src/App.tsx CHANGED
@@ -1,6 +1,4 @@
1
- import '@fontsource/inter/400.css';
2
- import '@fontsource/inter/700.css';
3
- import '@fontsource/inter/800.css';
1
+ import '@fontsource-variable/inter/index.css';
4
2
  import '@fontsource/jetbrains-mono/400.css';
5
3
  import 'highlight.js/styles/github.css';
6
4
  import '@umami/shiso/styles.css';
@@ -101,16 +101,18 @@ export function DocContent({ page, doc, site }: DocContentProps) {
101
101
 
102
102
  return (
103
103
  <article className="min-w-0 grow" {...pagefindAttrs}>
104
- {eyebrow && <div className="text-sm font-bold text-primary">{eyebrow}</div>}
104
+ {eyebrow && <div className="text-sm font-medium text-primary">{eyebrow}</div>}
105
105
  <div className="flex items-start justify-between gap-4">
106
106
  {title && (
107
- <h1 className="mt-2 text-4xl text-foreground leading-[1.2] tracking-[-0.03em] [font-family:var(--font-heading)] [font-weight:var(--font-heading-weight,800)]">
107
+ <h1 className="mt-2 text-4xl text-foreground leading-[1.2] tracking-[-0.03em] [font-family:var(--font-heading)] [font-weight:var(--font-heading-weight,700)]">
108
108
  {title}
109
109
  </h1>
110
110
  )}
111
111
  <ContextualMenu options={contextualOptions} labels={site.labels} />
112
112
  </div>
113
- {description && <p className="mt-1 mb-6 text-base text-muted-foreground">{description}</p>}
113
+ {description && (
114
+ <p className="mt-3 mb-8 text-lg text-muted-foreground leading-relaxed">{description}</p>
115
+ )}
114
116
  <div className="docs-markdown">
115
117
  <Content />
116
118
  </div>
@@ -11,7 +11,7 @@ import {
11
11
  CommandList,
12
12
  } from '@/components/ui/command';
13
13
  import { Dialog, DialogContent, DialogTitle, DialogTrigger } from '@/components/ui/dialog';
14
- import type { SearchResult } from '@/lib/search';
14
+ import { highlightTerms, type SearchResult } from '@/lib/search';
15
15
  import type { ResolvedSearchConfig } from '@/lib/search/config';
16
16
  import { resolveSearchProvider, type SearchProvider } from '@/lib/search/provider';
17
17
  import { getScopeByPathname } from '@/lib/site-config';
@@ -44,6 +44,12 @@ function renderSnippet(snippet: string) {
44
44
  );
45
45
  }
46
46
 
47
+ function renderWithQueryHighlight(text: string, query: string) {
48
+ const terms = query.trim().split(/\s+/).filter(Boolean);
49
+ if (!terms.length) return text;
50
+ return renderSnippet(highlightTerms(text, terms));
51
+ }
52
+
47
53
  /**
48
54
  * Provider-neutral search dialog. The selected provider and its index or
49
55
  * client are loaded on demand, so search stays out of the initial bundle.
@@ -224,8 +230,13 @@ export function Search({ config, labels }: { config: ResolvedSearchConfig; label
224
230
  onSelect={() => select(result)}
225
231
  >
226
232
  <div className="text-[0.9rem] font-semibold text-foreground">
227
- {result.page}
228
- {result.heading ? ` › ${result.heading}` : ''}
233
+ {renderWithQueryHighlight(result.page, query)}
234
+ {result.heading ? (
235
+ <>
236
+ {' › '}
237
+ {renderWithQueryHighlight(result.heading, query)}
238
+ </>
239
+ ) : null}
229
240
  </div>
230
241
  {result.snippet && (
231
242
  <div className="mt-[0.15rem] line-clamp-2 text-[0.8rem] text-muted-foreground">
@@ -24,16 +24,16 @@ export function PropertiesTable({ children }: PropertiesTableProps) {
24
24
 
25
25
  return (
26
26
  <div className="my-4 overflow-x-auto rounded-lg border border-border px-6 py-2">
27
- <table className="m-0 min-w-[40rem] table-fixed border-collapse">
27
+ <table className="m-0 min-w-[40rem] table-fixed border-collapse border-0">
28
28
  <thead>
29
29
  <tr>
30
- <th className="w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 pr-6 text-left text-sm font-semibold text-foreground">
30
+ <th className="w-1/5 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 pr-6 text-left text-sm font-semibold text-foreground">
31
31
  Name
32
32
  </th>
33
- <th className="w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 pr-6 text-left text-sm font-semibold text-foreground">
33
+ <th className="w-1/4 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 pr-6 text-left text-sm font-semibold text-foreground">
34
34
  Type
35
35
  </th>
36
- <th className="w-1/3 border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 text-left text-sm font-semibold text-foreground">
36
+ <th className="border-x-0 border-t-0 border-b border-border bg-transparent px-0 py-2 text-left text-sm font-semibold text-foreground">
37
37
  Description
38
38
  </th>
39
39
  </tr>
@@ -60,10 +60,17 @@
60
60
  /* Markdown content */
61
61
 
62
62
  @layer components {
63
+ /* Body copy is muted; headings, bold text, links, and inline code stay at
64
+ full foreground brightness so they stand out against the running text. */
63
65
  .docs-markdown {
64
66
  margin-bottom: 2.5rem;
65
67
  font-size: 16px;
66
68
  line-height: 1.75;
69
+ color: color-mix(in srgb, var(--foreground) 40%, var(--muted-foreground));
70
+ }
71
+
72
+ [data-theme="dark"] .docs-markdown {
73
+ color: color-mix(in srgb, var(--foreground) 25%, var(--muted-foreground));
67
74
  }
68
75
 
69
76
  .docs-markdown > * + * {
@@ -72,7 +79,7 @@
72
79
 
73
80
  .docs-markdown :where(h2, h3, h4, h5, h6) {
74
81
  font-family: var(--font-heading);
75
- font-weight: var(--font-heading-weight, 700);
82
+ font-weight: var(--font-heading-weight, 600);
76
83
  letter-spacing: -0.02em;
77
84
  line-height: 1.25;
78
85
  color: var(--foreground);
@@ -129,7 +136,13 @@
129
136
  margin: 0.35rem 0;
130
137
  }
131
138
 
139
+ .docs-markdown :where(strong, b) {
140
+ color: var(--foreground);
141
+ font-weight: 600;
142
+ }
143
+
132
144
  .docs-markdown a {
145
+ color: var(--foreground);
133
146
  text-decoration: underline;
134
147
  text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
135
148
  text-underline-offset: 0.2em;
@@ -140,6 +153,7 @@
140
153
  }
141
154
 
142
155
  .docs-markdown code:not(pre code) {
156
+ color: var(--foreground);
143
157
  border-radius: var(--radius-sm);
144
158
  background: color-mix(in srgb, currentColor 4%, transparent);
145
159
  padding: 0.1rem 0.3rem;
@@ -158,6 +172,10 @@
158
172
  border-radius: var(--radius-lg);
159
173
  }
160
174
 
175
+ .docs-markdown th {
176
+ color: var(--foreground);
177
+ }
178
+
161
179
  .docs-markdown th,
162
180
  .docs-markdown td {
163
181
  border: none;
@@ -40,7 +40,7 @@
40
40
  }
41
41
 
42
42
  :root {
43
- --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
43
+ --font-sans: "Inter Variable", system-ui, -apple-system, "Segoe UI", sans-serif;
44
44
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;
45
45
  --font-heading: var(--font-sans);
46
46
  --header-height: 3rem;