@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.
- package/README.md +77 -0
- package/css/components.css +312 -0
- package/css/tokens.css +212 -0
- package/package.json +65 -0
- package/src/ThemeProvider.tsx +86 -0
- package/src/__tests__/ApplyChangesModal.test.tsx +148 -0
- package/src/__tests__/BranchImport.test.tsx +46 -0
- package/src/__tests__/Button.test.tsx +45 -0
- package/src/__tests__/ChangeRequestSummary.test.tsx +57 -0
- package/src/__tests__/ContentBrowser.changes.test.tsx +611 -0
- package/src/__tests__/ContentBrowser.collab.test.tsx +322 -0
- package/src/__tests__/ContentBrowser.collabsync.test.tsx +264 -0
- package/src/__tests__/ContentBrowser.contentslot.test.tsx +53 -0
- package/src/__tests__/ContentBrowser.discriminator.test.tsx +142 -0
- package/src/__tests__/ContentBrowser.drafts.test.tsx +271 -0
- package/src/__tests__/ContentBrowser.fields.test.tsx +117 -0
- package/src/__tests__/ContentBrowser.media.test.tsx +140 -0
- package/src/__tests__/ContentBrowser.mixedcollab.test.tsx +63 -0
- package/src/__tests__/ContentBrowser.mixedvalues.test.tsx +38 -0
- package/src/__tests__/ContentBrowser.pagination.test.tsx +62 -0
- package/src/__tests__/ContentBrowser.previewtab.test.tsx +212 -0
- package/src/__tests__/ContentBrowser.reorder.test.tsx +45 -0
- package/src/__tests__/ContentBrowser.search.test.tsx +135 -0
- package/src/__tests__/ContentBrowser.selectvalue.test.tsx +185 -0
- package/src/__tests__/ContentBrowser.staged.test.tsx +132 -0
- package/src/__tests__/ContentBrowser.usermenu.test.tsx +56 -0
- package/src/__tests__/MediaBrowser.test.tsx +353 -0
- package/src/__tests__/MediaField.test.tsx +185 -0
- package/src/__tests__/Notifications.test.tsx +69 -0
- package/src/__tests__/Onboarding.test.tsx +287 -0
- package/src/__tests__/cssTokens.test.ts +201 -0
- package/src/__tests__/fieldComponents.test.ts +43 -0
- package/src/__tests__/reorder.test.ts +58 -0
- package/src/components/ApplyChangesModal.tsx +348 -0
- package/src/components/BranchImport.tsx +157 -0
- package/src/components/BranchMenu.tsx +192 -0
- package/src/components/Button.tsx +131 -0
- package/src/components/ChangeDetail.tsx +472 -0
- package/src/components/ChangeRequestSummary.tsx +173 -0
- package/src/components/CollabField.tsx +388 -0
- package/src/components/ContentBrowser.tsx +5073 -0
- package/src/components/Icon.tsx +28 -0
- package/src/components/Icon.web.tsx +31 -0
- package/src/components/Input.tsx +106 -0
- package/src/components/MediaBrowser.tsx +766 -0
- package/src/components/MediaField.tsx +670 -0
- package/src/components/MediaPreview.tsx +91 -0
- package/src/components/MediaPreview.web.tsx +169 -0
- package/src/components/NavRow.tsx +105 -0
- package/src/components/Notifications.tsx +301 -0
- package/src/components/Onboarding.tsx +751 -0
- package/src/components/ProjectMenu.tsx +124 -0
- package/src/components/Segment.tsx +87 -0
- package/src/components/Skeleton.tsx +216 -0
- package/src/components/Spinner.tsx +44 -0
- package/src/components/Text.tsx +85 -0
- package/src/components/documentDrafts.ts +213 -0
- package/src/components/layout.tsx +284 -0
- package/src/components/primitives.tsx +143 -0
- package/src/components/reorder.ts +40 -0
- package/src/fieldComponents.ts +63 -0
- package/src/icons.ts +102 -0
- package/src/index.ts +198 -0
- package/src/media.ts +229 -0
- package/src/theme.ts +116 -0
- package/src/web/Button.tsx +110 -0
- package/src/web/Icon.tsx +52 -0
- package/src/web/Input.tsx +39 -0
- package/src/web/index.ts +43 -0
- package/src/web/primitives.tsx +119 -0
package/src/theme.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Platform } from "react-native";
|
|
2
|
+
|
|
3
|
+
// Go Git CMS design system — monochrome, terminal-adjacent, desktop-first.
|
|
4
|
+
// A single neutral ink ramp carries the entire UI; the only chromatic tokens
|
|
5
|
+
// are the git diff signals (add/remove). Light and dark are the same semantic
|
|
6
|
+
// mapping over an inverted ink ramp.
|
|
7
|
+
|
|
8
|
+
export type ThemeMode = "light" | "dark";
|
|
9
|
+
|
|
10
|
+
type Ink = Record<0 | 50 | 100 | 150 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 1000, string>;
|
|
11
|
+
|
|
12
|
+
const inkLight: Ink = {
|
|
13
|
+
0: "#ffffff", 50: "#f7f7f7", 100: "#ededed", 150: "#e4e4e4", 200: "#dcdcdc",
|
|
14
|
+
300: "#c9c9c9", 400: "#a6a6a6", 500: "#808080", 600: "#5c5c5c", 700: "#3d3d3d",
|
|
15
|
+
800: "#242424", 900: "#151515", 1000: "#0a0a0a",
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const inkDark: Ink = {
|
|
19
|
+
0: "#0b0b0b", 50: "#151515", 100: "#1e1e1e", 150: "#242424", 200: "#2d2d2d",
|
|
20
|
+
300: "#3a3a3a", 400: "#585858", 500: "#808080", 600: "#a2a2a2", 700: "#c4c4c4",
|
|
21
|
+
800: "#dcdcdc", 900: "#ececec", 1000: "#f4f4f4",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Font stacks: Geist / Geist Mono on web (loaded via <link> in the host page),
|
|
25
|
+
// falling back to platform system fonts on native.
|
|
26
|
+
const fontSans = Platform.select({
|
|
27
|
+
web: "Geist, ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif",
|
|
28
|
+
default: "System",
|
|
29
|
+
}) as string;
|
|
30
|
+
|
|
31
|
+
const fontMono = Platform.select({
|
|
32
|
+
web: "'Geist Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace",
|
|
33
|
+
ios: "Menlo",
|
|
34
|
+
android: "monospace",
|
|
35
|
+
default: "monospace",
|
|
36
|
+
}) as string;
|
|
37
|
+
|
|
38
|
+
export function makeTheme(mode: ThemeMode) {
|
|
39
|
+
const ink = mode === "dark" ? inkDark : inkLight;
|
|
40
|
+
const dark = mode === "dark";
|
|
41
|
+
return {
|
|
42
|
+
mode,
|
|
43
|
+
ink,
|
|
44
|
+
color: {
|
|
45
|
+
// Text
|
|
46
|
+
textPrimary: ink[1000],
|
|
47
|
+
textSecondary: ink[600],
|
|
48
|
+
textTertiary: ink[500],
|
|
49
|
+
textDisabled: ink[400],
|
|
50
|
+
textInverted: ink[0],
|
|
51
|
+
// Surfaces
|
|
52
|
+
surfacePage: ink[0],
|
|
53
|
+
surfaceRaised: dark ? ink[50] : ink[0],
|
|
54
|
+
// The top bar is a permanent dark chrome in both modes: near-black in
|
|
55
|
+
// light, a slightly-raised charcoal in dark so it reads above the page.
|
|
56
|
+
// Its inline contents render under the dark theme to stay legible.
|
|
57
|
+
surfaceTopBar: dark ? "#1e1e1e" : "#1a1a1a",
|
|
58
|
+
surfaceSunken: ink[50],
|
|
59
|
+
surfaceHover: ink[50],
|
|
60
|
+
surfaceActive: ink[100],
|
|
61
|
+
surfaceInverted: ink[1000],
|
|
62
|
+
overlay: "rgba(10,10,10,0.45)",
|
|
63
|
+
// Borders — hairlines carry structure
|
|
64
|
+
borderSubtle: ink[100],
|
|
65
|
+
borderDefault: ink[200],
|
|
66
|
+
borderHover: ink[400],
|
|
67
|
+
borderStrong: ink[1000],
|
|
68
|
+
borderFocus: ink[1000],
|
|
69
|
+
// Active-column/tab accent rule. A mid-gray in both modes: the rule sits
|
|
70
|
+
// directly beneath the dark top bar, so pure black (light) merges into it
|
|
71
|
+
// and pure white (dark) glares — ink[600] reads as an accent against both
|
|
72
|
+
// the bar above and the column below.
|
|
73
|
+
borderActive: ink[600],
|
|
74
|
+
// Git diff signals — the only permitted color
|
|
75
|
+
diffAddFg: dark ? "#3fb950" : "#1a7f37",
|
|
76
|
+
diffAddBg: dark ? "#12261a" : "#e6f4ea",
|
|
77
|
+
diffDelFg: dark ? "#f85149" : "#b42318",
|
|
78
|
+
diffDelBg: dark ? "#2a1615" : "#fbeae8",
|
|
79
|
+
},
|
|
80
|
+
// 4px base grid
|
|
81
|
+
space: (n: number) => n * 4,
|
|
82
|
+
radius: { sm: 3, md: 5, lg: 8, pill: 999 },
|
|
83
|
+
control: { sm: 28, md: 34, lg: 40 },
|
|
84
|
+
layout: { sidebar: 260, column: 300, topbar: 48, container: 1200 },
|
|
85
|
+
font: { sans: fontSans, mono: fontMono },
|
|
86
|
+
type: {
|
|
87
|
+
display: { size: 40, line: 44 },
|
|
88
|
+
h1: { size: 28, line: 34 },
|
|
89
|
+
h2: { size: 22, line: 28 },
|
|
90
|
+
h3: { size: 17, line: 24 },
|
|
91
|
+
body: { size: 14, line: 21 },
|
|
92
|
+
bodyLg: { size: 16, line: 24 },
|
|
93
|
+
sm: { size: 13, line: 18 },
|
|
94
|
+
xs: { size: 11, line: 16 },
|
|
95
|
+
mono: { size: 13, line: 20 },
|
|
96
|
+
monoSm: { size: 12, line: 18 },
|
|
97
|
+
},
|
|
98
|
+
weight: {
|
|
99
|
+
light: "300" as const,
|
|
100
|
+
regular: "400" as const,
|
|
101
|
+
medium: "500" as const,
|
|
102
|
+
semibold: "600" as const,
|
|
103
|
+
bold: "700" as const,
|
|
104
|
+
},
|
|
105
|
+
letterSpacing: { tight: -0.4, normal: 0, wide: 0.3, label: 0.9 },
|
|
106
|
+
duration: { fast: 90, base: 150, slow: 240 },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export type Theme = ReturnType<typeof makeTheme>;
|
|
111
|
+
export const lightTheme = makeTheme("light");
|
|
112
|
+
export const darkTheme = makeTheme("dark");
|
|
113
|
+
|
|
114
|
+
// Responsive breakpoint: at/above this width the desktop multi-pane layout is
|
|
115
|
+
// used; below it, the mobile drill-down layout.
|
|
116
|
+
export const DESKTOP_BREAKPOINT = 900;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Icon } from "./Icon";
|
|
3
|
+
import type { IconName } from "../icons";
|
|
4
|
+
|
|
5
|
+
export type ButtonVariant = "primary" | "default" | "ghost" | "danger";
|
|
6
|
+
export type ButtonSize = "sm" | "md" | "lg";
|
|
7
|
+
|
|
8
|
+
type Common = {
|
|
9
|
+
variant?: ButtonVariant;
|
|
10
|
+
size?: ButtonSize;
|
|
11
|
+
iconLeft?: IconName | React.ReactNode;
|
|
12
|
+
fullWidth?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type ButtonProps = Common &
|
|
18
|
+
Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "children" | "className">;
|
|
19
|
+
|
|
20
|
+
export type ButtonLinkProps = Common &
|
|
21
|
+
Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "children" | "className">;
|
|
22
|
+
|
|
23
|
+
function classes({ variant = "default", size = "md", fullWidth, className }: Common) {
|
|
24
|
+
return [
|
|
25
|
+
"ds-btn",
|
|
26
|
+
`ds-btn--${variant}`,
|
|
27
|
+
size !== "md" && `ds-btn--${size}`,
|
|
28
|
+
fullWidth && "ds-btn--full",
|
|
29
|
+
className,
|
|
30
|
+
]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join(" ");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function leading(iconLeft: Common["iconLeft"], size: ButtonSize) {
|
|
36
|
+
if (iconLeft == null) return null;
|
|
37
|
+
if (typeof iconLeft !== "string") return iconLeft;
|
|
38
|
+
return <Icon name={iconLeft as IconName} size={size === "sm" ? 14 : 16} />;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The monochrome action button, mirroring the native Button's variant x size. */
|
|
42
|
+
export function Button({
|
|
43
|
+
variant = "default",
|
|
44
|
+
size = "md",
|
|
45
|
+
iconLeft,
|
|
46
|
+
fullWidth,
|
|
47
|
+
className,
|
|
48
|
+
children,
|
|
49
|
+
type = "button",
|
|
50
|
+
...rest
|
|
51
|
+
}: ButtonProps) {
|
|
52
|
+
return (
|
|
53
|
+
<button type={type} className={classes({ variant, size, fullWidth, className })} {...rest}>
|
|
54
|
+
{leading(iconLeft, size)}
|
|
55
|
+
{children != null && <span>{children}</span>}
|
|
56
|
+
</button>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The same button rendered as an anchor, for navigation rather than an action. */
|
|
61
|
+
export function ButtonLink({
|
|
62
|
+
variant = "default",
|
|
63
|
+
size = "md",
|
|
64
|
+
iconLeft,
|
|
65
|
+
fullWidth,
|
|
66
|
+
className,
|
|
67
|
+
children,
|
|
68
|
+
...rest
|
|
69
|
+
}: ButtonLinkProps) {
|
|
70
|
+
return (
|
|
71
|
+
<a className={classes({ variant, size, fullWidth, className })} {...rest}>
|
|
72
|
+
{leading(iconLeft, size)}
|
|
73
|
+
{children != null && <span>{children}</span>}
|
|
74
|
+
</a>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export type IconButtonProps = {
|
|
79
|
+
name: IconName;
|
|
80
|
+
/** Required — an icon-only control has no visible text to name it. */
|
|
81
|
+
label: string;
|
|
82
|
+
size?: "sm" | "md";
|
|
83
|
+
/** Drop the border and surface, leaving just the glyph. */
|
|
84
|
+
bare?: boolean;
|
|
85
|
+
className?: string;
|
|
86
|
+
} & Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "className">;
|
|
87
|
+
|
|
88
|
+
export function IconButton({
|
|
89
|
+
name,
|
|
90
|
+
label,
|
|
91
|
+
size = "md",
|
|
92
|
+
bare,
|
|
93
|
+
className,
|
|
94
|
+
type = "button",
|
|
95
|
+
...rest
|
|
96
|
+
}: IconButtonProps) {
|
|
97
|
+
return (
|
|
98
|
+
<button
|
|
99
|
+
type={type}
|
|
100
|
+
title={label}
|
|
101
|
+
aria-label={label}
|
|
102
|
+
className={["ds-iconbtn", size === "sm" && "ds-iconbtn--sm", bare && "ds-iconbtn--bare", className]
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.join(" ")}
|
|
105
|
+
{...rest}
|
|
106
|
+
>
|
|
107
|
+
<Icon name={name} size={size === "sm" ? 14 : 16} />
|
|
108
|
+
</button>
|
|
109
|
+
);
|
|
110
|
+
}
|
package/src/web/Icon.tsx
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { icons, type IconName } from "../icons";
|
|
3
|
+
|
|
4
|
+
export type IconProps = {
|
|
5
|
+
name: IconName;
|
|
6
|
+
size?: number;
|
|
7
|
+
/** Any CSS color, including a token: `var(--text-secondary)`. Defaults to the
|
|
8
|
+
* inherited text color so an icon takes on the ink of whatever contains it. */
|
|
9
|
+
color?: string;
|
|
10
|
+
strokeWidth?: number;
|
|
11
|
+
/** Icons are decorative by default; pass a label when one carries meaning. */
|
|
12
|
+
label?: string;
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The product icon set rendered as a plain <svg>, for web surfaces that don't
|
|
18
|
+
* run react-native-svg. Same paths as the native Icon — both read ../icons.
|
|
19
|
+
*/
|
|
20
|
+
export function Icon({
|
|
21
|
+
name,
|
|
22
|
+
size = 18,
|
|
23
|
+
color = "currentColor",
|
|
24
|
+
strokeWidth = 1.5,
|
|
25
|
+
label,
|
|
26
|
+
className,
|
|
27
|
+
}: IconProps) {
|
|
28
|
+
return (
|
|
29
|
+
<svg
|
|
30
|
+
className={className}
|
|
31
|
+
width={size}
|
|
32
|
+
height={size}
|
|
33
|
+
viewBox="0 0 24 24"
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke={color}
|
|
36
|
+
strokeWidth={strokeWidth}
|
|
37
|
+
strokeLinecap="round"
|
|
38
|
+
strokeLinejoin="round"
|
|
39
|
+
role={label ? "img" : undefined}
|
|
40
|
+
aria-label={label}
|
|
41
|
+
aria-hidden={label ? undefined : true}
|
|
42
|
+
focusable="false"
|
|
43
|
+
style={{ display: "block", flexShrink: 0 }}
|
|
44
|
+
>
|
|
45
|
+
{icons[name].map((d, i) => (
|
|
46
|
+
<path key={i} d={d} />
|
|
47
|
+
))}
|
|
48
|
+
</svg>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type { IconName };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
type Shared = {
|
|
4
|
+
/** Mono for anything technical: paths, branches, hashes, config keys. */
|
|
5
|
+
mono?: boolean;
|
|
6
|
+
size?: "sm" | "md";
|
|
7
|
+
className?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type InputProps = Shared &
|
|
11
|
+
Omit<React.InputHTMLAttributes<HTMLInputElement>, "className" | "size">;
|
|
12
|
+
|
|
13
|
+
export type TextareaProps = Shared &
|
|
14
|
+
Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "className">;
|
|
15
|
+
|
|
16
|
+
export type SelectProps = Shared &
|
|
17
|
+
Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "className" | "size">;
|
|
18
|
+
|
|
19
|
+
function classes({ mono, size, className }: Shared) {
|
|
20
|
+
return ["ds-input", mono && "ds-input--mono", size === "sm" && "ds-input--sm", className]
|
|
21
|
+
.filter(Boolean)
|
|
22
|
+
.join(" ");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function Input({ mono, size, className, ...rest }: InputProps) {
|
|
26
|
+
return <input className={classes({ mono, size, className })} {...rest} />;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function Textarea({ mono, size, className, ...rest }: TextareaProps) {
|
|
30
|
+
return <textarea className={classes({ mono, size, className })} {...rest} />;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function Select({ mono, size, className, children, ...rest }: SelectProps) {
|
|
34
|
+
return (
|
|
35
|
+
<select className={classes({ mono, size, className })} {...rest}>
|
|
36
|
+
{children}
|
|
37
|
+
</select>
|
|
38
|
+
);
|
|
39
|
+
}
|
package/src/web/index.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @gogitcms/design-system/web — the design system for surfaces that render to
|
|
2
|
+
// plain DOM instead of react-native-web: the Astro docs site, marketing pages,
|
|
3
|
+
// anything server-rendered.
|
|
4
|
+
//
|
|
5
|
+
// Same tokens, same icon paths, same visual decisions as the native components
|
|
6
|
+
// next door in ../components; the difference is that these emit HTML elements
|
|
7
|
+
// and read their styling from ../../css/components.css, so a button looks right
|
|
8
|
+
// in server HTML with no hydration. Import both stylesheets once in the host
|
|
9
|
+
// page:
|
|
10
|
+
//
|
|
11
|
+
// import "@gogitcms/design-system/css/tokens.css";
|
|
12
|
+
// import "@gogitcms/design-system/css/components.css";
|
|
13
|
+
|
|
14
|
+
export { Icon } from "./Icon";
|
|
15
|
+
export type { IconProps, IconName } from "./Icon";
|
|
16
|
+
|
|
17
|
+
export { Button, ButtonLink, IconButton } from "./Button";
|
|
18
|
+
export type {
|
|
19
|
+
ButtonProps,
|
|
20
|
+
ButtonLinkProps,
|
|
21
|
+
ButtonVariant,
|
|
22
|
+
ButtonSize,
|
|
23
|
+
IconButtonProps,
|
|
24
|
+
} from "./Button";
|
|
25
|
+
|
|
26
|
+
export { Input, Textarea, Select } from "./Input";
|
|
27
|
+
export type { InputProps, TextareaProps, SelectProps } from "./Input";
|
|
28
|
+
|
|
29
|
+
export { Badge, Avatar, Card, CardLink, SectionLabel, Divider, Meta } from "./primitives";
|
|
30
|
+
export type {
|
|
31
|
+
BadgeProps,
|
|
32
|
+
BadgeTone,
|
|
33
|
+
AvatarProps,
|
|
34
|
+
CardProps,
|
|
35
|
+
CardLinkProps,
|
|
36
|
+
SectionLabelProps,
|
|
37
|
+
DividerProps,
|
|
38
|
+
MetaProps,
|
|
39
|
+
} from "./primitives";
|
|
40
|
+
|
|
41
|
+
// Nothing here re-exports ../theme: it imports react-native for Platform.select,
|
|
42
|
+
// which a plain-DOM consumer has no reason to install. Web surfaces read token
|
|
43
|
+
// values from the CSS custom properties instead.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
export type BadgeTone = "neutral" | "add" | "del" | "strong";
|
|
4
|
+
|
|
5
|
+
export type BadgeProps = {
|
|
6
|
+
tone?: BadgeTone;
|
|
7
|
+
/** Badges are mono by default — they carry counts and git status letters. */
|
|
8
|
+
mono?: boolean;
|
|
9
|
+
className?: string;
|
|
10
|
+
children?: React.ReactNode;
|
|
11
|
+
} & Omit<React.HTMLAttributes<HTMLSpanElement>, "className" | "children">;
|
|
12
|
+
|
|
13
|
+
export function Badge({ tone = "neutral", mono = true, className, children, ...rest }: BadgeProps) {
|
|
14
|
+
return (
|
|
15
|
+
<span
|
|
16
|
+
className={["ds-badge", `ds-badge--${tone}`, !mono && "ds-badge--sans", className]
|
|
17
|
+
.filter(Boolean)
|
|
18
|
+
.join(" ")}
|
|
19
|
+
{...rest}
|
|
20
|
+
>
|
|
21
|
+
{children}
|
|
22
|
+
</span>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AvatarProps = {
|
|
27
|
+
initials: string;
|
|
28
|
+
size?: number;
|
|
29
|
+
className?: string;
|
|
30
|
+
} & Omit<React.HTMLAttributes<HTMLSpanElement>, "className">;
|
|
31
|
+
|
|
32
|
+
/** Square initials tile — the product has no uploaded-avatar surface. */
|
|
33
|
+
export function Avatar({ initials, size = 28, className, style, ...rest }: AvatarProps) {
|
|
34
|
+
return (
|
|
35
|
+
<span
|
|
36
|
+
className={["ds-avatar", className].filter(Boolean).join(" ")}
|
|
37
|
+
style={{ width: size, height: size, ...style }}
|
|
38
|
+
{...rest}
|
|
39
|
+
>
|
|
40
|
+
{initials}
|
|
41
|
+
</span>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type CardProps = {
|
|
46
|
+
/** Sunken cards sit on --surface-sunken; use for asides, not content rows. */
|
|
47
|
+
sunken?: boolean;
|
|
48
|
+
className?: string;
|
|
49
|
+
children?: React.ReactNode;
|
|
50
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "children">;
|
|
51
|
+
|
|
52
|
+
export function Card({ sunken, className, children, ...rest }: CardProps) {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className={["ds-card", sunken && "ds-card--sunken", className].filter(Boolean).join(" ")}
|
|
56
|
+
{...rest}
|
|
57
|
+
>
|
|
58
|
+
{children}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export type CardLinkProps = {
|
|
64
|
+
href: string;
|
|
65
|
+
className?: string;
|
|
66
|
+
children?: React.ReactNode;
|
|
67
|
+
} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "className" | "children" | "href">;
|
|
68
|
+
|
|
69
|
+
/** A card that is itself the link target — the docs home grid is built of these. */
|
|
70
|
+
export function CardLink({ href, className, children, ...rest }: CardLinkProps) {
|
|
71
|
+
return (
|
|
72
|
+
<a
|
|
73
|
+
href={href}
|
|
74
|
+
className={["ds-card", "ds-card--interactive", className].filter(Boolean).join(" ")}
|
|
75
|
+
{...rest}
|
|
76
|
+
>
|
|
77
|
+
{children}
|
|
78
|
+
</a>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type SectionLabelProps = {
|
|
83
|
+
className?: string;
|
|
84
|
+
children?: React.ReactNode;
|
|
85
|
+
} & Omit<React.HTMLAttributes<HTMLDivElement>, "className" | "children">;
|
|
86
|
+
|
|
87
|
+
/** The uppercase mono micro-label that heads a group. */
|
|
88
|
+
export function SectionLabel({ className, children, ...rest }: SectionLabelProps) {
|
|
89
|
+
return (
|
|
90
|
+
<div className={["ds-label", className].filter(Boolean).join(" ")} {...rest}>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export type DividerProps = {
|
|
97
|
+
vertical?: boolean;
|
|
98
|
+
className?: string;
|
|
99
|
+
style?: React.CSSProperties;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export function Divider({ vertical, className, style }: DividerProps) {
|
|
103
|
+
const cls = ["ds-divider", vertical && "ds-divider--vertical", className].filter(Boolean).join(" ");
|
|
104
|
+
return vertical ? <span className={cls} style={style} /> : <hr className={cls} style={style} />;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type MetaProps = {
|
|
108
|
+
className?: string;
|
|
109
|
+
children?: React.ReactNode;
|
|
110
|
+
} & Omit<React.HTMLAttributes<HTMLSpanElement>, "className" | "children">;
|
|
111
|
+
|
|
112
|
+
/** Mono metadata: paths, hashes, branch names, timestamps, counts. */
|
|
113
|
+
export function Meta({ className, children, ...rest }: MetaProps) {
|
|
114
|
+
return (
|
|
115
|
+
<span className={["ds-meta", className].filter(Boolean).join(" ")} {...rest}>
|
|
116
|
+
{children}
|
|
117
|
+
</span>
|
|
118
|
+
);
|
|
119
|
+
}
|