@ubx/docs-ui 0.1.0 → 0.4.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/dist/Footer.d.ts CHANGED
@@ -1 +1,9 @@
1
- export declare function Footer(): import("react").JSX.Element;
1
+ export declare function Footer({ tagline, links, }: {
2
+ /** The one-line statement of what this particular site's content is. */
3
+ tagline: React.ReactNode;
4
+ /** Footer destinations, stated per site rather than shared. */
5
+ links: {
6
+ label: string;
7
+ href: string;
8
+ }[];
9
+ }): import("react").JSX.Element;
package/dist/Footer.js CHANGED
@@ -2,10 +2,25 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Footer: Material style, a hairline divider and quiet text, not a
3
3
  // heavy block -- the same restrained treatment already established
4
4
  // for hairline dividers site-wide (globals.css's own --color-border).
5
- // Links repeat two of Header's own real, checked destinations
6
- // (Documentation, GitHub) rather than inventing new ones, plus a
7
- // License link to this repo's own real LICENSE file (Apache 2.0,
8
- // confirmed directly from the file, not assumed).
9
- export function Footer() {
10
- return (_jsx("footer", { className: "border-t border-border", children: _jsxs("div", { className: "mx-auto max-w-7xl px-6 py-8 text-sm text-foreground-muted", children: [_jsxs("div", { className: "flex flex-col items-center gap-3 sm:flex-row sm:justify-between", children: [_jsx("p", { children: "Reference content is generated from each provider\u2019s own real schema, not hand-written." }), _jsxs("nav", { className: "flex items-center gap-5", children: [_jsx("a", { href: "https://docs.ubiquex.io", className: "hover:text-primary", children: "Documentation" }), _jsx("a", { href: "https://github.com/Ubiquex", className: "hover:text-primary", children: "GitHub" }), _jsx("a", { href: "https://github.com/Ubiquex/ubx-docs-providers/blob/main/LICENSE", className: "hover:text-primary", children: "License" })] })] }), _jsx("p", { className: "mt-4 text-center text-xs sm:text-left", children: "\u00A9 2026 Ubiquex" })] }) }));
5
+ //
6
+ // IDENTITY IS A PROP, NOT A DEFAULT, for the same reason it is on
7
+ // GlobalSearch, and this component is the proof that the reason is real.
8
+ //
9
+ // Extracted with the provider site's own identity hardcoded: a tagline
10
+ // reading "Reference content is generated from each provider's own real
11
+ // schema, not hand-written" and a License link pointing at
12
+ // ubx-docs-providers. Both were correct for the site they came from and
13
+ // silently wrong on the user docs site, where all 137 pages are
14
+ // hand-written MDX and the repo is a different one. The tagline did not
15
+ // merely fail to apply, it asserted the exact opposite of the truth on
16
+ // every page of the site.
17
+ //
18
+ // GlobalSearch was caught during extraction because its regression was
19
+ // visible (a pill turned into a rectangle). This one was not, because
20
+ // wrong prose looks exactly like right prose until someone reads it. So
21
+ // the rule cannot be "check carefully during extraction"; it has to be
22
+ // that the type system refuses to let a site inherit an identity it did
23
+ // not state. Hence both props are required and neither has a default.
24
+ export function Footer({ tagline, links, }) {
25
+ return (_jsx("footer", { className: "border-t border-border", children: _jsxs("div", { className: "mx-auto max-w-7xl px-6 py-8 text-sm text-foreground-muted", children: [_jsxs("div", { className: "flex flex-col items-center gap-3 sm:flex-row sm:justify-between", children: [_jsx("p", { children: tagline }), _jsx("nav", { className: "flex items-center gap-5", children: links.map((l) => (_jsx("a", { href: l.href, className: "hover:text-primary", children: l.label }, l.href))) })] }), _jsx("p", { className: "mt-4 text-center text-xs sm:text-left", children: "\u00A9 2026 Ubiquex" })] }) }));
11
26
  }
package/dist/Header.d.ts CHANGED
@@ -8,11 +8,21 @@ export type SectionTab = {
8
8
  label: string;
9
9
  href: string;
10
10
  };
11
- export declare function Header({ nav, tabs, activeTab, mobileMenu, }: {
11
+ export declare function Header({ nav, tabs, activeTab, mobileMenu, search, }: {
12
12
  nav: NavLink[];
13
13
  /** Omit entirely for a single-tier header, which is the provider site. */
14
14
  tabs?: SectionTab[];
15
15
  /** href of the active tab, matched by prefix so nested pages stay lit. */
16
16
  activeTab?: string;
17
17
  mobileMenu?: React.ReactNode;
18
+ /**
19
+ * Search control, rendered to the left of the theme toggle.
20
+ *
21
+ * In the header rather than in page content because the alternative
22
+ * put it on exactly one page type per site: the provider site's home
23
+ * page, and the user docs site's section landing pages, which are the
24
+ * pages being removed. Every content page on both sites had no way to
25
+ * search at all.
26
+ */
27
+ search?: React.ReactNode;
18
28
  }): import("react").JSX.Element;
package/dist/Header.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import Link from "next/link";
3
3
  import { ThemeToggle } from "./ThemeToggle";
4
- export function Header({ nav, tabs, activeTab, mobileMenu, }) {
4
+ export function Header({ nav, tabs, activeTab, mobileMenu, search, }) {
5
5
  return (_jsxs("header", { className: "border-b border-border bg-background", children: [_jsxs("div", { className: "relative mx-auto flex max-w-7xl items-center gap-3 px-6 py-4", children: [mobileMenu, _jsxs(Link, { href: "/", className: "flex shrink-0 items-center", children: [_jsx("img", { src: "/logo/logo.png", alt: "ubx", className: "logo-light h-6 w-auto" }), _jsx("img", { src: "/logo/logo-dark.png", alt: "ubx", className: "logo-dark h-6 w-auto" })] }), _jsx("div", { className: "flex-1" }), _jsx("nav", { className: "absolute left-1/2 hidden -translate-x-1/2 items-center gap-5 md:flex", children: nav.map((item) => {
6
6
  const className = item.current
7
7
  ? "text-sm text-primary"
8
8
  : "text-sm text-foreground-muted hover:text-primary";
9
9
  return item.href.startsWith("/") ? (_jsx(Link, { href: item.href, className: className, children: item.label }, item.label)) : (_jsx("a", { href: item.href, className: className, children: item.label }, item.label));
10
- }) }), _jsx(ThemeToggle, {})] }), tabs && tabs.length > 0 ? (_jsx("div", { className: "mx-auto max-w-7xl px-6", children: _jsx("nav", { className: "flex gap-6 overflow-x-auto", children: tabs.map((tab) => {
10
+ }) }), search ? _jsx("div", { className: "w-40 shrink-0 sm:w-56 lg:w-64", children: search }) : null, _jsx(ThemeToggle, {})] }), tabs && tabs.length > 0 ? (_jsx("div", { className: "mx-auto max-w-7xl px-6", children: _jsx("nav", { className: "flex gap-6 overflow-x-auto", children: tabs.map((tab) => {
11
11
  // Prefix match, so /concepts/ledger keeps the Concepts tab
12
12
  // lit rather than only the exact section index.
13
13
  const active = activeTab === tab.href || activeTab?.startsWith(tab.href + "/");
@@ -1,3 +1,8 @@
1
- export declare function MobileSidebarToggle({ children }: {
1
+ export declare function MobileSidebarToggle({ children, label, }: {
2
2
  children: React.ReactNode;
3
+ /**
4
+ * What this drawer contains, in the consuming site's own vocabulary.
5
+ * Used as the drawer heading and as the basis for both aria-labels.
6
+ */
7
+ label: string;
3
8
  }): import("react").JSX.Element;
@@ -18,14 +18,29 @@ import { useEffect, useRef, useState } from "react";
18
18
  // vanishing the instant the tap registers.
19
19
  const TRANSITION_MS = 200;
20
20
  // UBI-247: takes the drawer contents as `children` rather than
21
- // constructing a ProviderSidebar itself. That coupling was the one thing
22
- // in this component that was not generic, and it is exactly the kind of
23
- // thing that would have been carried into @ubx/docs-ui unnoticed had the
24
- // extraction been done first against a single call site. The provider
25
- // site passes <ProviderSidebar .../>, this site passes <DocSidebar .../>,
26
- // and the open/close/transition/route-change behaviour below is
27
- // identical for both.
28
- export function MobileSidebarToggle({ children }) {
21
+ // constructing a ProviderSidebar itself. The provider site passes
22
+ // <ProviderSidebar .../>, this site passes <DocSidebar .../>, and the
23
+ // open/close/transition/route-change behaviour below is identical for
24
+ // both.
25
+ //
26
+ // This comment used to claim that the `children` coupling "was the one
27
+ // thing in this component that was not generic." That was wrong, and it
28
+ // is left recorded here rather than quietly deleted because the way it
29
+ // was wrong is the useful part. Three strings a few lines below still
30
+ // said "Services" and "service navigation", which is AWS provider
31
+ // vocabulary: the provider site's mobile drawer holds a provider's
32
+ // service groups. The user docs site's drawer holds documentation
33
+ // pages, so it announced the wrong thing to every mobile reader and to
34
+ // every screen reader.
35
+ //
36
+ // It survived because the extraction was audited per component by
37
+ // reading each one, and reading is exactly what does not catch a
38
+ // plausible-sounding noun. It was found later by mechanically
39
+ // extracting every user-visible string literal in the package and
40
+ // asking of each one whether it could be true on both sites. `label` is
41
+ // required for the same reason Footer's props are: a default here is a
42
+ // silent claim about which site you are.
43
+ export function MobileSidebarToggle({ children, label, }) {
29
44
  const [mounted, setMounted] = useState(false);
30
45
  const [visible, setVisible] = useState(false);
31
46
  const closeTimer = useRef(null);
@@ -68,8 +83,8 @@ export function MobileSidebarToggle({ children }) {
68
83
  close();
69
84
  // eslint-disable-next-line react-hooks/exhaustive-deps
70
85
  }, [pathname]);
71
- return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: open, "aria-label": "Open service navigation", className: "flex h-9 w-9 shrink-0 items-center justify-center rounded text-foreground-muted hover:bg-surface hover:text-primary lg:hidden", children: _jsx("svg", { viewBox: "0 0 20 20", width: "20", height: "20", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M3 5.5h14M3 10h14M3 14.5h14", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) }) }), mounted && (_jsxs("div", { role: "dialog", "aria-modal": "true", className: "fixed inset-0 z-50 lg:hidden", children: [_jsx("button", { type: "button", "aria-label": "Close service navigation", onClick: close, className: "absolute inset-0 bg-foreground/40 transition-opacity duration-200 motion-reduce:transition-none " +
86
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: open, "aria-label": `Open ${label} navigation`, className: "flex h-9 w-9 shrink-0 items-center justify-center rounded text-foreground-muted hover:bg-surface hover:text-primary lg:hidden", children: _jsx("svg", { viewBox: "0 0 20 20", width: "20", height: "20", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M3 5.5h14M3 10h14M3 14.5h14", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) }) }), mounted && (_jsxs("div", { role: "dialog", "aria-modal": "true", className: "fixed inset-0 z-50 lg:hidden", children: [_jsx("button", { type: "button", "aria-label": `Close ${label} navigation`, onClick: close, className: "absolute inset-0 bg-foreground/40 transition-opacity duration-200 motion-reduce:transition-none " +
72
87
  (visible ? "opacity-100" : "opacity-0") }), _jsxs("div", { className: "absolute inset-y-0 left-0 flex w-80 max-w-[85vw] flex-col overflow-y-auto bg-background p-4 shadow-lg " +
73
88
  "transition-transform duration-200 ease-out motion-reduce:transition-none " +
74
- (visible ? "translate-x-0" : "-translate-x-full"), children: [_jsxs("div", { className: "mb-3 flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-semibold text-foreground", children: "Services" }), _jsx("button", { type: "button", onClick: close, "aria-label": "Close service navigation", className: "flex h-8 w-8 items-center justify-center rounded text-foreground-muted hover:bg-surface hover:text-primary", children: _jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M4 4l8 8M12 4l-8 8", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) }) })] }), children] })] }))] }));
89
+ (visible ? "translate-x-0" : "-translate-x-full"), children: [_jsxs("div", { className: "mb-3 flex items-center justify-between", children: [_jsx("span", { className: "text-sm font-semibold text-foreground", children: label }), _jsx("button", { type: "button", onClick: close, "aria-label": `Close ${label} navigation`, className: "flex h-8 w-8 items-center justify-center rounded text-foreground-muted hover:bg-surface hover:text-primary", children: _jsx("svg", { viewBox: "0 0 16 16", width: "16", height: "16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M4 4l8 8M12 4l-8 8", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round" }) }) })] }), children] })] }))] }));
75
90
  }
@@ -0,0 +1,54 @@
1
+ import type React from "react";
2
+ import { type NavLink, type SectionTab } from "./Header";
3
+ export type PageShellProps = {
4
+ nav: NavLink[];
5
+ /** Omit for a single-tier header. The provider site omits it. */
6
+ tabs?: SectionTab[];
7
+ /** href of the active tab, prefix-matched so nested pages stay lit. */
8
+ activeTab?: string;
9
+ /**
10
+ * Sidebar tree. When present the shell renders the two-column rail and
11
+ * wires the same node into the mobile drawer, so the desktop and mobile
12
+ * navigation can never disagree about what they contain.
13
+ */
14
+ sidebar?: React.ReactNode;
15
+ /** Drawer heading, in the consuming site's own vocabulary. */
16
+ sidebarLabel?: string;
17
+ /**
18
+ * Search placeholder. Presence of this prop is what mounts search.
19
+ *
20
+ * The placeholder is a prop because it is genuinely site-specific
21
+ * ("Search resources and data sources..." against "Search the docs").
22
+ * The input's styling is NOT a prop, which reverses the decision
23
+ * GlobalSearch made when it was extracted. That decision was right at
24
+ * the time: the two sites styled the box differently, and a default
25
+ * would have silently imposed one site's look on the other. Now that
26
+ * the shell owns the placement, one styling is the correct answer for
27
+ * both, and leaving it configurable would just re-open the drift.
28
+ */
29
+ searchPlaceholder?: string;
30
+ /** Footer identity. Required for the reason stated on Footer itself. */
31
+ footer: {
32
+ tagline: React.ReactNode;
33
+ links: {
34
+ label: string;
35
+ href: string;
36
+ }[];
37
+ };
38
+ children: React.ReactNode;
39
+ };
40
+ export declare function PageShell({ nav, tabs, activeTab, sidebar, sidebarLabel, searchPlaceholder, footer, children, }: PageShellProps): React.JSX.Element;
41
+ /**
42
+ * Sets data-theme from localStorage before paint.
43
+ *
44
+ * Both sites carried this as a verbatim string constant in their own root
45
+ * layout, which is duplication the package could not see. It has to be an
46
+ * exported string rather than a component because it goes in <head> via
47
+ * dangerouslySetInnerHTML and must run ahead of hydration: a React
48
+ * component would run too late and the reader would see a flash of the
49
+ * OS default before their stored choice applied.
50
+ *
51
+ * Absent or invalid storage leaves no attribute at all, which is exactly
52
+ * "follow the OS".
53
+ */
54
+ export declare const THEME_INIT_SCRIPT = "(function () {\n try {\n var stored = window.localStorage.getItem(\"ubx-docs-theme\");\n if (stored === \"light\" || stored === \"dark\") {\n document.documentElement.setAttribute(\"data-theme\", stored);\n }\n } catch (e) {}\n})();";
@@ -0,0 +1,33 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Header } from "./Header";
3
+ import { Footer } from "./Footer";
4
+ import { GlobalSearch } from "./GlobalSearch";
5
+ import { MobileSidebarToggle } from "./MobileSidebarToggle";
6
+ /** Shared header search styling. See searchPlaceholder above. */
7
+ const SEARCH_INPUT_CLASS = "w-full rounded-full bg-field px-4 py-1.5 text-sm text-foreground outline-none " +
8
+ "placeholder:text-foreground-muted focus:ring-2 focus:ring-primary/30";
9
+ export function PageShell({ nav, tabs, activeTab, sidebar, sidebarLabel = "Navigation", searchPlaceholder, footer, children, }) {
10
+ const search = searchPlaceholder ? (_jsx(GlobalSearch, { placeholder: searchPlaceholder, inputClassName: SEARCH_INPUT_CLASS })) : undefined;
11
+ return (_jsxs(_Fragment, { children: [_jsx(Header, { nav: nav, tabs: tabs, activeTab: activeTab, search: search, mobileMenu: sidebar ? (_jsx(MobileSidebarToggle, { label: sidebarLabel, children: sidebar })) : undefined }), sidebar ? (_jsxs("div", { className: "mx-auto flex w-full max-w-7xl flex-1 gap-10 px-6 py-10", children: [_jsx("aside", { className: "hidden w-64 shrink-0 lg:block", children: sidebar }), _jsx("main", { className: "min-w-0 flex-1", children: children })] })) : (_jsx("main", { className: "mx-auto w-full max-w-7xl flex-1 px-6 py-12", children: children })), _jsx(Footer, { tagline: footer.tagline, links: footer.links })] }));
12
+ }
13
+ /**
14
+ * Sets data-theme from localStorage before paint.
15
+ *
16
+ * Both sites carried this as a verbatim string constant in their own root
17
+ * layout, which is duplication the package could not see. It has to be an
18
+ * exported string rather than a component because it goes in <head> via
19
+ * dangerouslySetInnerHTML and must run ahead of hydration: a React
20
+ * component would run too late and the reader would see a flash of the
21
+ * OS default before their stored choice applied.
22
+ *
23
+ * Absent or invalid storage leaves no attribute at all, which is exactly
24
+ * "follow the OS".
25
+ */
26
+ export const THEME_INIT_SCRIPT = `(function () {
27
+ try {
28
+ var stored = window.localStorage.getItem("ubx-docs-theme");
29
+ if (stored === "light" || stored === "dark") {
30
+ document.documentElement.setAttribute("data-theme", stored);
31
+ }
32
+ } catch (e) {}
33
+ })();`;
package/dist/index.d.ts CHANGED
@@ -8,3 +8,5 @@ export type { SearchEntry } from "./GlobalSearch";
8
8
  export { ThemeToggle } from "./ThemeToggle";
9
9
  export { Footer } from "./Footer";
10
10
  export { MobileSidebarToggle } from "./MobileSidebarToggle";
11
+ export { PageShell, THEME_INIT_SCRIPT } from "./PageShell";
12
+ export type { PageShellProps } from "./PageShell";
package/dist/index.js CHANGED
@@ -14,3 +14,4 @@ export { GlobalSearch } from "./GlobalSearch";
14
14
  export { ThemeToggle } from "./ThemeToggle";
15
15
  export { Footer } from "./Footer";
16
16
  export { MobileSidebarToggle } from "./MobileSidebarToggle";
17
+ export { PageShell, THEME_INIT_SCRIPT } from "./PageShell";
package/dist/theme-a.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { ThemeRegistration } from "shiki";
2
2
  export declare const themeA: ThemeRegistration;
3
3
  /** Languages theme A has been verified against, token by token. */
4
- export declare const SUPPORTED_LANGS: readonly ["go", "typescript", "python", "bash", "hcl", "json"];
4
+ export declare const SUPPORTED_LANGS: readonly ["go", "typescript", "python", "bash", "hcl", "json", "yaml", "toml", "docker"];
5
5
  export type SupportedLang = (typeof SUPPORTED_LANGS)[number];
6
6
  export declare function isSupportedLang(lang: string): lang is SupportedLang;
package/dist/theme-a.js CHANGED
@@ -87,6 +87,15 @@ export const themeA = {
87
87
  // when the provider site's rendered code was diffed before and
88
88
  // after the swap.
89
89
  "variable.other.property",
90
+ // YAML and TOML mapping keys. UBI-247 slice 3: verified against
91
+ // real token output before being written, and the naive
92
+ // assumption was wrong in two ways. YAML keys carry BOTH
93
+ // entity.name.tag.yaml and string.unquoted.plain.out.yaml, so
94
+ // override 1 below (which exists for shell) was silently
95
+ // stripping every key in every CI example to plain. TOML keys
96
+ // are variable.other.key.toml, covered by nothing above.
97
+ "entity.name.tag",
98
+ "variable.other.key.toml",
90
99
  ],
91
100
  settings: { foreground: "var(--color-code-yellow)" },
92
101
  },
@@ -112,6 +121,24 @@ export const themeA = {
112
121
  scope: ["keyword.operator"],
113
122
  settings: { foreground: "var(--color-foreground)" },
114
123
  },
124
+ // OVERRIDE 5. The YAML grammar scopes a bare `on` as a boolean
125
+ // constant, not as a key, because YAML 1.1 really does treat `on` as
126
+ // true. In GitHub Actions `on:` is the trigger key and appears in
127
+ // essentially every CI example (25 of 29 YAML fences in these docs
128
+ // are Actions workflows), so leaving it green made one key in every
129
+ // file render as though it were a language keyword while its
130
+ // siblings were yellow.
131
+ //
132
+ // The grammar cannot distinguish the two, so this is a real
133
+ // trade-off rather than a clean fix: a genuine YAML boolean value
134
+ // now renders plain instead of green. Chosen deliberately, because
135
+ // in this corpus `on:` as a key is common and `true`/`false` as a
136
+ // scalar value is rare, and a wrongly-emphasised key is more
137
+ // misleading than an unemphasised boolean.
138
+ {
139
+ scope: ["constant.language.boolean.yaml"],
140
+ settings: { foreground: "var(--color-foreground)" },
141
+ },
115
142
  // OVERRIDE 4. TypeScript scopes the arrow of an arrow function as
116
143
  // storage.type.function.arrow, which the generic `storage.type` rule
117
144
  // above catches and paints green. The hand-rolled tokenizer left it
@@ -139,6 +166,12 @@ export const SUPPORTED_LANGS = [
139
166
  "bash",
140
167
  "hcl",
141
168
  "json",
169
+ // Added UBI-247 slice 3, each verified token by token: yaml (29
170
+ // fences, concentrated in the Integrations section where CI config is
171
+ // the entire subject), toml, and dockerfile.
172
+ "yaml",
173
+ "toml",
174
+ "docker",
142
175
  ];
143
176
  export function isSupportedLang(lang) {
144
177
  return SUPPORTED_LANGS.includes(lang);
package/package.json CHANGED
@@ -1,19 +1,26 @@
1
1
  {
2
2
  "name": "@ubx/docs-ui",
3
- "version": "0.1.0",
4
- "description": "Shared UI for ubx documentation sites: theme A, Shiki-backed CodeBlock, header, search, theme toggle.",
3
+ "version": "0.4.0",
4
+ "description": "Shared UI for ubx documentation sites: the page shell, theme A, Shiki-backed CodeBlock, header, search, theme toggle.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
- "files": ["dist"],
7
+ "files": [
8
+ "dist"
9
+ ],
8
10
  "main": "./dist/index.js",
9
11
  "types": "./dist/index.d.ts",
10
12
  "exports": {
11
- ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" }
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
12
17
  },
13
18
  "scripts": {
14
- "build": "tsc -p tsconfig.json",
19
+ "build": "rm -rf dist && tsc -p tsconfig.json",
15
20
  "typecheck": "tsc -p tsconfig.json --noEmit",
16
- "prepublishOnly": "npm run build"
21
+ "prepublishOnly": "npm run build",
22
+ "test": "node --test \"test/**/*.test.mjs\"",
23
+ "check:package": "npm run build && node scripts/check-package.mjs"
17
24
  },
18
25
  "peerDependencies": {
19
26
  "next": ">=15",
@@ -32,6 +39,11 @@
32
39
  "react-dom": "19.2.8",
33
40
  "typescript": "^5"
34
41
  },
35
- "publishConfig": { "access": "public" },
36
- "repository": { "type": "git", "url": "git+https://github.com/Ubiquex/ubx-docs-ui.git" }
42
+ "publishConfig": {
43
+ "access": "public"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/Ubiquex/ubx-docs-ui.git"
48
+ }
37
49
  }