@gogitcms/design-system 0.15.0-next.3

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 (70) hide show
  1. package/README.md +77 -0
  2. package/css/components.css +312 -0
  3. package/css/tokens.css +212 -0
  4. package/package.json +65 -0
  5. package/src/ThemeProvider.tsx +86 -0
  6. package/src/__tests__/ApplyChangesModal.test.tsx +148 -0
  7. package/src/__tests__/BranchImport.test.tsx +46 -0
  8. package/src/__tests__/Button.test.tsx +45 -0
  9. package/src/__tests__/ChangeRequestSummary.test.tsx +57 -0
  10. package/src/__tests__/ContentBrowser.changes.test.tsx +611 -0
  11. package/src/__tests__/ContentBrowser.collab.test.tsx +322 -0
  12. package/src/__tests__/ContentBrowser.collabsync.test.tsx +264 -0
  13. package/src/__tests__/ContentBrowser.contentslot.test.tsx +53 -0
  14. package/src/__tests__/ContentBrowser.discriminator.test.tsx +142 -0
  15. package/src/__tests__/ContentBrowser.drafts.test.tsx +271 -0
  16. package/src/__tests__/ContentBrowser.fields.test.tsx +117 -0
  17. package/src/__tests__/ContentBrowser.media.test.tsx +140 -0
  18. package/src/__tests__/ContentBrowser.mixedcollab.test.tsx +63 -0
  19. package/src/__tests__/ContentBrowser.mixedvalues.test.tsx +38 -0
  20. package/src/__tests__/ContentBrowser.pagination.test.tsx +62 -0
  21. package/src/__tests__/ContentBrowser.previewtab.test.tsx +212 -0
  22. package/src/__tests__/ContentBrowser.reorder.test.tsx +45 -0
  23. package/src/__tests__/ContentBrowser.search.test.tsx +135 -0
  24. package/src/__tests__/ContentBrowser.selectvalue.test.tsx +185 -0
  25. package/src/__tests__/ContentBrowser.staged.test.tsx +132 -0
  26. package/src/__tests__/ContentBrowser.usermenu.test.tsx +56 -0
  27. package/src/__tests__/MediaBrowser.test.tsx +353 -0
  28. package/src/__tests__/MediaField.test.tsx +185 -0
  29. package/src/__tests__/Notifications.test.tsx +69 -0
  30. package/src/__tests__/Onboarding.test.tsx +287 -0
  31. package/src/__tests__/cssTokens.test.ts +201 -0
  32. package/src/__tests__/fieldComponents.test.ts +43 -0
  33. package/src/__tests__/reorder.test.ts +58 -0
  34. package/src/components/ApplyChangesModal.tsx +348 -0
  35. package/src/components/BranchImport.tsx +157 -0
  36. package/src/components/BranchMenu.tsx +192 -0
  37. package/src/components/Button.tsx +131 -0
  38. package/src/components/ChangeDetail.tsx +472 -0
  39. package/src/components/ChangeRequestSummary.tsx +173 -0
  40. package/src/components/CollabField.tsx +388 -0
  41. package/src/components/ContentBrowser.tsx +5073 -0
  42. package/src/components/Icon.tsx +28 -0
  43. package/src/components/Icon.web.tsx +31 -0
  44. package/src/components/Input.tsx +106 -0
  45. package/src/components/MediaBrowser.tsx +766 -0
  46. package/src/components/MediaField.tsx +670 -0
  47. package/src/components/MediaPreview.tsx +91 -0
  48. package/src/components/MediaPreview.web.tsx +169 -0
  49. package/src/components/NavRow.tsx +105 -0
  50. package/src/components/Notifications.tsx +301 -0
  51. package/src/components/Onboarding.tsx +751 -0
  52. package/src/components/ProjectMenu.tsx +124 -0
  53. package/src/components/Segment.tsx +87 -0
  54. package/src/components/Skeleton.tsx +216 -0
  55. package/src/components/Spinner.tsx +44 -0
  56. package/src/components/Text.tsx +85 -0
  57. package/src/components/documentDrafts.ts +213 -0
  58. package/src/components/layout.tsx +284 -0
  59. package/src/components/primitives.tsx +143 -0
  60. package/src/components/reorder.ts +40 -0
  61. package/src/fieldComponents.ts +63 -0
  62. package/src/icons.ts +102 -0
  63. package/src/index.ts +198 -0
  64. package/src/media.ts +229 -0
  65. package/src/theme.ts +116 -0
  66. package/src/web/Button.tsx +110 -0
  67. package/src/web/Icon.tsx +52 -0
  68. package/src/web/Input.tsx +39 -0
  69. package/src/web/index.ts +43 -0
  70. package/src/web/primitives.tsx +119 -0
@@ -0,0 +1,192 @@
1
+ import React, { useState } from "react";
2
+ import { View, Pressable } from "react-native";
3
+ import { useTheme } from "../ThemeProvider";
4
+ import { Text } from "./Text";
5
+ import { Icon } from "./Icon";
6
+ import { Input } from "./Input";
7
+ import { Button } from "./Button";
8
+
9
+ export type BranchRef = { id: string; name: string; protected?: boolean };
10
+
11
+ export type BranchMenuProps = {
12
+ /** The branch currently being edited. */
13
+ current: string;
14
+ /**
15
+ * Every imported branch. Omitted (or a single entry) renders the inert badge —
16
+ * there is nothing to switch to, so no affordance is offered.
17
+ */
18
+ branches?: BranchRef[];
19
+ /** Marked in the list; it is also the base every branch's changes compare to. */
20
+ defaultBranch?: string;
21
+ onSelect?: (branch: BranchRef) => void;
22
+ /** Omitted → no "New branch" row. Rejecting the name should throw. */
23
+ onCreate?: (name: string) => Promise<void> | void;
24
+ /**
25
+ * testID prefix for the trigger and the badge, so two menus in the same bar
26
+ * (source and target) are addressable apart. Defaults to "branch"; a caller
27
+ * that renders two passes a distinct value for the second.
28
+ */
29
+ testID?: string;
30
+ };
31
+
32
+ /**
33
+ * The top bar's branch control. It is a plain badge until the host supplies
34
+ * branches to switch between, at which point it becomes a menu — so a
35
+ * deployment that only ever imports one branch shows no dead affordance.
36
+ *
37
+ * Branch names are metadata and always render mono, per the system's rule that
38
+ * paths, hashes and refs are set in Geist Mono.
39
+ */
40
+ export function BranchMenu({ current, branches, defaultBranch, onSelect, onCreate, testID = "branch" }: BranchMenuProps) {
41
+ const t = useTheme();
42
+ const [open, setOpen] = useState(false);
43
+ const [creating, setCreating] = useState(false);
44
+ const [name, setName] = useState("");
45
+ const [busy, setBusy] = useState(false);
46
+ const [error, setError] = useState<string | null>(null);
47
+
48
+ const switchable = (branches?.length ?? 0) > 1 || !!onCreate;
49
+ const currentProtected = !!branches?.find((b) => b.name === current)?.protected;
50
+
51
+ const badge = (
52
+ <View
53
+ style={{
54
+ flexDirection: "row", alignItems: "center", gap: 6,
55
+ paddingHorizontal: t.space(2), height: t.control.sm,
56
+ borderRadius: t.radius.md, borderWidth: 1, borderColor: t.color.borderDefault,
57
+ }}
58
+ >
59
+ <Icon name="gitBranch" size={13} color={t.color.textSecondary} />
60
+ <Text variant="monoSm" color="secondary">{current}</Text>
61
+ {currentProtected ? (
62
+ <Icon name="lock" size={12} color={t.color.textTertiary} />
63
+ ) : null}
64
+ {switchable ? <Icon name="chevronDown" size={13} color={t.color.textTertiary} /> : null}
65
+ </View>
66
+ );
67
+
68
+ if (!switchable) return <View testID={`${testID}-badge`}>{badge}</View>;
69
+
70
+ const close = () => {
71
+ setOpen(false);
72
+ setCreating(false);
73
+ setName("");
74
+ setError(null);
75
+ };
76
+
77
+ const submit = async () => {
78
+ const trimmed = name.trim();
79
+ if (!trimmed || !onCreate) return;
80
+ setBusy(true);
81
+ setError(null);
82
+ try {
83
+ await onCreate(trimmed);
84
+ close();
85
+ } catch (e: unknown) {
86
+ setError((e as { message?: string })?.message || "Couldn’t create the branch.");
87
+ setBusy(false);
88
+ }
89
+ };
90
+
91
+ const rowStyle = ({ pressed, hovered }: { pressed: boolean; hovered?: boolean }) => ({
92
+ flexDirection: "row" as const, alignItems: "center" as const, gap: t.space(2),
93
+ paddingHorizontal: t.space(3), paddingVertical: t.space(2),
94
+ backgroundColor: hovered || pressed ? t.color.surfaceHover : "transparent",
95
+ });
96
+
97
+ return (
98
+ <View style={{ position: "relative" }}>
99
+ <Pressable
100
+ testID={`${testID}-menu`}
101
+ accessibilityRole="button"
102
+ accessibilityLabel={`Branch ${current}. Switch branch`}
103
+ onPress={() => (open ? close() : setOpen(true))}
104
+ style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
105
+ >
106
+ {badge}
107
+ </Pressable>
108
+ {open ? (
109
+ <View
110
+ testID={`${testID}-menu-list`}
111
+ style={{
112
+ position: "absolute", top: t.control.sm + 4, left: 0, minWidth: 240, zIndex: 20,
113
+ backgroundColor: t.color.surfaceRaised, borderWidth: 1, borderColor: t.color.borderDefault,
114
+ borderRadius: t.radius.md, paddingVertical: t.space(1),
115
+ }}
116
+ >
117
+ {creating ? (
118
+ <View style={{ padding: t.space(3), gap: t.space(2) }}>
119
+ <Input
120
+ label="New branch"
121
+ value={name}
122
+ onChangeText={setName}
123
+ placeholder="feat/my-change"
124
+ mono
125
+ error={!!error}
126
+ testID={`${testID}-create-input`}
127
+ />
128
+ <Text variant="monoSm" color="tertiary">
129
+ {`Branched from ${current}`}
130
+ </Text>
131
+ {error ? <Text variant="monoSm" color={t.color.diffDelFg}>{error}</Text> : null}
132
+ <View style={{ flexDirection: "row", justifyContent: "flex-end", gap: t.space(2) }}>
133
+ <Button title="Cancel" variant="ghost" size="sm" onPress={close} testID={`${testID}-create-cancel`} />
134
+ <Button
135
+ title={busy ? "Creating…" : "Create"}
136
+ variant="primary"
137
+ size="sm"
138
+ disabled={busy || name.trim() === ""}
139
+ onPress={submit}
140
+ testID={`${testID}-create-submit`}
141
+ />
142
+ </View>
143
+ </View>
144
+ ) : (
145
+ <>
146
+ {(branches ?? []).map((b) => {
147
+ const active = b.name === current;
148
+ return (
149
+ <Pressable
150
+ key={b.id}
151
+ testID={`${testID}-option-${b.id}`}
152
+ onPress={() => { close(); if (!active) onSelect?.(b); }}
153
+ style={rowStyle}
154
+ >
155
+ <Icon
156
+ name={active ? "check" : "gitBranch"}
157
+ size={14}
158
+ color={active ? t.color.textPrimary : t.color.textTertiary}
159
+ />
160
+ <Text variant="monoSm" weight={active ? "semibold" : "regular"} style={{ flex: 1 }}>
161
+ {b.name}
162
+ </Text>
163
+ {b.protected ? (
164
+ <Icon name="lock" size={12} color={t.color.textTertiary} />
165
+ ) : null}
166
+ {b.name === defaultBranch ? (
167
+ <Text variant="monoSm" color="tertiary">default</Text>
168
+ ) : null}
169
+ </Pressable>
170
+ );
171
+ })}
172
+ {onCreate ? (
173
+ <Pressable
174
+ testID={`${testID}-create`}
175
+ onPress={() => { setCreating(true); setError(null); }}
176
+ style={(state: { pressed: boolean; hovered?: boolean }) => ({
177
+ ...rowStyle(state),
178
+ borderTopWidth: 1,
179
+ borderTopColor: t.color.borderSubtle,
180
+ })}
181
+ >
182
+ <Icon name="plus" size={14} color={t.color.textSecondary} />
183
+ <Text variant="sm">New branch</Text>
184
+ </Pressable>
185
+ ) : null}
186
+ </>
187
+ )}
188
+ </View>
189
+ ) : null}
190
+ </View>
191
+ );
192
+ }
@@ -0,0 +1,131 @@
1
+ import React from "react";
2
+ import { Pressable, StyleProp, ViewStyle } from "react-native";
3
+ import { useTheme } from "../ThemeProvider";
4
+ import { Text } from "./Text";
5
+ import { Icon } from "./Icon";
6
+ import type { IconName } from "../icons";
7
+
8
+ export type ButtonVariant = "primary" | "default" | "ghost" | "danger";
9
+ export type ButtonSize = "sm" | "md" | "lg";
10
+
11
+ export type ButtonProps = {
12
+ title: string;
13
+ onPress: () => void;
14
+ variant?: ButtonVariant;
15
+ size?: ButtonSize;
16
+ disabled?: boolean;
17
+ iconLeft?: IconName;
18
+ fullWidth?: boolean;
19
+ style?: StyleProp<ViewStyle>;
20
+ testID?: string;
21
+ };
22
+
23
+ export function Button({
24
+ title,
25
+ onPress,
26
+ variant = "default",
27
+ size = "md",
28
+ disabled,
29
+ iconLeft,
30
+ fullWidth,
31
+ style,
32
+ testID,
33
+ }: ButtonProps) {
34
+ const t = useTheme();
35
+ const height = t.control[size];
36
+ const padH = size === "sm" ? t.space(3) : t.space(4);
37
+
38
+ function bg(pressed: boolean) {
39
+ switch (variant) {
40
+ case "primary": return pressed ? t.ink[800] : t.color.surfaceInverted;
41
+ case "default": return pressed ? t.color.surfaceActive : t.color.surfaceRaised;
42
+ case "ghost": return pressed ? t.color.surfaceHover : "transparent";
43
+ case "danger": return pressed ? t.color.diffDelBg : "transparent";
44
+ }
45
+ }
46
+ const fg =
47
+ variant === "primary" ? t.color.textInverted
48
+ : variant === "danger" ? t.color.diffDelFg
49
+ : t.color.textPrimary;
50
+ const borderColor =
51
+ variant === "default" ? t.color.borderDefault
52
+ : variant === "danger" ? t.color.borderDefault
53
+ : "transparent";
54
+
55
+ return (
56
+ <Pressable
57
+ testID={testID}
58
+ accessibilityRole="button"
59
+ accessibilityLabel={title}
60
+ onPress={onPress}
61
+ disabled={disabled}
62
+ style={({ pressed }) => [
63
+ {
64
+ height,
65
+ paddingHorizontal: padH,
66
+ flexDirection: "row",
67
+ alignItems: "center",
68
+ justifyContent: "center",
69
+ gap: t.space(1.5),
70
+ borderRadius: t.radius.md,
71
+ borderWidth: 1,
72
+ borderColor,
73
+ backgroundColor: bg(pressed),
74
+ opacity: disabled ? 0.4 : 1,
75
+ alignSelf: fullWidth ? "stretch" : "flex-start",
76
+ },
77
+ style,
78
+ ]}
79
+ >
80
+ {iconLeft ? <Icon name={iconLeft} size={size === "sm" ? 14 : 16} color={fg} /> : null}
81
+ <Text variant={size === "sm" ? "sm" : "body"} weight="medium" color={fg}>
82
+ {title}
83
+ </Text>
84
+ </Pressable>
85
+ );
86
+ }
87
+
88
+ export type IconButtonProps = {
89
+ name: IconName;
90
+ onPress: () => void;
91
+ size?: ButtonSize;
92
+ active?: boolean;
93
+ label: string; // accessibility
94
+ disabled?: boolean;
95
+ testID?: string;
96
+ };
97
+
98
+ export function IconButton({
99
+ name,
100
+ onPress,
101
+ size = "md",
102
+ active,
103
+ label,
104
+ disabled,
105
+ testID,
106
+ }: IconButtonProps) {
107
+ const t = useTheme();
108
+ const box = t.control[size];
109
+ return (
110
+ <Pressable
111
+ testID={testID}
112
+ accessibilityRole="button"
113
+ accessibilityLabel={label}
114
+ onPress={onPress}
115
+ disabled={disabled}
116
+ style={({ pressed }) => ({
117
+ width: box,
118
+ height: box,
119
+ alignItems: "center",
120
+ justifyContent: "center",
121
+ borderRadius: t.radius.md,
122
+ borderWidth: 1,
123
+ borderColor: active ? t.color.borderDefault : "transparent",
124
+ backgroundColor: active || pressed ? t.color.surfaceHover : "transparent",
125
+ opacity: disabled ? 0.4 : 1,
126
+ })}
127
+ >
128
+ <Icon name={name} size={size === "sm" ? 16 : 18} color={t.color.textSecondary} />
129
+ </Pressable>
130
+ );
131
+ }