@reviewflow/ui 0.0.0-stage → 6.22.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 (92) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +95 -2
  3. package/package.json +101 -3
  4. package/src/board/bucket-section.tsx +94 -0
  5. package/src/board/dataSource.ts +44 -0
  6. package/src/board/facts.ts +183 -0
  7. package/src/board/index.ts +12 -0
  8. package/src/board/pull-request-board.tsx +239 -0
  9. package/src/board/pull-request-row.tsx +181 -0
  10. package/src/components/branch-diff/diff-themes.ts +6 -0
  11. package/src/components/branch-diff/diff-worker-pool.tsx +12 -0
  12. package/src/components/branch-diff/diff-worker-pool.web.tsx +56 -0
  13. package/src/components/branch-diff/diff-worker.ts +7 -0
  14. package/src/components/branch-diff/use-collapsed-files.ts +49 -0
  15. package/src/components/branch-diff/use-diff-settings.tsx +97 -0
  16. package/src/components/branch-diff/whitespace-button.tsx +32 -0
  17. package/src/components/copyable-code-block.tsx +101 -0
  18. package/src/components/label-pill.tsx +84 -0
  19. package/src/components/link-in-row.ts +6 -0
  20. package/src/components/markdown-suggestion.tsx +60 -0
  21. package/src/components/markdown-text.tsx +1067 -0
  22. package/src/components/pull-request-actions-merge.tsx +341 -0
  23. package/src/components/pull-request-actions-review.tsx +218 -0
  24. package/src/components/pull-request-checks.tsx +86 -0
  25. package/src/components/pull-request-commit-files.ts +143 -0
  26. package/src/components/pull-request-commits.tsx +221 -0
  27. package/src/components/pull-request-conversation-card.tsx +220 -0
  28. package/src/components/pull-request-conversation-edit.tsx +147 -0
  29. package/src/components/pull-request-conversation-event.tsx +267 -0
  30. package/src/components/pull-request-conversation-person.tsx +28 -0
  31. package/src/components/pull-request-conversation-thread.tsx +158 -0
  32. package/src/components/pull-request-diff-annotations.tsx +443 -0
  33. package/src/components/pull-request-diff-collapse.ts +43 -0
  34. package/src/components/pull-request-diff-data.ts +174 -0
  35. package/src/components/pull-request-diff-impacts-summary.tsx +97 -0
  36. package/src/components/pull-request-diff-impacts.ts +45 -0
  37. package/src/components/pull-request-diff-jump.ts +87 -0
  38. package/src/components/pull-request-diff-lines.ts +103 -0
  39. package/src/components/pull-request-diff-overview.tsx +416 -0
  40. package/src/components/pull-request-diff-view.tsx +438 -0
  41. package/src/components/pull-request-diff-viewer.tsx +1374 -0
  42. package/src/components/pull-request-files.tsx +533 -0
  43. package/src/components/pull-request-markdown-input.tsx +91 -0
  44. package/src/components/pull-request-panel.tsx +1270 -0
  45. package/src/components/pull-request-part-notice.tsx +82 -0
  46. package/src/components/pull-request-reactions.tsx +178 -0
  47. package/src/components/pull-request-review.tsx +164 -0
  48. package/src/components/pull-request-revisions.tsx +273 -0
  49. package/src/components/pull-request-screen.tsx +299 -0
  50. package/src/components/pull-request-suggestion-actions.tsx +201 -0
  51. package/src/components/pull-request-timeline.tsx +270 -0
  52. package/src/components/repository-attachment-banner.tsx +42 -0
  53. package/src/components/scroll-parent.ts +50 -0
  54. package/src/css.d.ts +1 -0
  55. package/src/dataSource.ts +242 -0
  56. package/src/editor/alerts.ts +105 -0
  57. package/src/editor/code-editor.native.tsx +25 -0
  58. package/src/editor/code-editor.tsx +112 -0
  59. package/src/editor/codeEditorExtensions.ts +65 -0
  60. package/src/editor/codeHighlight.ts +71 -0
  61. package/src/editor/commands.ts +175 -0
  62. package/src/editor/decorations.ts +425 -0
  63. package/src/editor/editable-file.tsx +224 -0
  64. package/src/editor/emojiCompletion.ts +110 -0
  65. package/src/editor/extensions.ts +149 -0
  66. package/src/editor/frontmatter.ts +76 -0
  67. package/src/editor/inlineFormat.ts +114 -0
  68. package/src/editor/lineTypes.ts +55 -0
  69. package/src/editor/markdown-editor.css +511 -0
  70. package/src/editor/markdown-editor.native.tsx +29 -0
  71. package/src/editor/markdown-editor.tsx +295 -0
  72. package/src/editor/nodeClasses.ts +69 -0
  73. package/src/index.ts +75 -0
  74. package/src/markdown-github/flatten.ts +49 -0
  75. package/src/markdown-github/html-tokens.ts +93 -0
  76. package/src/markdown-github/pair-html.ts +354 -0
  77. package/src/markdown-github/safe-url.ts +15 -0
  78. package/src/markdown-github/table-columns.ts +23 -0
  79. package/src/markdown-github/toggle-task.ts +164 -0
  80. package/src/nativewind-env.d.ts +1 -0
  81. package/src/prStatus.ts +90 -0
  82. package/src/services/PullRequestDraftsProvider.tsx +179 -0
  83. package/src/services/PullRequestReviewDraftProvider.tsx +178 -0
  84. package/src/services/PullRequestSuggestionBatchProvider.tsx +112 -0
  85. package/src/services/emojify.ts +13 -0
  86. package/src/services/errorToMessage.ts +6 -0
  87. package/src/services/expiringDraftStore.ts +102 -0
  88. package/src/services/formatDataAge.ts +12 -0
  89. package/src/services/formatRelativeDate.ts +56 -0
  90. package/src/services/githubImages.tsx +33 -0
  91. package/src/services/isViewerAuthor.ts +7 -0
  92. package/src/slots.tsx +34 -0
@@ -0,0 +1,101 @@
1
+ import type { CodeBlockProps } from "alouette";
2
+ import { CodeBlock, IconButton, View } from "alouette";
3
+ import { CheckRegularIcon } from "alouette-icons/phosphor-icons/Check";
4
+ import { CopyRegularIcon } from "alouette-icons/phosphor-icons/Copy";
5
+ import { WarningRegularIcon } from "alouette-icons/phosphor-icons/Warning";
6
+ import type { ReactNode } from "react";
7
+ import { useState } from "react";
8
+
9
+ type CopyState = "copied" | "failed" | "idle";
10
+
11
+ const copyFeedbackMs = 2000;
12
+
13
+ /**
14
+ * `navigator.clipboard` exists only in a secure context, which the daemon's
15
+ * origins are (`localhost`, `127.0.0.1`, the remote-access tunnel); native has
16
+ * no `navigator.clipboard` at all, so there the block renders without a button
17
+ * rather than with one that cannot work.
18
+ */
19
+ const clipboard = (): Clipboard | undefined =>
20
+ // oxlint-disable-next-line node-js/no-unsupported-features/node-builtins -- this runs in a browser or react-native, never node
21
+ typeof navigator === "undefined" ? undefined : navigator.clipboard;
22
+
23
+ function CopyButton({ text }: { text: string }): ReactNode {
24
+ const [state, setState] = useState<CopyState>("idle");
25
+ const target = clipboard();
26
+ if (target === undefined) return null;
27
+
28
+ const settle = (next: CopyState): void => {
29
+ setState(next);
30
+ setTimeout(() => {
31
+ setState("idle");
32
+ }, copyFeedbackMs);
33
+ };
34
+
35
+ if (state === "copied") {
36
+ return (
37
+ <IconButton
38
+ accent="success"
39
+ variant="soft"
40
+ size="sm"
41
+ icon={<CheckRegularIcon />}
42
+ aria-label="Copied"
43
+ />
44
+ );
45
+ }
46
+
47
+ if (state === "failed") {
48
+ return (
49
+ <IconButton
50
+ accent="danger"
51
+ variant="soft"
52
+ size="sm"
53
+ icon={<WarningRegularIcon />}
54
+ aria-label="Copy failed"
55
+ />
56
+ );
57
+ }
58
+
59
+ return (
60
+ <IconButton
61
+ variant="soft"
62
+ size="sm"
63
+ icon={<CopyRegularIcon />}
64
+ aria-label="Copy"
65
+ onPress={() => {
66
+ target.writeText(text).then(
67
+ () => {
68
+ settle("copied");
69
+ },
70
+ (error: unknown) => {
71
+ console.error("copy to clipboard failed", error);
72
+ settle("failed");
73
+ },
74
+ );
75
+ }}
76
+ />
77
+ );
78
+ }
79
+
80
+ interface CopyableCodeBlockProps extends Omit<CodeBlockProps, "children"> {
81
+ text: string;
82
+ }
83
+
84
+ /**
85
+ * The button sits beside the block, not over it: alouette's `CodeBlock` is
86
+ * display-only, and a `soft` button floated on the frame has no ground, so the
87
+ * end of a long first line would show through its glyph.
88
+ */
89
+ export function CopyableCodeBlock({
90
+ text,
91
+ ...codeBlockProps
92
+ }: CopyableCodeBlockProps): ReactNode {
93
+ return (
94
+ <View className="flex-row items-end gap-xxs">
95
+ <View className="min-w-0 flex-1">
96
+ <CodeBlock {...codeBlockProps}>{text}</CodeBlock>
97
+ </View>
98
+ <CopyButton text={text} />
99
+ </View>
100
+ );
101
+ }
@@ -0,0 +1,84 @@
1
+ import type { PullRequestLabel } from "@reviewflow/sdk";
2
+ import { Badge, Text, View, useCurrentMode } from "alouette";
3
+ import type { ReactNode } from "react";
4
+ import { emojify } from "../services/emojify.ts";
5
+
6
+ interface Rgb {
7
+ r: number;
8
+ g: number;
9
+ b: number;
10
+ }
11
+
12
+ const rgbOf = (hex: string): Rgb | null => {
13
+ if (!/^[\da-f]{6}$/i.test(hex)) return null;
14
+ const channel = (offset: number): number =>
15
+ Number.parseInt(hex.slice(offset, offset + 2), 16);
16
+ return { r: channel(0), g: channel(2), b: channel(4) };
17
+ };
18
+
19
+ /** Perceived lightness, 0 to 1, with the luma weights of sRGB. */
20
+ const lightnessOf = ({ r, g, b }: Rgb): number =>
21
+ (r * 0.2126 + g * 0.7152 + b * 0.0722) / 255;
22
+
23
+ const rgba = ({ r, g, b }: Rgb, alpha: number): string =>
24
+ `rgba(${String(r)}, ${String(g)}, ${String(b)}, ${String(alpha)})`;
25
+
26
+ const towardWhite = ({ r, g, b }: Rgb, amount: number): Rgb => ({
27
+ r: Math.round(r + (255 - r) * amount),
28
+ g: Math.round(g + (255 - g) * amount),
29
+ b: Math.round(b + (255 - b) * amount),
30
+ });
31
+
32
+ interface PillColors {
33
+ background: string;
34
+ border: string;
35
+ text: string;
36
+ }
37
+
38
+ /**
39
+ * GitHub's own reading of a label colour: solid with dark or light ink on a
40
+ * light page; tinted with the colour lifted toward white on a dark one, where
41
+ * a solid dark-blue pill would vanish into the ground.
42
+ */
43
+ const pillColors = (rgb: Rgb, mode: "dark" | "light"): PillColors => {
44
+ const lightness = lightnessOf(rgb);
45
+ if (mode === "light") {
46
+ return {
47
+ background: rgba(rgb, 1),
48
+ border: lightness > 0.85 ? "rgba(31, 35, 40, 0.15)" : "transparent",
49
+ text: lightness > 0.5 ? "#1f2328" : "#ffffff",
50
+ };
51
+ }
52
+ return {
53
+ background: rgba(rgb, 0.18),
54
+ border: rgba(rgb, 0.3),
55
+ text: rgba(towardWhite(rgb, Math.max(0, 0.6 - lightness)), 1),
56
+ };
57
+ };
58
+
59
+ /**
60
+ * A GitHub label in its repository's colour. The colour is data, read at
61
+ * runtime, so it is the one colour in the app set through `style`: no class
62
+ * can name it. An unreadable colour falls back to a plain badge.
63
+ */
64
+ export function LabelPill({ label }: { label: PullRequestLabel }): ReactNode {
65
+ const mode = useCurrentMode();
66
+ const rgb = rgbOf(label.color);
67
+ if (rgb === null) return <Badge accent="info">{emojify(label.name)}</Badge>;
68
+ const colors = pillColors(rgb, mode === "dark" ? "dark" : "light");
69
+
70
+ return (
71
+ <View
72
+ className="rounded-full border px-sm py-xxs"
73
+ style={{ backgroundColor: colors.background, borderColor: colors.border }}
74
+ >
75
+ <Text
76
+ className="font-body-bold text-sm"
77
+ style={{ color: colors.text }}
78
+ numberOfLines={1}
79
+ >
80
+ {emojify(label.name)}
81
+ </Text>
82
+ </View>
83
+ );
84
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * For an `ExternalLinkText` inside a centred row of facts. It sets itself
3
+ * `self-start`, which pins it to the top of the row beside text and badges, and
4
+ * alouette merges no class there, so the override has to win on importance.
5
+ */
6
+ export const linkInRow = "self-center!";
@@ -0,0 +1,60 @@
1
+ import type { Accent } from "alouette";
2
+ import { Box, Text, View } from "alouette";
3
+ import type { ReactNode } from "react";
4
+ import { createContext, use } from "react";
5
+ import { CopyableCodeBlock } from "./copyable-code-block.tsx";
6
+
7
+ /**
8
+ * The lines a review comment's suggestion replaces. Only a thread knows them,
9
+ * so a suggestion rendered anywhere else stays a code block.
10
+ */
11
+ export const SuggestionOriginalContext = createContext<string[] | null>(null);
12
+
13
+ interface ChangeLineProps {
14
+ accent: Accent;
15
+ sign: string;
16
+ text: string;
17
+ }
18
+
19
+ function ChangeLine({ accent, sign, text }: ChangeLineProps): ReactNode {
20
+ return (
21
+ <Box accent={accent} className="flex-row gap-xs bg-highlight-accent px-xs">
22
+ <Text className="select-none font-mono text-xs text-accent">{sign}</Text>
23
+ <Text className="min-w-0 flex-1 select-auto font-mono text-xs text-sharp">
24
+ {text === "" ? " " : text}
25
+ </Text>
26
+ </Box>
27
+ );
28
+ }
29
+
30
+ /** A ```` ```suggestion ```` block, shown as the change it proposes. */
31
+ export function SuggestedChange({ value }: { value: string }): ReactNode {
32
+ const original = use(SuggestionOriginalContext);
33
+ if (original === null) {
34
+ return <CopyableCodeBlock size="sm" title="suggestion" text={value} />;
35
+ }
36
+ const suggested = value === "" ? [] : value.split("\n");
37
+ return (
38
+ <View className="gap-xxs">
39
+ <Text className="text-xs text-muted">Suggested change</Text>
40
+ <Box className="lowered overflow-hidden rounded-md py-xxs">
41
+ {original.map((line, index) => (
42
+ <ChangeLine
43
+ key={`-${String(index)}`}
44
+ accent="danger"
45
+ sign="-"
46
+ text={line}
47
+ />
48
+ ))}
49
+ {suggested.map((line, index) => (
50
+ <ChangeLine
51
+ key={`+${String(index)}`}
52
+ accent="success"
53
+ sign="+"
54
+ text={line}
55
+ />
56
+ ))}
57
+ </Box>
58
+ </View>
59
+ );
60
+ }