@reviewflow/ui 6.25.0 → 6.27.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 +6 -2
- package/src/board/pull-request-row.tsx +57 -18
- package/src/components/branch-diff/diff-change-mark.tsx +86 -0
- package/src/components/branch-diff/use-collapsed-files.ts +3 -0
- package/src/components/checkout-sync-buttons.tsx +76 -0
- package/src/components/checkout-view.tsx +789 -0
- package/src/components/commit-list.tsx +172 -0
- package/src/components/diff-viewer.tsx +1 -0
- package/src/components/omitted-files.tsx +32 -0
- package/src/components/pull-request-commits.tsx +4 -91
- package/src/components/pull-request-diff-view.tsx +1 -25
- package/src/components/pull-request-diff-viewer.tsx +145 -37
- package/src/components/pull-request-panel.tsx +46 -23
- package/src/components/pull-request-repo-name.tsx +27 -0
- package/src/components/pull-request-screen.tsx +34 -26
- package/src/components/remove-worktree-button.tsx +233 -0
- package/src/components/repo-overview.tsx +615 -0
- package/src/index.ts +44 -0
- package/src/prStatus.ts +8 -1
- package/src/services/checkoutSummary.ts +32 -0
- package/src/slots.tsx +8 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reviewflow/ui",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.27.0",
|
|
4
4
|
"description": "react-native pull request screen components. Consumed as SOURCES (no build): exports resolve `default` to ./src/index.ts, which metro transforms like the app's own files; the consuming app's css must @source this package's src so tailwind keeps its classes.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"alouette",
|
|
@@ -42,6 +42,10 @@
|
|
|
42
42
|
"types": "./src/index.ts",
|
|
43
43
|
"default": "./src/index.ts"
|
|
44
44
|
},
|
|
45
|
+
"./diff-viewer": {
|
|
46
|
+
"types": "./src/components/diff-viewer.tsx",
|
|
47
|
+
"default": "./src/components/diff-viewer.tsx"
|
|
48
|
+
},
|
|
45
49
|
"./diff-worker-pool": {
|
|
46
50
|
"types": "./src/components/branch-diff/diff-worker-pool.tsx",
|
|
47
51
|
"browser": "./src/components/branch-diff/diff-worker-pool.web.tsx",
|
|
@@ -73,7 +77,7 @@
|
|
|
73
77
|
"@lezer/markdown": "1.7.2",
|
|
74
78
|
"@pierre/diffs": "1.5.2",
|
|
75
79
|
"@pierre/trees": "1.0.0-beta.6",
|
|
76
|
-
"@reviewflow/sdk": "6.
|
|
80
|
+
"@reviewflow/sdk": "6.27.0",
|
|
77
81
|
"@tanstack/markdown": "0.0.13",
|
|
78
82
|
"alouette-icons": "12.0.0",
|
|
79
83
|
"gemoji": "8.1.0",
|
|
@@ -31,7 +31,49 @@ const stopRowPress = (event: GestureResponderEvent): void => {
|
|
|
31
31
|
event.stopPropagation();
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
export function PullRequestMetaSeparator(): ReactNode {
|
|
35
|
+
return <Text className="text-xs text-muted">·</Text>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const metaSeparator = <PullRequestMetaSeparator />;
|
|
39
|
+
|
|
40
|
+
export interface PullRequestRowFrameProps {
|
|
41
|
+
ariaLabel: string;
|
|
42
|
+
onPress: () => void;
|
|
43
|
+
/** the meta line's items, joined with `PullRequestMetaSeparator` */
|
|
44
|
+
meta: ReactNode;
|
|
45
|
+
title: string;
|
|
46
|
+
/** beside the title, e.g. who it waits on */
|
|
47
|
+
titleAside?: ReactNode;
|
|
48
|
+
/** under the title */
|
|
49
|
+
children?: ReactNode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The layout every pull request row shares, whatever its source: a meta line,
|
|
54
|
+
* the title, then what the source adds.
|
|
55
|
+
*/
|
|
56
|
+
export function PullRequestRowFrame({
|
|
57
|
+
ariaLabel,
|
|
58
|
+
onPress,
|
|
59
|
+
meta,
|
|
60
|
+
title,
|
|
61
|
+
titleAside,
|
|
62
|
+
children,
|
|
63
|
+
}: PullRequestRowFrameProps): ReactNode {
|
|
64
|
+
return (
|
|
65
|
+
<PressableListItem role="link" aria-label={ariaLabel} onPress={onPress}>
|
|
66
|
+
<View className="min-w-0 gap-xxs">
|
|
67
|
+
<View className="flex-row flex-wrap items-center gap-xs">{meta}</View>
|
|
68
|
+
<View className="flex-row flex-wrap items-baseline gap-xs">
|
|
69
|
+
<Text className="font-body-bold">{title}</Text>
|
|
70
|
+
{titleAside}
|
|
71
|
+
</View>
|
|
72
|
+
{children}
|
|
73
|
+
</View>
|
|
74
|
+
</PressableListItem>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
35
77
|
|
|
36
78
|
const statusSeparator = <Text className="text-sm text-muted">·</Text>;
|
|
37
79
|
|
|
@@ -59,7 +101,7 @@ function MetaLine({ pr }: { pr: ReviewflowBoardPr }): ReactNode {
|
|
|
59
101
|
const flowDate = reviewflowPrFlowDate(pr);
|
|
60
102
|
|
|
61
103
|
return (
|
|
62
|
-
|
|
104
|
+
<>
|
|
63
105
|
<Text className="font-mono text-xs text-muted">
|
|
64
106
|
{`${pr.ownerRepo}#${String(pr.number)}`}
|
|
65
107
|
</Text>
|
|
@@ -95,7 +137,7 @@ function MetaLine({ pr }: { pr: ReviewflowBoardPr }): ReactNode {
|
|
|
95
137
|
<Text className="text-xs text-muted">{flowDate}</Text>
|
|
96
138
|
</>
|
|
97
139
|
)}
|
|
98
|
-
|
|
140
|
+
</>
|
|
99
141
|
);
|
|
100
142
|
}
|
|
101
143
|
|
|
@@ -161,24 +203,21 @@ export function PullRequestRow({
|
|
|
161
203
|
const extra = renderRowExtra?.(row);
|
|
162
204
|
|
|
163
205
|
return (
|
|
164
|
-
<
|
|
165
|
-
|
|
166
|
-
|
|
206
|
+
<PullRequestRowFrame
|
|
207
|
+
ariaLabel={`Open ${pr.ownerRepo}#${String(pr.number)} ${pr.title}`}
|
|
208
|
+
meta={<MetaLine pr={pr} />}
|
|
209
|
+
title={pr.title}
|
|
210
|
+
titleAside={
|
|
211
|
+
owners === null ? null : (
|
|
212
|
+
<Text className="text-sm text-muted">{owners}</Text>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
167
215
|
onPress={() => {
|
|
168
216
|
onOpenPullRequest({ ownerRepo: pr.ownerRepo, number: pr.number });
|
|
169
217
|
}}
|
|
170
218
|
>
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
<Text className="font-body-bold">{pr.title}</Text>
|
|
175
|
-
{owners === null ? null : (
|
|
176
|
-
<Text className="text-sm text-muted">{owners}</Text>
|
|
177
|
-
)}
|
|
178
|
-
</View>
|
|
179
|
-
<StatusLine pr={pr} context={context} />
|
|
180
|
-
{extra === undefined || extra === null ? null : extra}
|
|
181
|
-
</View>
|
|
182
|
-
</PressableListItem>
|
|
219
|
+
<StatusLine pr={pr} context={context} />
|
|
220
|
+
{extra === undefined || extra === null ? null : extra}
|
|
221
|
+
</PullRequestRowFrame>
|
|
183
222
|
);
|
|
184
223
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { FileDiffMetadata } from "@pierre/diffs";
|
|
2
|
+
import type { Accent, SVGIconElement } from "alouette";
|
|
3
|
+
import { AccentScope, Icon, Tooltip, View } from "alouette";
|
|
4
|
+
import { ArrowSquareRightRegularIcon } from "alouette-icons/phosphor-icons/ArrowSquareRight";
|
|
5
|
+
import { MinusSquareRegularIcon } from "alouette-icons/phosphor-icons/MinusSquare";
|
|
6
|
+
import { PencilSimpleLineRegularIcon } from "alouette-icons/phosphor-icons/PencilSimpleLine";
|
|
7
|
+
import { PlusSquareRegularIcon } from "alouette-icons/phosphor-icons/PlusSquare";
|
|
8
|
+
import type { ReactNode } from "react";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* For a pierre file's `unsafeCSS`, with `DiffChangeMark` in its header prefix:
|
|
12
|
+
* pierre draws its change icon inside the file's shadow root, where nothing of
|
|
13
|
+
* ours can give it a tooltip.
|
|
14
|
+
*/
|
|
15
|
+
export const hidePierreChangeIcon = "[data-change-icon] { display: none; }";
|
|
16
|
+
|
|
17
|
+
interface ChangeMarkProps {
|
|
18
|
+
label: string;
|
|
19
|
+
accent: Accent;
|
|
20
|
+
icon: SVGIconElement;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function ChangeMark({ label, accent, icon }: ChangeMarkProps): ReactNode {
|
|
24
|
+
return (
|
|
25
|
+
<Tooltip
|
|
26
|
+
content={label}
|
|
27
|
+
render={(triggerProps) => (
|
|
28
|
+
<View role="img" aria-label={label} {...triggerProps}>
|
|
29
|
+
<AccentScope accent={accent}>
|
|
30
|
+
<Icon icon={icon} size={16} className="text-accent" />
|
|
31
|
+
</AccentScope>
|
|
32
|
+
</View>
|
|
33
|
+
)}
|
|
34
|
+
/>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface DiffChangeMarkProps {
|
|
39
|
+
type: FileDiffMetadata["type"];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function DiffChangeMark({ type }: DiffChangeMarkProps): ReactNode {
|
|
43
|
+
if (type === "new") {
|
|
44
|
+
return (
|
|
45
|
+
<ChangeMark
|
|
46
|
+
label="Added"
|
|
47
|
+
accent="success"
|
|
48
|
+
icon={<PlusSquareRegularIcon />}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (type === "deleted") {
|
|
53
|
+
return (
|
|
54
|
+
<ChangeMark
|
|
55
|
+
label="Deleted"
|
|
56
|
+
accent="danger"
|
|
57
|
+
icon={<MinusSquareRegularIcon />}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (type === "rename-pure") {
|
|
62
|
+
return (
|
|
63
|
+
<ChangeMark
|
|
64
|
+
label="Renamed, content unchanged"
|
|
65
|
+
accent="warning"
|
|
66
|
+
icon={<ArrowSquareRightRegularIcon />}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (type === "rename-changed") {
|
|
71
|
+
return (
|
|
72
|
+
<ChangeMark
|
|
73
|
+
label="Renamed and modified"
|
|
74
|
+
accent="warning"
|
|
75
|
+
icon={<ArrowSquareRightRegularIcon />}
|
|
76
|
+
/>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return (
|
|
80
|
+
<ChangeMark
|
|
81
|
+
label="Modified"
|
|
82
|
+
accent="info"
|
|
83
|
+
icon={<PencilSimpleLineRegularIcon />}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -18,6 +18,9 @@ interface CollapsedState {
|
|
|
18
18
|
* not the file list: the viewer parses the patch and is loaded lazily. Stored
|
|
19
19
|
* as a default plus exceptions, so either "all" needs no list of files and a
|
|
20
20
|
* file new to a refreshed diff follows the last "all" pressed.
|
|
21
|
+
*
|
|
22
|
+
* @deprecated `DiffViewer` and the pull request diff take a
|
|
23
|
+
* `PullRequestCollapse`: use `usePullRequestCollapse`.
|
|
21
24
|
*/
|
|
22
25
|
export const useCollapsedFiles = (): CollapsedFiles => {
|
|
23
26
|
const [state, setState] = useState<CollapsedState>({
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { CheckoutStatus } from "@reviewflow/sdk";
|
|
2
|
+
import { pullRefusal, pushRefusal } from "@reviewflow/sdk";
|
|
3
|
+
import { ActionButton } from "alouette";
|
|
4
|
+
import { CloudArrowDownRegularIcon } from "alouette-icons/phosphor-icons/CloudArrowDown";
|
|
5
|
+
import { CloudArrowUpRegularIcon } from "alouette-icons/phosphor-icons/CloudArrowUp";
|
|
6
|
+
import type { ReactNode } from "react";
|
|
7
|
+
import { errorToMessage } from "../services/errorToMessage.ts";
|
|
8
|
+
|
|
9
|
+
export interface CheckoutSyncButtonsProps {
|
|
10
|
+
status: CheckoutStatus;
|
|
11
|
+
hasOrigin: boolean;
|
|
12
|
+
/** what the user calls this checkout, so each label names one of several */
|
|
13
|
+
name: string;
|
|
14
|
+
onPull: () => Promise<void>;
|
|
15
|
+
onPush: () => Promise<void>;
|
|
16
|
+
/** after every press, succeeded or not: git state moved either way, or may have */
|
|
17
|
+
onDone: () => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Pull and Push appear only when `pullRefusal` / `pushRefusal` would not refuse
|
|
22
|
+
* them — the server asks the same question before running git, so a button is
|
|
23
|
+
* never offered for a press the server turns down on its own terms. A failure
|
|
24
|
+
* is git's reason, shown under the button.
|
|
25
|
+
*/
|
|
26
|
+
export function CheckoutSyncButtons({
|
|
27
|
+
status,
|
|
28
|
+
hasOrigin,
|
|
29
|
+
name,
|
|
30
|
+
onPull,
|
|
31
|
+
onPush,
|
|
32
|
+
onDone,
|
|
33
|
+
}: CheckoutSyncButtonsProps): ReactNode {
|
|
34
|
+
const run = async (action: () => Promise<void>): Promise<void> => {
|
|
35
|
+
try {
|
|
36
|
+
await action();
|
|
37
|
+
} finally {
|
|
38
|
+
onDone();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<>
|
|
44
|
+
{pullRefusal(status) === null ? (
|
|
45
|
+
<ActionButton
|
|
46
|
+
text={`Pull ${String(status.behind)}`}
|
|
47
|
+
icon={<CloudArrowDownRegularIcon />}
|
|
48
|
+
accent="brand"
|
|
49
|
+
variant="tonal"
|
|
50
|
+
size="sm"
|
|
51
|
+
aria-label={`Pull ${name}`}
|
|
52
|
+
errorMessageVariant="flat"
|
|
53
|
+
errorToMessage={errorToMessage}
|
|
54
|
+
onPress={() => run(onPull)}
|
|
55
|
+
/>
|
|
56
|
+
) : null}
|
|
57
|
+
{pushRefusal(status, hasOrigin) === null ? (
|
|
58
|
+
<ActionButton
|
|
59
|
+
text={
|
|
60
|
+
status.upstream === null
|
|
61
|
+
? "Publish"
|
|
62
|
+
: `Push ${String(status.ahead)}`
|
|
63
|
+
}
|
|
64
|
+
icon={<CloudArrowUpRegularIcon />}
|
|
65
|
+
accent="brand"
|
|
66
|
+
variant="tonal"
|
|
67
|
+
size="sm"
|
|
68
|
+
aria-label={`Push ${name}`}
|
|
69
|
+
errorMessageVariant="flat"
|
|
70
|
+
errorToMessage={errorToMessage}
|
|
71
|
+
onPress={() => run(onPush)}
|
|
72
|
+
/>
|
|
73
|
+
) : null}
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
76
|
+
}
|