@gogitcms/design-system 0.16.0-next.5 → 0.16.0-next.6
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 +1 -1
- package/src/components/Notifications.tsx +146 -28
- package/src/icons.ts +1 -0
- package/src/index.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gogitcms/design-system",
|
|
3
|
-
"version": "0.16.0-next.
|
|
3
|
+
"version": "0.16.0-next.6",
|
|
4
4
|
"main": "src/index.ts",
|
|
5
5
|
"types": "src/index.ts",
|
|
6
6
|
"// exports": "The root entry is the react-native source the SPAs, desktop app and mobile app consume. ./web is the plain-DOM build for server-rendered surfaces (the Astro docs site) that don't run react-native-web, and ./css ships the tokens as custom properties. The trailing wildcard keeps deep paths resolvable — plugin bundling and the Vite aliases reach into src/ directly.",
|
|
@@ -4,10 +4,11 @@ import { useTheme, useThemeMode, ThemeScope } from "../ThemeProvider";
|
|
|
4
4
|
import { lightTheme, darkTheme } from "../theme";
|
|
5
5
|
import { Text } from "./Text";
|
|
6
6
|
import { Icon } from "./Icon";
|
|
7
|
+
import type { IconName } from "../icons";
|
|
7
8
|
import { Spinner } from "./Spinner";
|
|
8
9
|
|
|
9
10
|
// One notification, shaped for display. Transport-agnostic: the frontend/mobile
|
|
10
|
-
// apps map GraphQL rows and the desktop
|
|
11
|
+
// apps map GraphQL rows and the desktop and dashboard map REST rows into this.
|
|
11
12
|
export type NotificationItem = {
|
|
12
13
|
id: string;
|
|
13
14
|
title: string;
|
|
@@ -16,8 +17,39 @@ export type NotificationItem = {
|
|
|
16
17
|
read: boolean;
|
|
17
18
|
createdAt: string; // ISO 8601
|
|
18
19
|
author?: string; // display name/email of who caused it, when known
|
|
20
|
+
// What produced it — picks the icon. Unknown kinds fall back to the bell.
|
|
21
|
+
kind?: NotificationKind | string;
|
|
22
|
+
// Where it happened, for a list that spans several repositories/projects.
|
|
23
|
+
// Omit whichever the list is already scoped to.
|
|
24
|
+
repository?: string;
|
|
25
|
+
project?: string;
|
|
19
26
|
};
|
|
20
27
|
|
|
28
|
+
export type NotificationKind = "import" | "export" | "merge" | "change_request" | "member" | "branch" | "project";
|
|
29
|
+
|
|
30
|
+
// The icon for a notification kind. Exported so hosts that draw their own rows
|
|
31
|
+
// (a scope picker's section headers, say) agree with the list.
|
|
32
|
+
export function notificationKindIcon(kind?: string): IconName {
|
|
33
|
+
switch (kind) {
|
|
34
|
+
case "import":
|
|
35
|
+
return "download";
|
|
36
|
+
case "export":
|
|
37
|
+
return "upload";
|
|
38
|
+
case "merge":
|
|
39
|
+
return "gitMerge";
|
|
40
|
+
case "change_request":
|
|
41
|
+
return "gitPullRequest";
|
|
42
|
+
case "member":
|
|
43
|
+
return "user";
|
|
44
|
+
case "branch":
|
|
45
|
+
return "gitBranch";
|
|
46
|
+
case "project":
|
|
47
|
+
return "folder";
|
|
48
|
+
default:
|
|
49
|
+
return "bell";
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
21
53
|
// relativeTime renders a compact age ("just now", "5m", "3h", "2d").
|
|
22
54
|
function relativeTime(iso: string): string {
|
|
23
55
|
const then = Date.parse(iso);
|
|
@@ -32,7 +64,65 @@ function relativeTime(iso: string): string {
|
|
|
32
64
|
return `${days}d`;
|
|
33
65
|
}
|
|
34
66
|
|
|
35
|
-
//
|
|
67
|
+
// InlineCode renders a sentence whose backticked spans (`main`, `#12`'s
|
|
68
|
+
// branch) are set in the mono face, so a title like "Created `zyx` branch"
|
|
69
|
+
// reads as prose around a name rather than as punctuation. An unmatched
|
|
70
|
+
// backtick is left as typed.
|
|
71
|
+
export function InlineCode({
|
|
72
|
+
text,
|
|
73
|
+
variant = "sm",
|
|
74
|
+
weight,
|
|
75
|
+
color,
|
|
76
|
+
numberOfLines,
|
|
77
|
+
testID,
|
|
78
|
+
}: {
|
|
79
|
+
text: string;
|
|
80
|
+
variant?: "sm" | "body" | "monoSm" | "xs";
|
|
81
|
+
weight?: "regular" | "medium" | "semibold";
|
|
82
|
+
color?: string;
|
|
83
|
+
numberOfLines?: number;
|
|
84
|
+
testID?: string;
|
|
85
|
+
}) {
|
|
86
|
+
const t = useTheme();
|
|
87
|
+
const parts = text.split("`");
|
|
88
|
+
// An even number of parts means an odd number of backticks: not a pair, so
|
|
89
|
+
// render the text untouched.
|
|
90
|
+
if (parts.length % 2 === 0) {
|
|
91
|
+
return (
|
|
92
|
+
<Text variant={variant} weight={weight} color={color} numberOfLines={numberOfLines} testID={testID}>
|
|
93
|
+
{text}
|
|
94
|
+
</Text>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return (
|
|
98
|
+
<Text variant={variant} weight={weight} color={color} numberOfLines={numberOfLines} testID={testID}>
|
|
99
|
+
{parts.map((part, i) =>
|
|
100
|
+
i % 2 === 1 ? (
|
|
101
|
+
<Text
|
|
102
|
+
key={i}
|
|
103
|
+
variant={variant}
|
|
104
|
+
weight={weight}
|
|
105
|
+
color={color}
|
|
106
|
+
mono
|
|
107
|
+
style={{
|
|
108
|
+
backgroundColor: t.color.surfaceSunken,
|
|
109
|
+
borderRadius: t.radius.sm,
|
|
110
|
+
paddingHorizontal: 3,
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
113
|
+
{part}
|
|
114
|
+
</Text>
|
|
115
|
+
) : (
|
|
116
|
+
part
|
|
117
|
+
),
|
|
118
|
+
)}
|
|
119
|
+
</Text>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// A single notification row: a kind icon, the title (unread rows in bold, error
|
|
124
|
+
// rows tinted), the body, and a context line — where it happened and who did
|
|
125
|
+
// it — with the age on the right.
|
|
36
126
|
function NotificationRow({
|
|
37
127
|
item,
|
|
38
128
|
divided,
|
|
@@ -44,12 +134,16 @@ function NotificationRow({
|
|
|
44
134
|
}) {
|
|
45
135
|
const t = useTheme();
|
|
46
136
|
const titleColor = item.level === "error" ? t.color.diffDelFg : t.color.textPrimary;
|
|
137
|
+
const context = [item.repository, item.project].filter(Boolean).join(" · ");
|
|
138
|
+
const meta = [context, item.author ? `by ${item.author}` : ""].filter(Boolean).join(" · ");
|
|
47
139
|
return (
|
|
48
140
|
<Pressable
|
|
49
141
|
onPress={onPress}
|
|
142
|
+
disabled={!onPress}
|
|
143
|
+
testID={`notification-${item.id}`}
|
|
50
144
|
style={({ pressed, hovered }: { pressed: boolean; hovered?: boolean }) => ({
|
|
51
145
|
flexDirection: "row",
|
|
52
|
-
gap: t.space(
|
|
146
|
+
gap: t.space(3),
|
|
53
147
|
paddingHorizontal: t.space(4),
|
|
54
148
|
paddingVertical: t.space(3),
|
|
55
149
|
borderBottomWidth: divided ? 1 : 0,
|
|
@@ -57,33 +151,57 @@ function NotificationRow({
|
|
|
57
151
|
backgroundColor: hovered || pressed ? t.color.surfaceHover : "transparent",
|
|
58
152
|
})}
|
|
59
153
|
>
|
|
60
|
-
<View
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
<
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
color={
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
154
|
+
<View style={{ width: 28, height: 28, alignItems: "center", justifyContent: "center", position: "relative" }}>
|
|
155
|
+
<View
|
|
156
|
+
style={{
|
|
157
|
+
width: 28,
|
|
158
|
+
height: 28,
|
|
159
|
+
borderRadius: t.radius.pill,
|
|
160
|
+
alignItems: "center",
|
|
161
|
+
justifyContent: "center",
|
|
162
|
+
backgroundColor: item.level === "error" ? t.color.diffDelBg : t.color.surfaceSunken,
|
|
163
|
+
}}
|
|
164
|
+
>
|
|
165
|
+
<Icon
|
|
166
|
+
name={notificationKindIcon(item.kind)}
|
|
167
|
+
size={14}
|
|
168
|
+
color={item.level === "error" ? t.color.diffDelFg : t.color.textSecondary}
|
|
169
|
+
/>
|
|
170
|
+
</View>
|
|
171
|
+
{/* Unread dot, riding on the icon's corner. */}
|
|
172
|
+
{item.read ? null : (
|
|
173
|
+
<View
|
|
174
|
+
testID="notification-unread"
|
|
175
|
+
style={{
|
|
176
|
+
position: "absolute",
|
|
177
|
+
top: -1,
|
|
178
|
+
right: -1,
|
|
179
|
+
width: 8,
|
|
180
|
+
height: 8,
|
|
181
|
+
borderRadius: t.radius.pill,
|
|
182
|
+
backgroundColor: t.color.diffDelFg,
|
|
183
|
+
borderWidth: 1,
|
|
184
|
+
borderColor: t.color.surfaceRaised,
|
|
185
|
+
}}
|
|
186
|
+
/>
|
|
187
|
+
)}
|
|
188
|
+
</View>
|
|
189
|
+
<View style={{ flex: 1, gap: 2, minWidth: 0 }}>
|
|
190
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: t.space(2) }}>
|
|
191
|
+
<View style={{ flex: 1, minWidth: 0 }}>
|
|
192
|
+
<InlineCode
|
|
193
|
+
text={item.title}
|
|
194
|
+
variant="sm"
|
|
195
|
+
weight={item.read ? "medium" : "semibold"}
|
|
196
|
+
color={titleColor}
|
|
197
|
+
numberOfLines={2}
|
|
198
|
+
/>
|
|
199
|
+
</View>
|
|
80
200
|
<Text variant="monoSm" color="tertiary">{relativeTime(item.createdAt)}</Text>
|
|
81
201
|
</View>
|
|
82
|
-
{item.body ?
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
{item.author ? (
|
|
86
|
-
<Text variant="monoSm" color="tertiary" numberOfLines={1}>{`by ${item.author}`}</Text>
|
|
202
|
+
{item.body ? <InlineCode text={item.body} variant="monoSm" color={t.color.textSecondary} numberOfLines={2} /> : null}
|
|
203
|
+
{meta ? (
|
|
204
|
+
<Text variant="monoSm" color="tertiary" numberOfLines={1}>{meta}</Text>
|
|
87
205
|
) : null}
|
|
88
206
|
</View>
|
|
89
207
|
</Pressable>
|
package/src/icons.ts
CHANGED
|
@@ -28,6 +28,7 @@ export const icons = {
|
|
|
28
28
|
chevronDown: ["m6 9 6 6 6-6"],
|
|
29
29
|
chevronUp: ["m18 15-6-6-6 6"],
|
|
30
30
|
folder: ["M3 6a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"],
|
|
31
|
+
user: ["M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2", "M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z"],
|
|
31
32
|
building: ["M3 21h18", "M6 21V7l7-4v18", "M18 21V11l-5-3"],
|
|
32
33
|
menu: ["M3 6h18", "M3 12h18", "M3 18h18"],
|
|
33
34
|
plus: ["M5 12h14", "M12 5v14"],
|
package/src/index.ts
CHANGED
|
@@ -115,9 +115,10 @@ export { Spinner } from "./components/Spinner";
|
|
|
115
115
|
export type { SpinnerProps } from "./components/Spinner";
|
|
116
116
|
|
|
117
117
|
// Notifications
|
|
118
|
-
export { NotificationBell, NotificationList } from "./components/Notifications";
|
|
118
|
+
export { NotificationBell, NotificationList, InlineCode, notificationKindIcon } from "./components/Notifications";
|
|
119
119
|
export type {
|
|
120
120
|
NotificationItem,
|
|
121
|
+
NotificationKind,
|
|
121
122
|
NotificationBellProps,
|
|
122
123
|
NotificationListProps,
|
|
123
124
|
} from "./components/Notifications";
|