@xzibit/ui 0.2.0 → 0.3.1

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 CHANGED
@@ -2,6 +2,78 @@
2
2
 
3
3
  All notable changes to `@xzibit/ui` are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/) loosely; versioning follows [SemVer](https://semver.org/).
4
4
 
5
+ ## [0.3.1] — 2026-05-24
6
+
7
+ ### Fixed
8
+
9
+ - **`'use client'` directives now ship in the bundled output.** Source files for `TopBar`, `AppsDropdown`, `BackToLauncher`, and `useApps` now carry a top-of-file `'use client'` directive, and the build (tsup + new `esbuild-plugin-preserve-directives`) preserves these directives in both ESM and CJS output. Previous versions (v0.1.0 – v0.3.0) stripped the directives at build time, which caused Next.js App Router to attempt server-side rendering of `useState`-bearing components and crash during prerender (`TypeError: (0 , d.useState) is not a function`).
10
+
11
+ Concretely: any consumer importing `TopBar` (or `AppsDropdown` / `BackToLauncher` / `useApps`) directly into a Server Component (default for `app/layout.tsx` and `app/page.tsx` files in Next.js 13+) would fail at build time with an exit-code-1 prerender error. ERP Overview Phase 1.11–1.13 deployments all hit this and silently failed Vercel deploys for three consecutive ships (live site continued serving the Phase 1.10 cached build, which masked the failures from reporting).
12
+
13
+ After upgrading to v0.3.1, no consumer-side workaround is required. Direct import into Server Components works.
14
+
15
+ ### Why this happened
16
+
17
+ esbuild's default behaviour is to strip top-of-file directives during bundling. tsup wraps esbuild and inherits this default. Without an explicit plugin (`esbuild-plugin-preserve-directives`), source-file `'use client'` declarations disappear from the dist output, leaving consumers to wrap the imports themselves in a local `'use client'` re-export module — the workaround ERP Overview shipped as Phase 1.14's `src/components/TopBarClient.tsx`.
18
+
19
+ ### Build config change
20
+
21
+ - **NEW** `tsup.config.ts` replaces the inline `tsup src/index.ts --format cjs,esm --dts --clean` script. Equivalent flags, plus `esbuildPlugins: [preserveDirectives()]`.
22
+ - **NEW devDependency:** `esbuild-plugin-preserve-directives@^0.0.11`.
23
+ - `scripts.build` simplified to `tsup` (reads `tsup.config.ts`); same for `dev`.
24
+
25
+ ### Consumer migration after v0.3.1 lands
26
+
27
+ If you previously created a local `'use client'` wrapper for `TopBar` (or any other v0.3.0 export), you can now delete the wrapper and import directly from `@xzibit/ui` into your Server Component layout. See ERP Overview SHA `9fb78a4` for the pre-fix workaround pattern; SHA after Phase 1.15 will show the post-fix clean pattern.
28
+
29
+ ### Backward compatible
30
+
31
+ API surface unchanged from v0.3.0. Same exports, same prop signatures, same default behaviour. v0.3.1 is a strict bug-fix release.
32
+
33
+ ### Acceptance test (for future package work)
34
+
35
+ In a fresh Next.js 14+ App Router scaffold, place this in `app/layout.tsx`:
36
+
37
+ ```tsx
38
+ import { TopBar } from '@xzibit/ui';
39
+ export default function RootLayout({ children }) {
40
+ return (
41
+ <html lang="en">
42
+ <body>
43
+ <TopBar appName="Test App" />
44
+ <main style={{ marginTop: 44 }}>{children}</main>
45
+ </body>
46
+ </html>
47
+ );
48
+ }
49
+ ```
50
+
51
+ Then `npm run build` must complete without a `useState is not a function` error and Vercel must report READY. This is the canonical regression test for the v0.3.1 fix.
52
+
53
+ ---
54
+
55
+ ## [0.3.0] — 2026-05-24
56
+
57
+ ### Added
58
+
59
+ - **`disablePadding` prop on `<ContentContainer>`** — when `true`, renders no padding (only `max-width` + `margin: 0 auto`). For app-shell integration where the parent layout already provides padding (e.g. left-nav offset), preventing the tier padding from stacking and reducing effective content width.
60
+
61
+ ```tsx
62
+ <ContentContainer tier="reference" disablePadding>
63
+ {/* shell-managed padding flows through */}
64
+ </ContentContainer>
65
+ ```
66
+
67
+ ### Why
68
+
69
+ ERP Overview Phase 1.13 (the canonical `<ContentContainer>` migration ship 2026-05-24) surfaced a left-nav stacking conflict: the per-page outer div uses `padding: '2.5rem 2.5rem 2.5rem 280px'` (left-nav offset), and stacking the container's `3rem 2rem` reduces effective width on wide monitors from the spec's 1200px to ~816px. Their resolution — `className="content-container--layout"` + `!important` padding zero — works but is a smell. `disablePadding` is the canonical API. Every left-nav-bearing portfolio app will hit this same conflict; this prop is the right answer for v0.3 before the portfolio-wide broadcast.
70
+
71
+ ### Backward compatible
72
+
73
+ `disablePadding` defaults to `false`. v0.2.x adopters who don't set it see no change. v0.3+ is the recommended version for app-shell integration; v0.2.x is fine for page-level adoption where shell padding isn't a concern.
74
+
75
+ ---
76
+
5
77
  ## [0.2.0] — 2026-05-24
6
78
 
7
79
  ### Added
package/README.md CHANGED
@@ -14,10 +14,26 @@ Shared chrome components for the Xzibit Apps portfolio. Single source of truth
14
14
  npm install @xzibit/ui
15
15
  ```
16
16
 
17
- Peer dependencies: `react@^18`, `react-dom@^18`.
17
+ Peer dependencies: `react@^18 || ^19`, `react-dom@^18 || ^19`.
18
18
 
19
19
  (Recommended companion: `@xzibit/tokens` for CSS variable values — without it, components fall back to inline hex defaults.)
20
20
 
21
+ ### Next.js App Router compatibility (v0.3.1+)
22
+
23
+ `TopBar`, `AppsDropdown`, `BackToLauncher`, and `useApps` are Client Components — they carry a `'use client'` directive in the bundled output (preserved through tsup via `esbuild-plugin-preserve-directives`). You can import them directly into Server Components (e.g. `app/layout.tsx` and `app/page.tsx`) without wrapping them in a local `'use client'` re-export. Next.js sees the directive at the package boundary and renders them client-side automatically.
24
+
25
+ `ContentContainer` and `XzibitMark` have no client-only code (no hooks, no event handlers) — they're server-safe and unmarked.
26
+
27
+ Versions v0.1.0 – v0.3.0 shipped without preserved directives. Consumers on those versions need a local wrapper:
28
+
29
+ ```tsx
30
+ // src/components/TopBarClient.tsx — only needed on v0.1.0 – v0.3.0
31
+ 'use client';
32
+ export { TopBar } from '@xzibit/ui';
33
+ ```
34
+
35
+ …then import the wrapper instead of the package directly. **Upgrade to v0.3.1+ and delete the wrapper.**
36
+
21
37
  ---
22
38
 
23
39
  ## Use
@@ -146,7 +162,7 @@ This sets `--xz-charcoal`, `--xz-teal`, `--xz-white`, `--border`, etc. — and `
146
162
  | `<AppsDropdown />` | Sectioned + alphabetical apps dropdown driven by `/api/me/apps` |
147
163
  | `<XzibitMark size={28} />` | Xzibit X brand mark as inline SVG (any size, any density) |
148
164
  | `useApps()` | React hook fetching `/api/me/apps` with loading + error + refetch |
149
- | `<ContentContainer tier="reference">` *(v0.2+)* | Content max-width container per DESIGN-STANDARD v2.4 §Content Density Tiers — tiers: `'editorial'` (720px), `'reference'` (1200px, default), `'data'` (unconstrained). Wraps `<main>` content. |
165
+ | `<ContentContainer tier="reference">` *(v0.2+)* | Content max-width container per DESIGN-STANDARD v2.4 §Content Density Tiers — tiers: `'editorial'` (720px), `'reference'` (1200px, default), `'data'` (unconstrained). Wraps `<main>` content. v0.3+ adds `disablePadding` prop for app-shell integration where layout already provides padding (e.g. left-nav offset). |
150
166
 
151
167
  ---
152
168
 
package/dist/index.cjs CHANGED
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  "use strict";
2
4
  var __defProp = Object.defineProperty;
3
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -563,17 +565,28 @@ function BuildBadge({
563
565
 
564
566
  // src/ContentContainer.tsx
565
567
  var import_jsx_runtime5 = require("react/jsx-runtime");
566
- var TIER_STYLES = {
567
- editorial: { maxWidth: 720, margin: "0 auto", padding: "3rem 2rem" },
568
- reference: { maxWidth: 1200, margin: "0 auto", padding: "3rem 2rem" },
569
- data: { maxWidth: "100%", margin: "0 auto", padding: "1.5rem 2rem" }
568
+ var TIER_MAX_WIDTH = {
569
+ editorial: 720,
570
+ reference: 1200,
571
+ data: "100%"
572
+ };
573
+ var TIER_PADDING = {
574
+ editorial: "3rem 2rem",
575
+ reference: "3rem 2rem",
576
+ data: "1.5rem 2rem"
570
577
  };
571
578
  function ContentContainer({
572
579
  tier = "reference",
580
+ disablePadding = false,
573
581
  className,
574
582
  children
575
583
  }) {
576
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, style: TIER_STYLES[tier], children });
584
+ const style = {
585
+ maxWidth: TIER_MAX_WIDTH[tier],
586
+ margin: "0 auto",
587
+ ...disablePadding ? {} : { padding: TIER_PADDING[tier] }
588
+ };
589
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, style, children });
577
590
  }
578
591
  // Annotate the CommonJS export names for ESM import in node:
579
592
  0 && (module.exports = {
package/dist/index.d.cts CHANGED
@@ -196,6 +196,22 @@ interface ContentContainerProps {
196
196
  * Defaults to `'reference'` (the portfolio default per v2.4).
197
197
  */
198
198
  tier?: ContentTier;
199
+ /**
200
+ * If `true`, render no padding — useful when the parent app shell already
201
+ * provides layout-level padding (e.g. left-nav offset).
202
+ *
203
+ * Default: `false` (tier padding is applied).
204
+ *
205
+ * **Common use case:** app-shell `layout.tsx` integration where the surrounding
206
+ * layout div sets `padding: '2.5rem 2.5rem 2.5rem 280px'` for left-nav offset.
207
+ * Without `disablePadding`, the container's `3rem 2rem` stacks with the shell
208
+ * padding and reduces effective content width (e.g. 1200px ContentContainer
209
+ * minus 64px container padding minus 320px shell padding ≈ 816px actual).
210
+ *
211
+ * Added in v0.3.0 (2026-05-24) after ERP Overview Phase 1.13 surfaced the
212
+ * left-nav stacking conflict.
213
+ */
214
+ disablePadding?: boolean;
199
215
  /** Optional className for additional styling. */
200
216
  className?: string;
201
217
  /** Children to render inside the container. */
@@ -226,6 +242,6 @@ interface ContentContainerProps {
226
242
  * </ContentContainer>
227
243
  * ```
228
244
  */
229
- declare function ContentContainer({ tier, className, children, }: ContentContainerProps): react_jsx_runtime.JSX.Element;
245
+ declare function ContentContainer({ tier, disablePadding, className, children, }: ContentContainerProps): react_jsx_runtime.JSX.Element;
230
246
 
231
247
  export { type App, AppsDropdown, type AppsDropdownProps, type AppsResponse, BackToLauncher, type BackToLauncherProps, ContentContainer, type ContentContainerProps, type ContentTier, type RawApp, TopBar, type TopBarProps, type UseAppsOptions, type UseAppsResult, XzibitMark, type XzibitMarkProps, normalizeApp, useApps };
package/dist/index.d.ts CHANGED
@@ -196,6 +196,22 @@ interface ContentContainerProps {
196
196
  * Defaults to `'reference'` (the portfolio default per v2.4).
197
197
  */
198
198
  tier?: ContentTier;
199
+ /**
200
+ * If `true`, render no padding — useful when the parent app shell already
201
+ * provides layout-level padding (e.g. left-nav offset).
202
+ *
203
+ * Default: `false` (tier padding is applied).
204
+ *
205
+ * **Common use case:** app-shell `layout.tsx` integration where the surrounding
206
+ * layout div sets `padding: '2.5rem 2.5rem 2.5rem 280px'` for left-nav offset.
207
+ * Without `disablePadding`, the container's `3rem 2rem` stacks with the shell
208
+ * padding and reduces effective content width (e.g. 1200px ContentContainer
209
+ * minus 64px container padding minus 320px shell padding ≈ 816px actual).
210
+ *
211
+ * Added in v0.3.0 (2026-05-24) after ERP Overview Phase 1.13 surfaced the
212
+ * left-nav stacking conflict.
213
+ */
214
+ disablePadding?: boolean;
199
215
  /** Optional className for additional styling. */
200
216
  className?: string;
201
217
  /** Children to render inside the container. */
@@ -226,6 +242,6 @@ interface ContentContainerProps {
226
242
  * </ContentContainer>
227
243
  * ```
228
244
  */
229
- declare function ContentContainer({ tier, className, children, }: ContentContainerProps): react_jsx_runtime.JSX.Element;
245
+ declare function ContentContainer({ tier, disablePadding, className, children, }: ContentContainerProps): react_jsx_runtime.JSX.Element;
230
246
 
231
247
  export { type App, AppsDropdown, type AppsDropdownProps, type AppsResponse, BackToLauncher, type BackToLauncherProps, ContentContainer, type ContentContainerProps, type ContentTier, type RawApp, TopBar, type TopBarProps, type UseAppsOptions, type UseAppsResult, XzibitMark, type XzibitMarkProps, normalizeApp, useApps };
package/dist/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ 'use client';
2
+
1
3
  // src/BackToLauncher.tsx
2
4
  import { useState } from "react";
3
5
 
@@ -531,17 +533,28 @@ function BuildBadge({
531
533
 
532
534
  // src/ContentContainer.tsx
533
535
  import { jsx as jsx5 } from "react/jsx-runtime";
534
- var TIER_STYLES = {
535
- editorial: { maxWidth: 720, margin: "0 auto", padding: "3rem 2rem" },
536
- reference: { maxWidth: 1200, margin: "0 auto", padding: "3rem 2rem" },
537
- data: { maxWidth: "100%", margin: "0 auto", padding: "1.5rem 2rem" }
536
+ var TIER_MAX_WIDTH = {
537
+ editorial: 720,
538
+ reference: 1200,
539
+ data: "100%"
540
+ };
541
+ var TIER_PADDING = {
542
+ editorial: "3rem 2rem",
543
+ reference: "3rem 2rem",
544
+ data: "1.5rem 2rem"
538
545
  };
539
546
  function ContentContainer({
540
547
  tier = "reference",
548
+ disablePadding = false,
541
549
  className,
542
550
  children
543
551
  }) {
544
- return /* @__PURE__ */ jsx5("div", { className, style: TIER_STYLES[tier], children });
552
+ const style = {
553
+ maxWidth: TIER_MAX_WIDTH[tier],
554
+ margin: "0 auto",
555
+ ...disablePadding ? {} : { padding: TIER_PADDING[tier] }
556
+ };
557
+ return /* @__PURE__ */ jsx5("div", { className, style, children });
545
558
  }
546
559
  export {
547
560
  AppsDropdown,
@@ -0,0 +1 @@
1
+ {"inputs":{"src/XzibitMark.tsx":{"bytes":1644,"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/BackToLauncher.tsx":{"bytes":2222,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/XzibitMark.tsx","kind":"import-statement","original":"./XzibitMark"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/types.ts":{"bytes":2534,"imports":[],"format":"esm"},"src/useApps.ts":{"bytes":2717,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/types.ts","kind":"import-statement","original":"./types"}],"format":"esm"},"src/AppsDropdown.tsx":{"bytes":8128,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/useApps.ts","kind":"import-statement","original":"./useApps"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/TopBar.tsx":{"bytes":3910,"imports":[{"path":"src/BackToLauncher.tsx","kind":"import-statement","original":"./BackToLauncher"},{"path":"src/AppsDropdown.tsx","kind":"import-statement","original":"./AppsDropdown"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/ContentContainer.tsx":{"bytes":2945,"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":912,"imports":[{"path":"src/TopBar.tsx","kind":"import-statement","original":"./TopBar"},{"path":"src/BackToLauncher.tsx","kind":"import-statement","original":"./BackToLauncher"},{"path":"src/AppsDropdown.tsx","kind":"import-statement","original":"./AppsDropdown"},{"path":"src/XzibitMark.tsx","kind":"import-statement","original":"./XzibitMark"},{"path":"src/useApps.ts","kind":"import-statement","original":"./useApps"},{"path":"src/ContentContainer.tsx","kind":"import-statement","original":"./ContentContainer"},{"path":"src/types.ts","kind":"import-statement","original":"./types"}],"format":"esm"}},"outputs":{"dist/index.cjs":{"imports":[{"path":"react","kind":"require-call","external":true},{"path":"react/jsx-runtime","kind":"require-call","external":true},{"path":"react/jsx-runtime","kind":"require-call","external":true},{"path":"react","kind":"require-call","external":true},{"path":"react","kind":"require-call","external":true},{"path":"react/jsx-runtime","kind":"require-call","external":true},{"path":"react/jsx-runtime","kind":"require-call","external":true},{"path":"react/jsx-runtime","kind":"require-call","external":true}],"exports":[],"entryPoint":"src/index.ts","inputs":{"src/index.ts":{"bytesInOutput":337},"src/BackToLauncher.tsx":{"bytesInOutput":1929},"src/XzibitMark.tsx":{"bytesInOutput":1335},"src/AppsDropdown.tsx":{"bytesInOutput":7830},"src/useApps.ts":{"bytesInOutput":1230},"src/types.ts":{"bytesInOutput":255},"src/TopBar.tsx":{"bytesInOutput":3499},"src/ContentContainer.tsx":{"bytesInOutput":575}},"bytes":18231}}}
@@ -0,0 +1 @@
1
+ {"inputs":{"src/XzibitMark.tsx":{"bytes":1644,"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/BackToLauncher.tsx":{"bytes":2222,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/XzibitMark.tsx","kind":"import-statement","original":"./XzibitMark"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/types.ts":{"bytes":2534,"imports":[],"format":"esm"},"src/useApps.ts":{"bytes":2717,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/types.ts","kind":"import-statement","original":"./types"}],"format":"esm"},"src/AppsDropdown.tsx":{"bytes":8128,"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"src/useApps.ts","kind":"import-statement","original":"./useApps"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/TopBar.tsx":{"bytes":3910,"imports":[{"path":"src/BackToLauncher.tsx","kind":"import-statement","original":"./BackToLauncher"},{"path":"src/AppsDropdown.tsx","kind":"import-statement","original":"./AppsDropdown"},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/ContentContainer.tsx":{"bytes":2945,"imports":[{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"format":"esm"},"src/index.ts":{"bytes":912,"imports":[{"path":"src/TopBar.tsx","kind":"import-statement","original":"./TopBar"},{"path":"src/BackToLauncher.tsx","kind":"import-statement","original":"./BackToLauncher"},{"path":"src/AppsDropdown.tsx","kind":"import-statement","original":"./AppsDropdown"},{"path":"src/XzibitMark.tsx","kind":"import-statement","original":"./XzibitMark"},{"path":"src/useApps.ts","kind":"import-statement","original":"./useApps"},{"path":"src/ContentContainer.tsx","kind":"import-statement","original":"./ContentContainer"},{"path":"src/types.ts","kind":"import-statement","original":"./types"}],"format":"esm"}},"outputs":{"dist/index.js":{"imports":[{"path":"react","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true},{"path":"react/jsx-runtime","kind":"import-statement","external":true}],"exports":["AppsDropdown","BackToLauncher","ContentContainer","TopBar","XzibitMark","normalizeApp","useApps"],"entryPoint":"src/index.ts","inputs":{"src/BackToLauncher.tsx":{"bytesInOutput":1796},"src/XzibitMark.tsx":{"bytesInOutput":1159},"src/AppsDropdown.tsx":{"bytesInOutput":7266},"src/useApps.ts":{"bytesInOutput":1171},"src/types.ts":{"bytesInOutput":255},"src/TopBar.tsx":{"bytesInOutput":3171},"src/index.ts":{"bytesInOutput":0},"src/ContentContainer.tsx":{"bytesInOutput":544}},"bytes":15707}}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xzibit/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Shared chrome components for the Xzibit Apps portfolio. v0.1: TopBar + BackToLauncher + AppsDropdown + XzibitMark + useApps hook. Single source of truth for portfolio chrome — tweak once, every app picks it up on next deploy.",
5
5
  "license": "MIT",
6
6
  "author": "Xzibit Apps",
@@ -37,8 +37,8 @@
37
37
  "topbar"
38
38
  ],
39
39
  "scripts": {
40
- "build": "tsup src/index.ts --format cjs,esm --dts --clean",
41
- "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
40
+ "build": "tsup",
41
+ "dev": "tsup --watch",
42
42
  "prepublishOnly": "npm run build"
43
43
  },
44
44
  "peerDependencies": {
@@ -46,11 +46,12 @@
46
46
  "react-dom": "^18.0.0 || ^19.0.0"
47
47
  },
48
48
  "devDependencies": {
49
- "typescript": "^5.0.0",
50
- "tsup": "^8.0.0",
49
+ "@types/node": "^20.0.0",
51
50
  "@types/react": "^18.0.0",
52
51
  "@types/react-dom": "^18.0.0",
53
- "@types/node": "^20.0.0"
52
+ "esbuild-plugin-preserve-directives": "^0.0.11",
53
+ "tsup": "^8.0.0",
54
+ "typescript": "^5.0.0"
54
55
  },
55
56
  "publishConfig": {
56
57
  "access": "public"