@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.
- package/package.json +17 -2
- package/src/components/branch-diff/diff-font.ts +26 -0
- package/src/components/branch-diff/diff-themes.ts +53 -1
- package/src/components/branch-diff/reviewflow-diff-selection.ts +11 -0
- package/src/components/branch-diff/reviewflow-diff-themes.ts +2218 -0
- package/src/components/branch-diff/use-diff-settings.tsx +15 -3
- package/src/components/branch-diff/whitespace-button.tsx +2 -2
- package/src/components/diff-settings-section.tsx +189 -0
- package/src/components/diff-theme-preview.tsx +63 -0
- package/src/components/file-kind.ts +91 -0
- package/src/components/file-type-icon.tsx +70 -0
- package/src/components/pull-request-diff-annotations.tsx +5 -2
- package/src/components/pull-request-diff-jump.ts +59 -1
- package/src/components/pull-request-diff-overview-tree.ts +57 -0
- package/src/components/pull-request-diff-overview.tsx +488 -88
- package/src/components/pull-request-diff-tree-files.ts +64 -0
- package/src/components/pull-request-diff-tree.tsx +232 -0
- package/src/components/pull-request-diff-view.tsx +111 -27
- package/src/components/pull-request-diff-viewer.tsx +23 -2
- package/src/components/pull-request-files.tsx +4 -4
- package/src/components/pull-request-panel.tsx +4 -2
- package/src/components/pull-request-screen.tsx +16 -4
- package/src/components/scroll-parent.ts +20 -0
- package/src/components/tooltip-icon-button.tsx +27 -0
- package/src/editor/code-editor.tsx +1 -0
- package/src/editor/code-token-colors.css +39 -0
- package/src/editor/codeHighlight.ts +2 -1
- package/src/editor/markdown-editor.css +1 -34
- package/src/editor/markdown-editor.tsx +1 -0
- package/src/index.ts +6 -6
- 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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
|
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
|
-
/**
|
|
207
|
-
|
|
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 `
|
|
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 `
|
|
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-
|
|
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
|
}
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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>;
|