@meetreeve/ui 0.5.0 → 0.5.2

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/index.d.mts CHANGED
@@ -49,6 +49,49 @@ interface BrandGlyphProps {
49
49
  }
50
50
  declare function BrandGlyph({ brand, size, subtle, className }: BrandGlyphProps): react.JSX.Element;
51
51
 
52
+ /**
53
+ * Brand lockup metric rules (DEV-5139) — the single source of truth for how a
54
+ * lettermark relates to its wordmark. Grounded in standard identity-system
55
+ * practice: the measurement unit for a lockup is the WORDMARK'S CAP HEIGHT,
56
+ * proportions are locked as ratios, and the pieces share one baseline.
57
+ *
58
+ * Rules v1:
59
+ * 1. Baseline lock — mark, accent dot, and wordmark sit on ONE shared
60
+ * baseline. Flex `items-baseline` in the lockup; never `items-center`
61
+ * for text marks (center-aligning boxes ≠ aligning baselines — measured
62
+ * 3px baseline float on cadasense, DEV-5139).
63
+ * 2. Cap-height parity + optical overshoot — the mark's cap height is
64
+ * MARK_CAP_RATIO × the wordmark's cap height. A lone round glyph
65
+ * (C/G/O/Q/S) optically undershoots flat caps, so exact parity reads
66
+ * small; 1.08 restores perceived equality (measured 0.93 before).
67
+ * 3. Lockup gap — mark→wordmark gap is 0.5–0.8× the wordmark cap height.
68
+ * 4. Clearspace — ≥1 cap height of empty space around the whole lockup
69
+ * (the header's own padding satisfies this; consumers embedding the
70
+ * lockup elsewhere own the rule).
71
+ * 5. Standalone glyph fill — on a square canvas (favicon, avatar), the
72
+ * letter's cap height is ~60–70% of the canvas edge; ghost-tile below
73
+ * 20px (DEV-4539).
74
+ * 6. Ratios are locked — scale the lockup uniformly; never resize one
75
+ * element of it. These constants are that lock.
76
+ *
77
+ * The generated-asset pipeline (reeve-services brand_identity
78
+ * svg_templates) mirrors these values — see DEV-5140. Change them together.
79
+ */
80
+ /** Rule 2 — mark cap height ÷ wordmark cap height. */
81
+ declare const MARK_CAP_RATIO = 1.08;
82
+ /**
83
+ * Rule 3, applied: 8px gap ≈ 0.79× the wordmark's ~10.1px cap height —
84
+ * inside the 0.5–0.8 band. Named so nobody "fixes" it per-surface.
85
+ */
86
+ declare const LOCKUP_GAP = "gap-2";
87
+ /**
88
+ * Rule 1, applied: the lockup is one locked unit — baseline-aligned inside,
89
+ * centered as a whole by whatever wraps it (e.g. a touch target).
90
+ */
91
+ declare const LOCKUP_ROW = "flex items-baseline";
92
+ /** Rule 5 — standalone glyph cap height as a fraction of the canvas edge. */
93
+ declare const GLYPH_CANVAS_FILL = 0.65;
94
+
52
95
  interface HeaderNavItem {
53
96
  label: string;
54
97
  href?: string;
@@ -458,4 +501,4 @@ interface ContextSwitcherProps {
458
501
  }
459
502
  declare function ContextSwitcher({ items, activeRefId, onSelect, footer, placeholder, collapsed, side, align, contentClassName, }: ContextSwitcherProps): react.JSX.Element;
460
503
 
461
- export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, type GroupRungItem, type HeaderNavItem, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
504
+ export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, GLYPH_CANVAS_FILL, type GroupRungItem, type HeaderNavItem, LOCKUP_GAP, LOCKUP_ROW, MARK_CAP_RATIO, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
package/dist/index.d.ts CHANGED
@@ -49,6 +49,49 @@ interface BrandGlyphProps {
49
49
  }
50
50
  declare function BrandGlyph({ brand, size, subtle, className }: BrandGlyphProps): react.JSX.Element;
51
51
 
52
+ /**
53
+ * Brand lockup metric rules (DEV-5139) — the single source of truth for how a
54
+ * lettermark relates to its wordmark. Grounded in standard identity-system
55
+ * practice: the measurement unit for a lockup is the WORDMARK'S CAP HEIGHT,
56
+ * proportions are locked as ratios, and the pieces share one baseline.
57
+ *
58
+ * Rules v1:
59
+ * 1. Baseline lock — mark, accent dot, and wordmark sit on ONE shared
60
+ * baseline. Flex `items-baseline` in the lockup; never `items-center`
61
+ * for text marks (center-aligning boxes ≠ aligning baselines — measured
62
+ * 3px baseline float on cadasense, DEV-5139).
63
+ * 2. Cap-height parity + optical overshoot — the mark's cap height is
64
+ * MARK_CAP_RATIO × the wordmark's cap height. A lone round glyph
65
+ * (C/G/O/Q/S) optically undershoots flat caps, so exact parity reads
66
+ * small; 1.08 restores perceived equality (measured 0.93 before).
67
+ * 3. Lockup gap — mark→wordmark gap is 0.5–0.8× the wordmark cap height.
68
+ * 4. Clearspace — ≥1 cap height of empty space around the whole lockup
69
+ * (the header's own padding satisfies this; consumers embedding the
70
+ * lockup elsewhere own the rule).
71
+ * 5. Standalone glyph fill — on a square canvas (favicon, avatar), the
72
+ * letter's cap height is ~60–70% of the canvas edge; ghost-tile below
73
+ * 20px (DEV-4539).
74
+ * 6. Ratios are locked — scale the lockup uniformly; never resize one
75
+ * element of it. These constants are that lock.
76
+ *
77
+ * The generated-asset pipeline (reeve-services brand_identity
78
+ * svg_templates) mirrors these values — see DEV-5140. Change them together.
79
+ */
80
+ /** Rule 2 — mark cap height ÷ wordmark cap height. */
81
+ declare const MARK_CAP_RATIO = 1.08;
82
+ /**
83
+ * Rule 3, applied: 8px gap ≈ 0.79× the wordmark's ~10.1px cap height —
84
+ * inside the 0.5–0.8 band. Named so nobody "fixes" it per-surface.
85
+ */
86
+ declare const LOCKUP_GAP = "gap-2";
87
+ /**
88
+ * Rule 1, applied: the lockup is one locked unit — baseline-aligned inside,
89
+ * centered as a whole by whatever wraps it (e.g. a touch target).
90
+ */
91
+ declare const LOCKUP_ROW = "flex items-baseline";
92
+ /** Rule 5 — standalone glyph cap height as a fraction of the canvas edge. */
93
+ declare const GLYPH_CANVAS_FILL = 0.65;
94
+
52
95
  interface HeaderNavItem {
53
96
  label: string;
54
97
  href?: string;
@@ -458,4 +501,4 @@ interface ContextSwitcherProps {
458
501
  }
459
502
  declare function ContextSwitcher({ items, activeRefId, onSelect, footer, placeholder, collapsed, side, align, contentClassName, }: ContextSwitcherProps): react.JSX.Element;
460
503
 
461
- export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, type GroupRungItem, type HeaderNavItem, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
504
+ export { type AppRungItem, AppSidebar, type AppSidebarBrand, type AppSidebarModuleDef, type AppSidebarProps, type AppSidebarTopItem, type Brand, BrandGlyph, type BrandGlyphProps, type ContextOrgGroup, ContextSwitcher, type ContextSwitcherItem, type ContextSwitcherProps, type FallbackModuleDef, Flyout, type FlyoutChild, type FlyoutProps, GLYPH_CANVAS_FILL, type GroupRungItem, type HeaderNavItem, LOCKUP_GAP, LOCKUP_ROW, MARK_CAP_RATIO, REEVE_ACCENT, ResponsiveHeader, type ResponsiveHeaderProps, type RungItem, type SidebarLadder, type SidebarLadderParsed, type SidebarRung, type UseLadderRungOptions, deriveFallbackLadder, faviconUrl, groupByOrg, reconcileLadder, resolveLadder, useLadderRung, useResponsivePlaceholder, zSidebarLadder };
package/dist/index.js CHANGED
@@ -35,6 +35,10 @@ __export(src_exports, {
35
35
  BrandGlyph: () => BrandGlyph,
36
36
  ContextSwitcher: () => ContextSwitcher,
37
37
  Flyout: () => Flyout,
38
+ GLYPH_CANVAS_FILL: () => GLYPH_CANVAS_FILL,
39
+ LOCKUP_GAP: () => LOCKUP_GAP,
40
+ LOCKUP_ROW: () => LOCKUP_ROW,
41
+ MARK_CAP_RATIO: () => MARK_CAP_RATIO,
38
42
  REEVE_ACCENT: () => REEVE_ACCENT,
39
43
  ResponsiveHeader: () => ResponsiveHeader,
40
44
  deriveFallbackLadder: () => deriveFallbackLadder,
@@ -49,7 +53,6 @@ __export(src_exports, {
49
53
  module.exports = __toCommonJS(src_exports);
50
54
 
51
55
  // src/brand-glyph.tsx
52
- var import_image = __toESM(require("next/image"));
53
56
  var import_react = require("react");
54
57
 
55
58
  // src/lib/cn.ts
@@ -77,6 +80,13 @@ function firstInitial(name) {
77
80
  return trimmed ? trimmed[0].toUpperCase() : "?";
78
81
  }
79
82
 
83
+ // src/lib/lockup.ts
84
+ var MARK_CAP_RATIO = 1.08;
85
+ var MONOGRAM_TEXT_LG = "text-[15px]";
86
+ var LOCKUP_GAP = "gap-2";
87
+ var LOCKUP_ROW = "flex items-baseline";
88
+ var GLYPH_CANVAS_FILL = 0.65;
89
+
80
90
  // src/lib/monogram.ts
81
91
  var MONOGRAM_COLORS = [
82
92
  "#7c3aed",
@@ -125,21 +135,26 @@ function BrandGlyph({ brand, size = "sm", subtle = false, className }) {
125
135
  "span",
126
136
  {
127
137
  className: cn(
128
- "flex shrink-0 items-center justify-center overflow-hidden rounded",
138
+ // `self-center` opts image glyphs out of the lockup's baseline
139
+ // alignment (DEV-5139): an image box has no text baseline, so
140
+ // flexbox would seat its BOTTOM edge on the wordmark baseline and
141
+ // shove the icon upward. Centering preserves the pre-DEV-5139 look.
142
+ "flex shrink-0 items-center justify-center self-center overflow-hidden rounded",
129
143
  // Tint needs a dark-mode counterpart or it vanishes on dark backgrounds.
130
144
  subtle ? "bg-black/5 dark:bg-white/10" : "bg-black/10 dark:bg-white/15",
131
145
  box,
132
146
  className
133
147
  ),
134
148
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
135
- import_image.default,
149
+ "img",
136
150
  {
137
151
  src,
138
152
  alt: "",
139
153
  width: dim,
140
154
  height: dim,
155
+ loading: "lazy",
156
+ decoding: "async",
141
157
  "data-testid": "brand-image",
142
- unoptimized: true,
143
158
  onError: () => setFailed((prev) => new Set(prev).add(src)),
144
159
  className: cn(img, "rounded-sm object-contain")
145
160
  }
@@ -156,7 +171,11 @@ function BrandGlyph({ brand, size = "sm", subtle = false, className }) {
156
171
  "data-testid": "brand-monogram",
157
172
  className: cn(
158
173
  "flex shrink-0 select-none items-baseline justify-center font-semibold uppercase",
159
- size === "lg" ? "text-[13px]" : "text-[11px]",
174
+ // DEV-5139 rule 2: at "lg" the letter runs 15px = the text-sm
175
+ // wordmark (14px) × MARK_CAP_RATIO (1.08) — a lone round glyph at
176
+ // exact parity optically reads small. "sm" has no wordmark partner
177
+ // in its surfaces; unchanged pending a lockup audit there.
178
+ size === "lg" ? MONOGRAM_TEXT_LG : "text-[11px]",
160
179
  box,
161
180
  className
162
181
  ),
@@ -304,7 +323,7 @@ function ResponsiveHeader({
304
323
  };
305
324
  }, [open]);
306
325
  const hasSecondary = Boolean(nav && nav.length > 0 || auth || themeToggle);
307
- const brandLockup = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
326
+ const brandLockup = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { "data-testid": "header-lockup", className: cn(LOCKUP_ROW, LOCKUP_GAP), children: [
308
327
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BrandGlyph, { brand, size: "lg" }),
309
328
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
310
329
  "span",
@@ -335,10 +354,10 @@ function ResponsiveHeader({
335
354
  "aria-label": brand.name || "Home",
336
355
  "data-testid": "header-brand",
337
356
  style: TOUCH_TARGET_STYLE,
338
- className: "flex shrink-0 items-center gap-2",
357
+ className: "flex shrink-0 items-center",
339
358
  children: brandLockup
340
359
  }
341
- ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "data-testid": "header-brand", className: "flex shrink-0 items-center gap-2", children: brandLockup }),
360
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { "data-testid": "header-brand", className: "flex shrink-0 items-center", children: brandLockup }),
342
361
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
343
362
  "div",
344
363
  {
@@ -1212,6 +1231,10 @@ function ContextSwitcher({
1212
1231
  BrandGlyph,
1213
1232
  ContextSwitcher,
1214
1233
  Flyout,
1234
+ GLYPH_CANVAS_FILL,
1235
+ LOCKUP_GAP,
1236
+ LOCKUP_ROW,
1237
+ MARK_CAP_RATIO,
1215
1238
  REEVE_ACCENT,
1216
1239
  ResponsiveHeader,
1217
1240
  deriveFallbackLadder,
package/dist/index.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  "use client";
2
2
 
3
3
  // src/brand-glyph.tsx
4
- import Image from "next/image";
5
4
  import { useState } from "react";
6
5
 
7
6
  // src/lib/cn.ts
@@ -29,6 +28,13 @@ function firstInitial(name) {
29
28
  return trimmed ? trimmed[0].toUpperCase() : "?";
30
29
  }
31
30
 
31
+ // src/lib/lockup.ts
32
+ var MARK_CAP_RATIO = 1.08;
33
+ var MONOGRAM_TEXT_LG = "text-[15px]";
34
+ var LOCKUP_GAP = "gap-2";
35
+ var LOCKUP_ROW = "flex items-baseline";
36
+ var GLYPH_CANVAS_FILL = 0.65;
37
+
32
38
  // src/lib/monogram.ts
33
39
  var MONOGRAM_COLORS = [
34
40
  "#7c3aed",
@@ -77,21 +83,26 @@ function BrandGlyph({ brand, size = "sm", subtle = false, className }) {
77
83
  "span",
78
84
  {
79
85
  className: cn(
80
- "flex shrink-0 items-center justify-center overflow-hidden rounded",
86
+ // `self-center` opts image glyphs out of the lockup's baseline
87
+ // alignment (DEV-5139): an image box has no text baseline, so
88
+ // flexbox would seat its BOTTOM edge on the wordmark baseline and
89
+ // shove the icon upward. Centering preserves the pre-DEV-5139 look.
90
+ "flex shrink-0 items-center justify-center self-center overflow-hidden rounded",
81
91
  // Tint needs a dark-mode counterpart or it vanishes on dark backgrounds.
82
92
  subtle ? "bg-black/5 dark:bg-white/10" : "bg-black/10 dark:bg-white/15",
83
93
  box,
84
94
  className
85
95
  ),
86
96
  children: /* @__PURE__ */ jsx(
87
- Image,
97
+ "img",
88
98
  {
89
99
  src,
90
100
  alt: "",
91
101
  width: dim,
92
102
  height: dim,
103
+ loading: "lazy",
104
+ decoding: "async",
93
105
  "data-testid": "brand-image",
94
- unoptimized: true,
95
106
  onError: () => setFailed((prev) => new Set(prev).add(src)),
96
107
  className: cn(img, "rounded-sm object-contain")
97
108
  }
@@ -108,7 +119,11 @@ function BrandGlyph({ brand, size = "sm", subtle = false, className }) {
108
119
  "data-testid": "brand-monogram",
109
120
  className: cn(
110
121
  "flex shrink-0 select-none items-baseline justify-center font-semibold uppercase",
111
- size === "lg" ? "text-[13px]" : "text-[11px]",
122
+ // DEV-5139 rule 2: at "lg" the letter runs 15px = the text-sm
123
+ // wordmark (14px) × MARK_CAP_RATIO (1.08) — a lone round glyph at
124
+ // exact parity optically reads small. "sm" has no wordmark partner
125
+ // in its surfaces; unchanged pending a lockup audit there.
126
+ size === "lg" ? MONOGRAM_TEXT_LG : "text-[11px]",
112
127
  box,
113
128
  className
114
129
  ),
@@ -256,7 +271,7 @@ function ResponsiveHeader({
256
271
  };
257
272
  }, [open]);
258
273
  const hasSecondary = Boolean(nav && nav.length > 0 || auth || themeToggle);
259
- const brandLockup = /* @__PURE__ */ jsxs2(Fragment, { children: [
274
+ const brandLockup = /* @__PURE__ */ jsxs2("span", { "data-testid": "header-lockup", className: cn(LOCKUP_ROW, LOCKUP_GAP), children: [
260
275
  /* @__PURE__ */ jsx2(BrandGlyph, { brand, size: "lg" }),
261
276
  /* @__PURE__ */ jsx2(
262
277
  "span",
@@ -287,10 +302,10 @@ function ResponsiveHeader({
287
302
  "aria-label": brand.name || "Home",
288
303
  "data-testid": "header-brand",
289
304
  style: TOUCH_TARGET_STYLE,
290
- className: "flex shrink-0 items-center gap-2",
305
+ className: "flex shrink-0 items-center",
291
306
  children: brandLockup
292
307
  }
293
- ) : /* @__PURE__ */ jsx2("div", { "data-testid": "header-brand", className: "flex shrink-0 items-center gap-2", children: brandLockup }),
308
+ ) : /* @__PURE__ */ jsx2("div", { "data-testid": "header-brand", className: "flex shrink-0 items-center", children: brandLockup }),
294
309
  /* @__PURE__ */ jsx2(
295
310
  "div",
296
311
  {
@@ -1163,6 +1178,10 @@ export {
1163
1178
  BrandGlyph,
1164
1179
  ContextSwitcher,
1165
1180
  Flyout,
1181
+ GLYPH_CANVAS_FILL,
1182
+ LOCKUP_GAP,
1183
+ LOCKUP_ROW,
1184
+ MARK_CAP_RATIO,
1166
1185
  REEVE_ACCENT,
1167
1186
  ResponsiveHeader,
1168
1187
  deriveFallbackLadder,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetreeve/ui",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Shared brand-adaptive UI primitives for MindFortress frontends — min-size tokens, a favicon→logo→monogram BrandGlyph, a responsive header that keeps the search bar usable on mobile, and AppSidebar's viewport-driven auto-collapse ladder.",
5
5
  "license": "UNLICENSED",
6
6
  "main": "./dist/index.js",
@@ -30,6 +30,7 @@
30
30
  "dev": "tsup --watch",
31
31
  "test": "vitest run",
32
32
  "typecheck": "tsc --noEmit",
33
+ "check:esm": "node scripts/check-esm-import.mjs",
33
34
  "prepublishOnly": "npm run build"
34
35
  },
35
36
  "publishConfig": {