@reviewflow/ui 6.24.0 → 6.25.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.
Files changed (31) hide show
  1. package/package.json +17 -2
  2. package/src/components/branch-diff/diff-font.ts +26 -0
  3. package/src/components/branch-diff/diff-themes.ts +53 -1
  4. package/src/components/branch-diff/reviewflow-diff-selection.ts +11 -0
  5. package/src/components/branch-diff/reviewflow-diff-themes.ts +2218 -0
  6. package/src/components/branch-diff/use-diff-settings.tsx +15 -3
  7. package/src/components/branch-diff/whitespace-button.tsx +2 -2
  8. package/src/components/diff-settings-section.tsx +189 -0
  9. package/src/components/diff-theme-preview.tsx +63 -0
  10. package/src/components/file-kind.ts +91 -0
  11. package/src/components/file-type-icon.tsx +70 -0
  12. package/src/components/pull-request-diff-annotations.tsx +5 -2
  13. package/src/components/pull-request-diff-jump.ts +59 -1
  14. package/src/components/pull-request-diff-overview-tree.ts +57 -0
  15. package/src/components/pull-request-diff-overview.tsx +488 -88
  16. package/src/components/pull-request-diff-tree-files.ts +64 -0
  17. package/src/components/pull-request-diff-tree.tsx +232 -0
  18. package/src/components/pull-request-diff-view.tsx +111 -27
  19. package/src/components/pull-request-diff-viewer.tsx +23 -2
  20. package/src/components/pull-request-files.tsx +4 -4
  21. package/src/components/pull-request-panel.tsx +4 -2
  22. package/src/components/pull-request-screen.tsx +16 -4
  23. package/src/components/scroll-parent.ts +20 -0
  24. package/src/components/tooltip-icon-button.tsx +27 -0
  25. package/src/editor/code-editor.tsx +1 -0
  26. package/src/editor/code-token-colors.css +39 -0
  27. package/src/editor/codeHighlight.ts +2 -1
  28. package/src/editor/markdown-editor.css +1 -34
  29. package/src/editor/markdown-editor.tsx +1 -0
  30. package/src/index.ts +6 -6
  31. package/src/palette.ts +22 -0
@@ -10,7 +10,6 @@ import { formatRelativeDate, pluralOf } from "@reviewflow/sdk";
10
10
  import {
11
11
  Button,
12
12
  Code,
13
- IconButton,
14
13
  InfoMessage,
15
14
  Menu,
16
15
  MenuItem,
@@ -54,6 +53,7 @@ import {
54
53
  PullRequestPartPending,
55
54
  PullRequestPartReading,
56
55
  } from "./pull-request-part-notice.tsx";
56
+ import { TooltipIconButton } from "./tooltip-icon-button.tsx";
57
57
 
58
58
  type Scope = "all" | "since";
59
59
 
@@ -130,7 +130,7 @@ function ByCommitButton({
130
130
  onToggle,
131
131
  }: ByCommitButtonProps): ReactNode {
132
132
  return (
133
- <IconButton
133
+ <TooltipIconButton
134
134
  icon={<GitCommitRegularIcon />}
135
135
  size="sm"
136
136
  accent={byCommit ? "brand" : "neutral"}
@@ -173,7 +173,7 @@ function CommitStepper({
173
173
  if (commit === undefined) return null;
174
174
  return (
175
175
  <View className="min-w-0 shrink flex-row items-center gap-xs">
176
- <IconButton
176
+ <TooltipIconButton
177
177
  icon={<CaretLeftRegularIcon />}
178
178
  size="sm"
179
179
  accent="neutral"
@@ -207,7 +207,7 @@ function CommitStepper({
207
207
  />
208
208
  ))}
209
209
  </Menu>
210
- <IconButton
210
+ <TooltipIconButton
211
211
  icon={<CaretRightRegularIcon />}
212
212
  size="sm"
213
213
  accent="neutral"
@@ -1163,7 +1163,8 @@ function PullRequestActions({ detail, onEdited }: EditableProps): ReactNode {
1163
1163
  export interface PullRequestPanelProps extends CandidatesProps {
1164
1164
  /** why the editors stay read-only, when GitHub refused what they offer */
1165
1165
  candidatesError: string | null;
1166
- tab: PullRequestTab;
1166
+ /** absent: the first tab, Review for a reviewer and Conversation for the author */
1167
+ tab?: PullRequestTab;
1167
1168
  onTabChange: (tab: PullRequestTab) => void;
1168
1169
  /** the commit whose diff is open on the Commits tab; absent: the tab holds it */
1169
1170
  openCommit?: string | null;
@@ -1183,7 +1184,7 @@ export function PullRequestPanel({
1183
1184
  candidates,
1184
1185
  candidatesError,
1185
1186
  onEdited,
1186
- tab,
1187
+ tab: tabProp,
1187
1188
  onTabChange,
1188
1189
  openCommit,
1189
1190
  onOpenCommitChange,
@@ -1194,6 +1195,7 @@ export function PullRequestPanel({
1194
1195
  "checks",
1195
1196
  ).value;
1196
1197
  const viewerIsAuthor = isViewerAuthor(detail);
1198
+ const tab = tabProp ?? (viewerIsAuthor ? "timeline" : "review");
1197
1199
  const reviewTab = (
1198
1200
  <Tab
1199
1201
  value="review"
@@ -1,5 +1,6 @@
1
1
  import { pullRequestPartsStaleAfter } from "@reviewflow/sdk";
2
2
  import type {
3
+ DiffFont,
3
4
  DiffWhitespace,
4
5
  PullRequestDetailResponse,
5
6
  PullRequestEdit,
@@ -63,7 +64,7 @@ interface PullRequestBodyProps {
63
64
  ownerRepo: string;
64
65
  number: number;
65
66
  headerStart?: ReactNode;
66
- tab: PullRequestTab;
67
+ tab?: PullRequestTab;
67
68
  onTabChange: (tab: PullRequestTab) => void;
68
69
  openCommit?: string | null;
69
70
  onOpenCommitChange?: (oid: string | null) => void;
@@ -203,8 +204,11 @@ function PullRequestBody({
203
204
  export interface PullRequestScreenProps {
204
205
  ownerRepo: string;
205
206
  number: number;
206
- /** the tab shown; controlled, so an app can keep it in its url */
207
- tab: PullRequestTab;
207
+ /**
208
+ * The tab shown; controlled, so an app can keep it in its url. Absent: the
209
+ * first tab, Review for a reviewer and Conversation for the author.
210
+ */
211
+ tab?: PullRequestTab;
208
212
  onTabChange: (tab: PullRequestTab) => void;
209
213
  /**
210
214
  * The commit whose diff is open on the Commits tab, by oid. Absent: the
@@ -239,8 +243,12 @@ export interface PullRequestScreenProps {
239
243
  * Absent: they live as long as the screen.
240
244
  */
241
245
  draftStore?: DraftStore;
242
- /** the diff's syntax theme family; default `github` */
246
+ /** the diff's syntax theme family; default `reviewflow` */
243
247
  diffTheme?: DiffTheme;
248
+ /** the diff's code font; default `alouette` */
249
+ diffFont?: DiffFont;
250
+ /** the CSS `font-family` of the `custom` diff font */
251
+ diffCustomFont?: string;
244
252
  /** wrap long lines in the diff; default true */
245
253
  diffWordWrap?: boolean;
246
254
  /** whether the diff starts with whitespace changes hidden; the toolbar toggle then owns it */
@@ -271,6 +279,8 @@ export function PullRequestScreen({
271
279
  slots = {},
272
280
  draftStore,
273
281
  diffTheme,
282
+ diffFont,
283
+ diffCustomFont,
274
284
  diffWordWrap,
275
285
  initialWhitespace,
276
286
  diffFileOrder,
@@ -281,6 +291,8 @@ export function PullRequestScreen({
281
291
  <PullRequestSlotsProvider value={slots}>
282
292
  <DiffSettingsProvider
283
293
  theme={diffTheme}
294
+ font={diffFont}
295
+ customFont={diffCustomFont}
284
296
  wordWrap={diffWordWrap}
285
297
  initialWhitespace={initialWhitespace}
286
298
  fileOrder={diffFileOrder}
@@ -41,6 +41,26 @@ export const scrollToTopOf = (
41
41
  container.scrollTop -= hiddenAbove(element, container, stickyTop);
42
42
  };
43
43
 
44
+ /**
45
+ * Scrolls `element`'s scroll parent alone, the least that shows it whole
46
+ * below the `stickyTop` pixels a pinned row covers: the page around it stays
47
+ * where the reader has it, unlike `scrollIntoView`.
48
+ */
49
+ export const revealWithinScrollParent = (
50
+ element: HTMLElement,
51
+ stickyTop: number,
52
+ ): void => {
53
+ const container = scrollParentOf(element);
54
+ if (container === null) return;
55
+ const bounds = container.getBoundingClientRect();
56
+ const { top, bottom } = element.getBoundingClientRect();
57
+ const hidden = bounds.top + stickyTop - top;
58
+ if (hidden > 0) container.scrollTop -= hidden;
59
+ else if (bottom > bounds.bottom) {
60
+ container.scrollTop += bottom - bounds.bottom;
61
+ }
62
+ };
63
+
44
64
  /** As `scrollToTopOf`, only when `element`'s top is hidden above that bar. */
45
65
  export const revealTopOf = (element: HTMLElement, stickyTop: number): void => {
46
66
  const container = scrollParentOf(element);
@@ -0,0 +1,27 @@
1
+ import type { IconButtonProps } from "alouette";
2
+ import { IconButton, Tooltip } from "alouette";
3
+ import type { ReactNode } from "react";
4
+
5
+ /**
6
+ * An icon-only button whose label shows on hover, so its purpose is not left
7
+ * to the glyph. A disabled one shows its reason instead, in the tooltip the
8
+ * button renders itself. To replace by alouette's own once released:
9
+ * https://github.com/christophehurpeau/alouette/pull/448
10
+ */
11
+ export function TooltipIconButton(props: IconButtonProps): ReactNode {
12
+ const { disabledReason, "aria-label": label } = props;
13
+ return (
14
+ <Tooltip
15
+ content={disabledReason ? null : label}
16
+ // the label already names the button: as its description too, a screen reader reads it twice
17
+ render={(triggerProps) => (
18
+ <IconButton
19
+ {...props}
20
+ {...triggerProps}
21
+ aria-describedby={undefined}
22
+ accessibilityHint={undefined}
23
+ />
24
+ )}
25
+ />
26
+ );
27
+ }
@@ -6,6 +6,7 @@ import type { CodeLanguage } from "./codeEditorExtensions.ts";
6
6
  import { codeEditorExtensions } from "./codeEditorExtensions.ts";
7
7
  import { changeBetween, externalUpdate } from "./extensions.ts";
8
8
  import "./markdown-editor.css";
9
+ import "./code-token-colors.css";
9
10
 
10
11
  export interface CodeEditorProps {
11
12
  value: string;
@@ -0,0 +1,39 @@
1
+ /* Generated by build-code-theme.ts from the palette in src/palette.ts. DO NOT EDIT. */
2
+ /* The code tokens of the editors, in fences and in the frontmatter's yaml. */
3
+
4
+ .markdown-editor {
5
+ --md-tok-comment: #63707e;
6
+ --md-tok-keyword: #cd112a;
7
+ --md-tok-string: #064d8e;
8
+ --md-tok-constant: #0c71c1;
9
+ --md-tok-property: #0c71c1;
10
+ --md-tok-type: #986b0c;
11
+ --md-tok-function: #8837df;
12
+ --md-tok-invalid: #cd112a;
13
+ }
14
+
15
+ .dark .markdown-editor {
16
+ --md-tok-comment: #b4bdc7;
17
+ --md-tok-keyword: #f56d68;
18
+ --md-tok-string: #97caf6;
19
+ --md-tok-constant: #47a5f4;
20
+ --md-tok-property: #47a5f4;
21
+ --md-tok-type: #e2b83c;
22
+ --md-tok-function: #af87e8;
23
+ --md-tok-invalid: #f56d68;
24
+ }
25
+
26
+ /* the "system" preference never sets .dark: the palette resolves it with this
27
+ media query, so the dark tokens are repeated here */
28
+ @media (prefers-color-scheme: dark) {
29
+ .system .markdown-editor {
30
+ --md-tok-comment: #b4bdc7;
31
+ --md-tok-keyword: #f56d68;
32
+ --md-tok-string: #97caf6;
33
+ --md-tok-constant: #47a5f4;
34
+ --md-tok-property: #47a5f4;
35
+ --md-tok-type: #e2b83c;
36
+ --md-tok-function: #af87e8;
37
+ --md-tok-invalid: #f56d68;
38
+ }
39
+ }
@@ -13,7 +13,8 @@ import { tags } from "@lezer/highlight";
13
13
  * **Every style is scoped to a nested language**: `@lezer/markdown` tags its
14
14
  * own nodes too (`heading1`, `strong`), and an unscoped style would paint prose
15
15
  * a second time over the `md-*` classes. Classes, not colours: the palette is
16
- * in `markdown-editor.css`, switched with the theme.
16
+ * in `code-token-colors.css`, generated with the diff's reviewflow theme
17
+ * (`build-code-theme.ts`), switched with the theme.
17
18
  *
18
19
  * Coarse on purpose — a parent tag catches its children, so a language emitting
19
20
  * a tag nobody listed still lands somewhere sensible. Ported from
@@ -28,14 +28,7 @@
28
28
  --md-frontmatter-bg: color-mix(in srgb, var(--color-muted) 7%, transparent);
29
29
  --md-quote-bg: color-mix(in srgb, var(--color-muted) 6%, transparent);
30
30
 
31
- --md-tok-comment: #6e7781;
32
- --md-tok-keyword: #cf222e;
33
- --md-tok-string: #0a3069;
34
- --md-tok-constant: #0550ae;
35
- --md-tok-property: #0550ae;
36
- --md-tok-type: #116329;
37
- --md-tok-function: #8250df;
38
- --md-tok-invalid: #82071e;
31
+ /* the --md-tok-* colors are generated, in code-token-colors.css */
39
32
 
40
33
  /* fills a column the caller grew; the editor still sizes to its content, and
41
34
  the page — the app layout is the scroll container — scrolls a long one */
@@ -53,32 +46,6 @@
53
46
  transition: border-color 150ms ease-in;
54
47
  }
55
48
 
56
- .dark .markdown-editor {
57
- --md-tok-comment: #8b949e;
58
- --md-tok-keyword: #ff7b72;
59
- --md-tok-string: #a5d6ff;
60
- --md-tok-constant: #79c0ff;
61
- --md-tok-property: #79c0ff;
62
- --md-tok-type: #7ee787;
63
- --md-tok-function: #d2a8ff;
64
- --md-tok-invalid: #f85149;
65
- }
66
-
67
- /* the "system" preference never sets .dark: the palette resolves it with this
68
- media query, so the dark tokens are repeated here */
69
- @media (prefers-color-scheme: dark) {
70
- .system .markdown-editor {
71
- --md-tok-comment: #8b949e;
72
- --md-tok-keyword: #ff7b72;
73
- --md-tok-string: #a5d6ff;
74
- --md-tok-constant: #79c0ff;
75
- --md-tok-property: #79c0ff;
76
- --md-tok-type: #7ee787;
77
- --md-tok-function: #d2a8ff;
78
- --md-tok-invalid: #f85149;
79
- }
80
- }
81
-
82
49
  .markdown-editor:hover {
83
50
  border-color: var(--color-interactive-outlined-hover);
84
51
  }
@@ -32,6 +32,7 @@ import {
32
32
  markdownEditorExtensions,
33
33
  } from "./extensions.ts";
34
34
  import "./markdown-editor.css";
35
+ import "./code-token-colors.css";
35
36
 
36
37
  interface FormatButtonProps {
37
38
  icon: SVGIconElement;
package/src/index.ts CHANGED
@@ -34,18 +34,18 @@ export {
34
34
  isDiffFileOrder,
35
35
  } from "./components/branch-diff/use-diff-settings.tsx";
36
36
  export type { DiffFileOrder } from "./components/branch-diff/use-diff-settings.tsx";
37
- export type { DiffTheme } from "./components/branch-diff/diff-themes.ts";
38
- export {
39
- diffThemeNames,
40
- diffThemeOptions,
41
- diffThemes,
42
- } from "./components/branch-diff/diff-themes.ts";
37
+ // From the sdk, not `branch-diff/diff-themes.ts`: that one registers the reviewflow themes with
38
+ // @pierre/diffs, which this index would put in every importer's main bundle.
39
+ export type { DiffTheme } from "@reviewflow/sdk";
40
+ export { diffThemeNames, diffThemeOptions, diffThemes } from "@reviewflow/sdk";
43
41
  // DiffWorkerPool is the `@reviewflow/ui/diff-worker-pool` subpath, not exported here: its web
44
42
  // variant pulls @pierre/diffs and shiki, which this index would put in every importer's main
45
43
  // bundle. A lazily loaded diff viewer imports the subpath.
46
44
  export type { CollapsedFiles } from "./components/branch-diff/use-collapsed-files.ts";
47
45
  export { useCollapsedFiles } from "./components/branch-diff/use-collapsed-files.ts";
48
46
  export { WhitespaceButton } from "./components/branch-diff/whitespace-button.tsx";
47
+ export type { DiffDisplaySettings } from "./components/diff-settings-section.tsx";
48
+ export { DiffSettingsSection } from "./components/diff-settings-section.tsx";
49
49
  export { CopyableCodeBlock } from "./components/copyable-code-block.tsx";
50
50
  export { PullRequestReviewDraftProvider } from "./services/PullRequestReviewDraftProvider.tsx";
51
51
  export type { DraftStore } from "./services/PullRequestDraftsProvider.tsx";
package/src/palette.ts ADDED
@@ -0,0 +1,22 @@
1
+ import type { AccentName, PaletteSpec } from "alouette/theme-generator";
2
+
3
+ // Hues fitted (in OKLCH) on GitHub Primer's own scales, so reviewflow reads as
4
+ // a companion to the GitHub UI it augments: green brand and success, Primer
5
+ // blue for info, its slightly pink red, its amber-to-cream yellow ramp, and the
6
+ // blue-tinted grays instead of alouette's neutral ones.
7
+ // Read by the app's `build-theme.ts` (its alouette palette) and by this
8
+ // package's `build-code-theme.ts` (the reviewflow code theme).
9
+ export const reviewflowPalette = {
10
+ grayscale: { type: "grayscale", hue: 250, intensity: 0.18 },
11
+ brand: { type: "accent", hue: 148, hueHi: 152 },
12
+ success: { type: "accent", hue: 148, hueHi: 152 },
13
+ danger: { type: "accent", hue: 21, hueHi: 26 },
14
+ info: { type: "accent", hue: 260, hueHi: 242 },
15
+ warning: { type: "brightAccent", hue: 52, hueHi: 99 },
16
+ } as const satisfies Partial<Record<AccentName, PaletteSpec>>;
17
+
18
+ // Hues alouette has no accent for, used by the code theme only: Primer's
19
+ // purple, for functions as GitHub colors them, its tints pinker as Primer's.
20
+ export const reviewflowCodePalette = {
21
+ purple: { type: "accent", hue: 295, hueHi: 305, intensity: 0.85 },
22
+ } as const satisfies Record<string, PaletteSpec>;