@backmatter/studio 0.0.8 → 0.0.10

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.
Files changed (65) hide show
  1. package/dist/chunks/UiKitApp-BxvTwsko.js +2410 -0
  2. package/dist/chunks/WorkspaceRoutes-BKKEkAq7.js +90126 -0
  3. package/dist/chunks/{dashboard-bNp6AbKE.js → dashboard-8rjkfEXl.js} +380 -382
  4. package/dist/chunks/{index-DoDE-hwB.js → index-laWAz5_p.js} +111 -112
  5. package/dist/dashboard.js +2 -2
  6. package/dist/index.js +287 -146
  7. package/dist/site-widget.css +1615 -0
  8. package/dist/site-widget.js +30903 -628
  9. package/dist/studio.css +268 -67
  10. package/package.json +4 -4
  11. package/src/App.tsx +12 -29
  12. package/src/api.ts +55 -0
  13. package/src/chat/ChangeCard.tsx +2 -0
  14. package/src/chat/ChatMessage.tsx +73 -62
  15. package/src/chat/ChatPanel.tsx +110 -15
  16. package/src/chat/DocListCard.tsx +2 -0
  17. package/src/chat/LiveStatus.tsx +78 -8
  18. package/src/chat/Reveal.tsx +34 -0
  19. package/src/chat/Shimmer.tsx +65 -23
  20. package/src/chat/chat-helpers.ts +35 -0
  21. package/src/chat/motion.ts +73 -0
  22. package/src/chat/useStickToBottom.ts +129 -14
  23. package/src/chrome/ContentExplorerSidebar.tsx +23 -8
  24. package/src/chrome/content-tree-visibility.ts +55 -0
  25. package/src/devbar-state.ts +125 -0
  26. package/src/index.css +3 -131
  27. package/src/pages/CliAuthorizePage.tsx +61 -28
  28. package/src/pages/MediaGalleryPage.tsx +74 -11
  29. package/src/pages/MediaLightbox.tsx +93 -1
  30. package/src/pages/SettingsUsagePage.tsx +23 -1
  31. package/src/pages/media-grid.ts +13 -0
  32. package/src/pages/ui-kit/UiKitApp.tsx +164 -0
  33. package/src/pages/ui-kit/buttons.tsx +30 -0
  34. package/src/pages/ui-kit/catalog.tsx +41 -0
  35. package/src/pages/ui-kit/colors.tsx +95 -0
  36. package/src/pages/ui-kit/devbar.tsx +160 -0
  37. package/src/pages/ui-kit/feedback.tsx +39 -0
  38. package/src/pages/ui-kit/fields.tsx +47 -0
  39. package/src/pages/ui-kit/lists.tsx +19 -0
  40. package/src/pages/ui-kit/overlays.tsx +39 -0
  41. package/src/pages/ui-kit/path.ts +9 -0
  42. package/src/pages/ui-kit/sections.tsx +32 -0
  43. package/src/pages/ui-kit/surfaces.tsx +121 -0
  44. package/src/pages/ui-kit/topic.tsx +112 -0
  45. package/src/pages/ui-kit/topics.ts +71 -0
  46. package/src/pages/ui-kit/type.tsx +55 -0
  47. package/src/preview-frame-radius.ts +51 -0
  48. package/src/preview-frame.tsx +108 -49
  49. package/src/shell-resets.css +155 -0
  50. package/src/site-bar.tsx +1810 -425
  51. package/src/site-dashboard-href.ts +21 -0
  52. package/src/site-devbar-overlay-inject.ts +15 -0
  53. package/src/site-devbar-overlay.css +217 -0
  54. package/src/site-pointer.ts +1 -1
  55. package/src/site-publish-menu.tsx +33 -0
  56. package/src/site-widget-styles.ts +7 -0
  57. package/src/site-widget.tsx +61 -21
  58. package/src/studio-prose-ui.css +39 -0
  59. package/src/theme/ThemeProvider.tsx +4 -1
  60. package/src/workspace-path.ts +10 -6
  61. package/dist/chunks/EmbedPublishPage-CtIlaeGO.js +0 -149
  62. package/dist/chunks/UiKitPage-DcnSQ-Hf.js +0 -173
  63. package/dist/chunks/WorkspaceRoutes-DVpUOaMV.js +0 -59550
  64. package/dist/chunks/status-bar-action-C0fsAWvD.js +0 -30618
  65. package/src/pages/UiKitPage.tsx +0 -331
@@ -0,0 +1,55 @@
1
+ // packages/studio/src/pages/ui-kit/type.tsx
2
+ import { Text } from "../../ui/Text";
3
+ import { Specimen, Topic } from "./topic";
4
+ import { UI_KIT_TOPICS } from "./topics";
5
+
6
+ const topic = UI_KIT_TOPICS.find((item) => item.slug === "type")!;
7
+
8
+ export function TypeTopic() {
9
+ return (
10
+ <Topic title={topic.title} lede={topic.lede}>
11
+ <Specimen label="display">
12
+ <Text size="display" weight="semibold">
13
+ Display
14
+ </Text>
15
+ </Specimen>
16
+ <Specimen label="xl">
17
+ <Text size="xl" weight="semibold">
18
+ Title
19
+ </Text>
20
+ </Specimen>
21
+ <Specimen label="lg">
22
+ <Text size="lg" weight="medium">
23
+ Heading
24
+ </Text>
25
+ </Specimen>
26
+ <Specimen label="md">
27
+ <Text>Body</Text>
28
+ </Specimen>
29
+ <Specimen label="sm">
30
+ <Text size="sm">Small</Text>
31
+ </Specimen>
32
+ <Specimen label="xs">
33
+ <Text size="xs" weight="semibold">
34
+ Overline
35
+ </Text>
36
+ </Specimen>
37
+ <Specimen label="muted">
38
+ <Text tone="muted" size="sm">
39
+ Muted
40
+ </Text>
41
+ </Specimen>
42
+ <Specimen label="danger">
43
+ <Text tone="danger">Danger</Text>
44
+ </Specimen>
45
+ <Specimen label="success">
46
+ <Text tone="success">Success</Text>
47
+ </Specimen>
48
+ <Specimen label="mono">
49
+ <Text mono size="sm">
50
+ content/blog/hello.mdx
51
+ </Text>
52
+ </Specimen>
53
+ </Topic>
54
+ );
55
+ }
@@ -0,0 +1,51 @@
1
+ // packages/studio/src/preview-frame-radius.ts
2
+ // Heuristic bottom corner radius for the on-site preview ring (no web API for real window shape).
3
+
4
+ /** Chromium on macOS 26+ (Tahoe) — see chromium/brave window corner updates. */
5
+ export const PREVIEW_FRAME_RADIUS_MAC_CHROMIUM_TAHOE = 20;
6
+ /** Chromium on macOS with rounded desktop windows (approx. Sequoia era). */
7
+ export const PREVIEW_FRAME_RADIUS_MAC_CHROMIUM = 16;
8
+ /** Safari / Firefox on macOS. */
9
+ export const PREVIEW_FRAME_RADIUS_MAC = 12;
10
+ export const PREVIEW_FRAME_RADIUS_WINDOWS = 8;
11
+ export const PREVIEW_FRAME_RADIUS_DEFAULT = 12;
12
+
13
+ /** Pull macOS major from Chromium `platformVersion` (e.g. "26.1.0" → 26). */
14
+ export function macPlatformMajor(platformVersion?: string): number | null {
15
+ if (!platformVersion) return null;
16
+ const major = Number.parseInt(platformVersion.split(".")[0] ?? "", 10);
17
+ return Number.isFinite(major) ? major : null;
18
+ }
19
+
20
+ export function isMacUserAgent(userAgent: string): boolean {
21
+ return /Macintosh|Mac OS X/.test(userAgent);
22
+ }
23
+
24
+ /** Chromium desktop builds on macOS (Chrome, Brave, Edge, Opera). */
25
+ export function isMacChromiumUserAgent(userAgent: string): boolean {
26
+ if (!isMacUserAgent(userAgent)) return false;
27
+ if (/Firefox\//.test(userAgent)) return false;
28
+ return /Chrome\/|Chromium\/|Edg\/|OPR\/|Brave\//.test(userAgent);
29
+ }
30
+
31
+ /**
32
+ * Guess preview ring bottom radius (px). `platformVersion` comes from
33
+ * `navigator.userAgentData.getHighEntropyValues(['platformVersion'])` when available.
34
+ */
35
+ export function previewFrameRadiusFromUserAgent(
36
+ userAgent: string,
37
+ platformVersion?: string,
38
+ ): number {
39
+ if (/Windows/.test(userAgent)) return PREVIEW_FRAME_RADIUS_WINDOWS;
40
+ if (!isMacUserAgent(userAgent)) return PREVIEW_FRAME_RADIUS_DEFAULT;
41
+
42
+ const macMajor = macPlatformMajor(platformVersion);
43
+ if (isMacChromiumUserAgent(userAgent)) {
44
+ if (macMajor !== null && macMajor >= 26) return PREVIEW_FRAME_RADIUS_MAC_CHROMIUM_TAHOE;
45
+ if (macMajor !== null && macMajor >= 15) return PREVIEW_FRAME_RADIUS_MAC_CHROMIUM;
46
+ // UA-only: still assume modern Chromium on Mac (Brave/Chrome rarely expose OS in UA).
47
+ return PREVIEW_FRAME_RADIUS_MAC_CHROMIUM;
48
+ }
49
+
50
+ return PREVIEW_FRAME_RADIUS_MAC;
51
+ }
@@ -1,38 +1,107 @@
1
1
  // packages/studio/src/preview-frame.tsx
2
- // Full-viewport ring and a top-right preview label while the site session is active.
2
+ // Full-viewport violet frame while preview is on. No corner label.
3
+ import { useLayoutEffect, useRef, useState, type CSSProperties } from "react";
4
+ import {
5
+ DEVBAR_EASE_IN,
6
+ DEVBAR_EASE_OUT,
7
+ DEVBAR_FRAME_ENTER_MS,
8
+ DEVBAR_FRAME_EXIT_MS,
9
+ prefersReducedMotion,
10
+ } from "./devbar-state";
11
+ import { PREVIEW_FRAME_RADIUS_DEFAULT, previewFrameRadiusFromUserAgent } from "./preview-frame-radius";
12
+
3
13
  export const PREVIEW_FRAME_CLASS = "backmatter-preview-frame";
4
14
 
5
- /** Shared with the on-site dev bar while preview is active. */
6
- export const PREVIEW_CHROME_BORDER = "#d97706";
15
+ type NavigatorWithUaData = Navigator & {
16
+ userAgentData?: {
17
+ getHighEntropyValues: (
18
+ hints: readonly string[],
19
+ ) => Promise<{ platformVersion?: string }>;
20
+ };
21
+ };
7
22
 
8
- function EyeIcon() {
9
- return (
10
- <svg width="14" height="14" viewBox="0 0 16 16" fill="none" aria-hidden>
11
- <path
12
- d="M1.5 8s2.5-4.5 6.5-4.5S14.5 8 14.5 8s-2.5 4.5-6.5 4.5S1.5 8 1.5 8Z"
13
- stroke="currentColor"
14
- strokeWidth="1.25"
15
- strokeLinejoin="round"
16
- />
17
- <circle cx="8" cy="8" r="1.75" stroke="currentColor" strokeWidth="1.25" />
18
- </svg>
19
- );
23
+ /** Chromium `platformVersion` when Client Hints are available (not in all TS `Navigator` libs). */
24
+ function readNavigatorPlatformVersion(): Promise<string | undefined> {
25
+ if (typeof navigator === "undefined") return Promise.resolve(undefined);
26
+ const uad = (navigator as NavigatorWithUaData).userAgentData;
27
+ if (!uad?.getHighEntropyValues) return Promise.resolve(undefined);
28
+ return uad.getHighEntropyValues(["platformVersion"]).then((hints) => hints.platformVersion);
29
+ }
30
+
31
+ /** Bottom window corner radius when the host does not set `devBar.previewFrameRadius`. */
32
+ export function guessPreviewFrameRadius(): number {
33
+ if (typeof navigator === "undefined") return PREVIEW_FRAME_RADIUS_DEFAULT;
34
+ return previewFrameRadiusFromUserAgent(navigator.userAgent);
20
35
  }
21
36
 
22
- export function PreviewFrame() {
37
+ /** Host config, then `--bm-preview-frame-radius` on `<html>`, then platform guess. */
38
+ export function resolvePreviewFrameRadius(override?: number): number {
39
+ if (override !== undefined && Number.isFinite(override) && override >= 0) return override;
40
+ if (typeof document !== "undefined") {
41
+ const raw = getComputedStyle(document.documentElement).getPropertyValue("--bm-preview-frame-radius").trim();
42
+ if (raw) {
43
+ const parsed = Number.parseFloat(raw);
44
+ if (Number.isFinite(parsed) && parsed >= 0) return parsed;
45
+ }
46
+ }
47
+ return guessPreviewFrameRadius();
48
+ }
49
+
50
+ /** Violet used by the frame and the preview toolbar. */
51
+ export const PREVIEW_CHROME_BORDER = "oklch(0.56 0.21 295)";
52
+
53
+ export function PreviewFrame({
54
+ active,
55
+ radius,
56
+ }: {
57
+ active: boolean;
58
+ /** Bottom window corner radius (px). Top stays square under the browser chrome. */
59
+ radius?: number;
60
+ }) {
61
+ const ref = useRef<HTMLDivElement>(null);
62
+ const was = useRef<boolean | null>(null);
63
+ const [corner, setCorner] = useState(() => resolvePreviewFrameRadius(radius));
64
+
65
+ useLayoutEffect(() => {
66
+ if (radius !== undefined) {
67
+ setCorner(resolvePreviewFrameRadius(radius));
68
+ return;
69
+ }
70
+ const ua = navigator.userAgent;
71
+ setCorner(previewFrameRadiusFromUserAgent(ua));
72
+
73
+ void readNavigatorPlatformVersion().then((platformVersion) => {
74
+ if (platformVersion === undefined) return;
75
+ setCorner(previewFrameRadiusFromUserAgent(ua, platformVersion));
76
+ });
77
+ }, [radius]);
78
+
79
+ useLayoutEffect(() => {
80
+ const frame = ref.current;
81
+ const previous = was.current;
82
+ was.current = active;
83
+ if (!frame || previous === null || previous || !active || prefersReducedMotion()) return;
84
+ frame.animate(
85
+ [
86
+ { borderWidth: "3px" },
87
+ { borderWidth: "7px", offset: 0.5 },
88
+ { borderWidth: "3px" },
89
+ ],
90
+ { duration: 700, delay: 180, easing: DEVBAR_EASE_OUT },
91
+ );
92
+ }, [active]);
93
+
23
94
  return (
24
95
  <>
25
96
  <style>{FRAME_CSS}</style>
26
97
  <div
98
+ ref={ref}
27
99
  className={PREVIEW_FRAME_CLASS}
28
100
  data-backmatter-preview-frame=""
101
+ data-active={active ? "true" : "false"}
102
+ style={{ "--bm-preview-frame-radius": `${corner}px` } as CSSProperties}
29
103
  aria-hidden
30
- >
31
- <div className={`${PREVIEW_FRAME_CLASS}-badge`}>
32
- <EyeIcon />
33
- <span>Preview</span>
34
- </div>
35
- </div>
104
+ />
36
105
  </>
37
106
  );
38
107
  }
@@ -42,37 +111,27 @@ const FRAME_CSS = `
42
111
  position: fixed;
43
112
  inset: 0;
44
113
  z-index: 2147482999;
114
+ box-sizing: border-box;
45
115
  pointer-events: none;
46
- box-shadow: inset 0 0 0 2px ${PREVIEW_CHROME_BORDER};
47
- overflow: visible;
116
+ border: 3px solid ${PREVIEW_CHROME_BORDER};
117
+ border-radius: 0 0 var(--bm-preview-frame-radius, 12px) var(--bm-preview-frame-radius, 12px);
118
+ background: transparent;
119
+ clip-path: circle(0% at 100% 100%);
120
+ transition: clip-path ${DEVBAR_FRAME_EXIT_MS}ms ${DEVBAR_EASE_IN};
48
121
  }
49
- .${PREVIEW_FRAME_CLASS}-badge {
50
- position: absolute;
51
- top: env(safe-area-inset-top, 0px);
52
- right: env(safe-area-inset-right, 0px);
53
- display: inline-flex;
54
- align-items: center;
55
- gap: 6px;
56
- padding: 6px 12px;
57
- border: 2px solid ${PREVIEW_CHROME_BORDER};
58
- border-top: 0;
59
- border-radius: 0 0 14px 14px;
60
- background: #fff7ed;
61
- color: #9a3412;
62
- font: 500 12px/1 ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
63
- white-space: nowrap;
64
- -webkit-font-smoothing: antialiased;
122
+ .${PREVIEW_FRAME_CLASS}[data-active="true"] {
123
+ /* 150% is shorter than the viewport diagonal — the ring never reached the top. */
124
+ clip-path: circle(150vmax at 100% 100%);
125
+ transition: clip-path ${DEVBAR_FRAME_ENTER_MS}ms ${DEVBAR_EASE_OUT} 80ms;
65
126
  }
66
- @keyframes backmatter-preview-frame-in {
67
- from { opacity: 0; }
68
- to { opacity: 1; }
69
- }
70
- @media (prefers-reduced-motion: no-preference) {
127
+ @media (prefers-reduced-motion: reduce) {
71
128
  .${PREVIEW_FRAME_CLASS} {
72
- animation: backmatter-preview-frame-in 240ms cubic-bezier(0.23, 1, 0.32, 1) both;
129
+ transition: none;
130
+ clip-path: none;
131
+ opacity: 0;
132
+ }
133
+ .${PREVIEW_FRAME_CLASS}[data-active="true"] {
134
+ opacity: 1;
73
135
  }
74
- }
75
- @media (prefers-reduced-motion: reduce) {
76
- .${PREVIEW_FRAME_CLASS} { animation: none; }
77
136
  }
78
137
  `;
@@ -0,0 +1,155 @@
1
+ /* packages/studio/src/shell-resets.css
2
+ Scoped element resets for studio chrome on host pages and in the dashboard.
3
+ The on-site dev bar lives in a shadow root and uses BAR_CSS in site-bar.tsx. */
4
+
5
+ :is(
6
+ .backmatter-studio,
7
+ .backmatter-devbar-overlay,
8
+ [data-backmatter-panel],
9
+ [data-base-ui-portal],
10
+ [data-selection-bar]
11
+ ),
12
+ :is(
13
+ .backmatter-studio,
14
+ .backmatter-devbar-overlay,
15
+ [data-backmatter-panel],
16
+ [data-base-ui-portal],
17
+ [data-selection-bar]
18
+ )
19
+ :is(*, *::before, *::after) {
20
+ box-sizing: border-box;
21
+ }
22
+
23
+ html:has(.backmatter-studio),
24
+ html:has(.backmatter-studio) body {
25
+ margin: 0;
26
+ min-height: 100dvh;
27
+ }
28
+
29
+ html:has(.backmatter-studio) {
30
+ background: #f7f7f8;
31
+ color-scheme: light;
32
+ }
33
+
34
+ html[data-theme="dark"]:has(.backmatter-studio) {
35
+ background: #18181b;
36
+ color-scheme: dark;
37
+ }
38
+
39
+ html:has(.backmatter-studio) body {
40
+ -webkit-font-smoothing: antialiased;
41
+ font-family:
42
+ "Geist Sans",
43
+ Inter,
44
+ ui-sans-serif,
45
+ system-ui,
46
+ sans-serif;
47
+ }
48
+
49
+ :is(
50
+ .backmatter-studio,
51
+ .backmatter-devbar-overlay,
52
+ [data-backmatter-panel],
53
+ [data-base-ui-portal],
54
+ [data-selection-bar]
55
+ )
56
+ :is(button, input, textarea, select) {
57
+ /* Don't use `font: inherit` — it overrides StyleX sizes in @layer. */
58
+ font-family: inherit;
59
+ }
60
+
61
+ :is(
62
+ .backmatter-studio,
63
+ [data-backmatter-panel],
64
+ [data-base-ui-portal],
65
+ [data-selection-bar]
66
+ )
67
+ :is(button, [role="button"]) {
68
+ cursor: default;
69
+ }
70
+
71
+ :is(
72
+ .backmatter-studio,
73
+ [data-backmatter-panel],
74
+ [data-base-ui-portal],
75
+ [data-selection-bar]
76
+ )
77
+ :is(button:disabled, button[aria-disabled="true"]) {
78
+ cursor: not-allowed;
79
+ }
80
+
81
+ [data-drag-handle] {
82
+ cursor: grab;
83
+ }
84
+
85
+ [data-drag-handle][data-dragging] {
86
+ cursor: grabbing;
87
+ }
88
+
89
+ @layer reset {
90
+ :is(
91
+ .backmatter-studio,
92
+ [data-backmatter-panel],
93
+ [data-base-ui-portal],
94
+ [data-selection-bar]
95
+ )
96
+ a {
97
+ color: inherit;
98
+ }
99
+ }
100
+
101
+ :is(
102
+ .backmatter-studio,
103
+ [data-backmatter-panel],
104
+ [data-base-ui-portal],
105
+ [data-selection-bar]
106
+ )
107
+ a {
108
+ text-decoration: none;
109
+ cursor: default;
110
+ }
111
+
112
+ :is(
113
+ .backmatter-studio,
114
+ [data-backmatter-panel],
115
+ [data-base-ui-portal],
116
+ [data-selection-bar]
117
+ )
118
+ a[href^="http"],
119
+ :is(
120
+ .backmatter-studio,
121
+ [data-backmatter-panel],
122
+ [data-base-ui-portal],
123
+ [data-selection-bar]
124
+ )
125
+ a[href^="//"],
126
+ :is(
127
+ .backmatter-studio,
128
+ [data-backmatter-panel],
129
+ [data-base-ui-portal],
130
+ [data-selection-bar]
131
+ )
132
+ a[target="_blank"] {
133
+ cursor: pointer;
134
+ }
135
+
136
+ :is(
137
+ .backmatter-studio,
138
+ [data-backmatter-panel],
139
+ [data-base-ui-portal],
140
+ [data-selection-bar]
141
+ )
142
+ :focus-visible {
143
+ outline: 2px solid #18181b;
144
+ outline-offset: 2px;
145
+ }
146
+
147
+ html[data-theme="dark"]:has(.backmatter-studio)
148
+ :is(.backmatter-studio, [data-base-ui-portal], [data-selection-bar])
149
+ :focus-visible {
150
+ outline-color: #fafafa;
151
+ }
152
+
153
+ [data-no-ring]:focus-visible {
154
+ outline: none;
155
+ }