Extract PrBadge component and refactor workspace hover card
This commit is contained in:
@@ -27,6 +27,7 @@ import { type GestureType } from "react-native-gesture-handler";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import {
|
||||
Archive,
|
||||
ArrowUpRight,
|
||||
CircleAlert,
|
||||
ChevronDown,
|
||||
ChevronRight,
|
||||
@@ -112,11 +113,6 @@ const DEFAULT_STATUS_DOT_SIZE = 7;
|
||||
const EMPHASIZED_STATUS_DOT_SIZE = 9;
|
||||
const DEFAULT_STATUS_DOT_OFFSET = 0;
|
||||
const EMPHASIZED_STATUS_DOT_OFFSET = -1;
|
||||
const GITHUB_PR_STATE_LABELS: Record<PrHint["state"], string> = {
|
||||
open: "Open",
|
||||
merged: "Merged",
|
||||
closed: "Closed",
|
||||
};
|
||||
|
||||
interface SidebarWorkspaceListProps {
|
||||
projects: SidebarProjectEntry[];
|
||||
@@ -178,7 +174,7 @@ interface WorkspaceRowInnerProps {
|
||||
archiveShortcutKeys?: ShortcutKey[][] | null;
|
||||
}
|
||||
|
||||
function WorkspacePrBadge({ hint }: { hint: PrHint }) {
|
||||
export function PrBadge({ hint }: { hint: PrHint }) {
|
||||
const { theme } = useUnistyles();
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const activeColor = isHovered ? theme.colors.foreground : theme.colors.foregroundMuted;
|
||||
@@ -198,32 +194,45 @@ function WorkspacePrBadge({ hint }: { hint: PrHint }) {
|
||||
return (
|
||||
<Pressable
|
||||
accessibilityRole="link"
|
||||
accessibilityLabel={`${hint.state} pull request #${hint.number}`}
|
||||
accessibilityLabel={`Pull request #${hint.number}`}
|
||||
hitSlop={4}
|
||||
onPressIn={handlePressIn}
|
||||
onPress={handlePress}
|
||||
onPointerEnter={() => setIsHovered(true)}
|
||||
onPointerLeave={() => setIsHovered(false)}
|
||||
style={({ pressed }) => [
|
||||
styles.workspacePrBadge,
|
||||
pressed && styles.workspacePrBadgePressed,
|
||||
prBadgeStyles.badge,
|
||||
pressed && prBadgeStyles.badgePressed,
|
||||
]}
|
||||
>
|
||||
<GitPullRequest size={12} color={activeColor} />
|
||||
<Text
|
||||
style={[
|
||||
styles.workspacePrBadgeText,
|
||||
{ color: activeColor },
|
||||
]}
|
||||
style={[prBadgeStyles.text, { color: activeColor }]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
#{hint.number} · {GITHUB_PR_STATE_LABELS[hint.state]}
|
||||
#{hint.number}
|
||||
</Text>
|
||||
{isHovered && <ExternalLink size={10} color={activeColor} />}
|
||||
<ArrowUpRight size={10} color={activeColor} style={{ opacity: isHovered ? 1 : 0 }} />
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const prBadgeStyles = StyleSheet.create((theme) => ({
|
||||
badge: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 2,
|
||||
},
|
||||
badgePressed: {
|
||||
opacity: 0.82,
|
||||
},
|
||||
text: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
lineHeight: 14,
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
function WorkspaceStatusIndicator({
|
||||
bucket,
|
||||
@@ -1042,8 +1051,8 @@ function WorkspaceRowInner({
|
||||
</View>
|
||||
{prHint ? (
|
||||
<View style={styles.workspacePrBadgeRow}>
|
||||
<WorkspacePrBadge hint={prHint} />
|
||||
<CheckStatusIndicator status={prHint.checksStatus ?? "none"} size={11} />
|
||||
<CheckStatusIndicator status={prHint.checksStatus ?? "none"} size={12} />
|
||||
<PrBadge hint={prHint} />
|
||||
</View>
|
||||
) : null}
|
||||
</Pressable>
|
||||
@@ -2414,23 +2423,9 @@ const styles = StyleSheet.create((theme) => ({
|
||||
workspacePrBadgeRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
gap: theme.spacing[2],
|
||||
paddingLeft: WORKSPACE_STATUS_DOT_WIDTH + theme.spacing[2],
|
||||
},
|
||||
workspacePrBadge: {
|
||||
alignSelf: "flex-start",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
},
|
||||
workspacePrBadgePressed: {
|
||||
opacity: 0.82,
|
||||
},
|
||||
workspacePrBadgeText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
lineHeight: 14,
|
||||
},
|
||||
workspaceCreatingText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
|
||||
@@ -10,7 +10,7 @@ import { useMutation } from "@tanstack/react-query";
|
||||
import { Dimensions, Platform, Text, View } from "react-native";
|
||||
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { Check, ExternalLink, GitPullRequest, LoaderCircle, Minus, Play, X } from "lucide-react-native";
|
||||
import { Check, ExternalLink, LoaderCircle, Minus, Play, X } from "lucide-react-native";
|
||||
import { Pressable } from "react-native";
|
||||
import { Portal } from "@gorhom/portal";
|
||||
import { useBottomSheetModalInternal } from "@gorhom/bottom-sheet";
|
||||
@@ -19,6 +19,7 @@ import type { PrHint } from "@/hooks/use-checkout-pr-status-query";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { openExternalUrl } from "@/utils/open-external-url";
|
||||
import { PrBadge } from "@/components/sidebar-workspace-list";
|
||||
|
||||
interface Rect {
|
||||
x: number;
|
||||
@@ -191,12 +192,6 @@ function WorkspaceHoverCardDesktop({
|
||||
);
|
||||
}
|
||||
|
||||
const GITHUB_PR_STATE_LABELS: Record<PrHint["state"], string> = {
|
||||
open: "Open",
|
||||
merged: "Merged",
|
||||
closed: "Closed",
|
||||
};
|
||||
|
||||
function getServiceHealthColor(input: {
|
||||
health: SidebarWorkspaceEntry["services"][number]["health"];
|
||||
theme: ReturnType<typeof useUnistyles>["theme"];
|
||||
@@ -223,84 +218,93 @@ function getServiceHealthLabel(
|
||||
}
|
||||
|
||||
|
||||
function getCheckStatusColor(input: {
|
||||
status: string;
|
||||
theme: ReturnType<typeof useUnistyles>["theme"];
|
||||
}): string {
|
||||
if (input.status === "success") return input.theme.colors.palette.green[500];
|
||||
if (input.status === "failure") return input.theme.colors.palette.red[500];
|
||||
if (input.status === "pending") return input.theme.colors.palette.amber[500];
|
||||
return input.theme.colors.foregroundMuted;
|
||||
}
|
||||
|
||||
function getCheckStatusIcon(status: string): typeof Check {
|
||||
if (status === "success") return Check;
|
||||
if (status === "failure") return X;
|
||||
return Minus;
|
||||
}
|
||||
|
||||
export function CheckStatusIndicator({
|
||||
status,
|
||||
size = 14,
|
||||
size = 12,
|
||||
}: {
|
||||
status: string;
|
||||
size?: number;
|
||||
}): ReactElement | null {
|
||||
const { theme } = useUnistyles();
|
||||
const iconSize = Math.round(size * 0.6);
|
||||
|
||||
if (!status || status === "none") return null;
|
||||
|
||||
if (status === "pending") {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
borderWidth: 2,
|
||||
borderColor: theme.colors.palette.amber[500],
|
||||
backgroundColor: "transparent",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const color = getCheckStatusColor({ status, theme });
|
||||
const IconComponent = getCheckStatusIcon(status);
|
||||
|
||||
if (status === "success") {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
backgroundColor: "rgba(34,197,94,0.15)",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Check size={iconSize} color={theme.colors.palette.green[500]} strokeWidth={3} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
if (status === "failure") {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
backgroundColor: "rgba(239,68,68,0.15)",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<X size={iconSize} color={theme.colors.palette.red[500]} strokeWidth={3} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
// skipped / cancelled / unknown
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
width: size,
|
||||
height: size,
|
||||
borderRadius: size / 2,
|
||||
backgroundColor: "rgba(128,128,128,0.15)",
|
||||
borderWidth: 1,
|
||||
borderColor: color,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Minus size={iconSize} color={theme.colors.foregroundMuted} strokeWidth={3} />
|
||||
<IconComponent size={Math.round(size * 0.5)} color={color} strokeWidth={3} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function ChecksSummary({ checks }: { checks: Array<{ status: string }> }): ReactElement {
|
||||
const { theme } = useUnistyles();
|
||||
const counts: Record<string, number> = {};
|
||||
for (const check of checks) {
|
||||
const bucket = check.status === "success" ? "success" : check.status === "failure" ? "failure" : "pending";
|
||||
counts[bucket] = (counts[bucket] ?? 0) + 1;
|
||||
}
|
||||
|
||||
const buckets: Array<{ status: string; count: number }> = [];
|
||||
if (counts.failure) buckets.push({ status: "failure", count: counts.failure });
|
||||
if (counts.success) buckets.push({ status: "success", count: counts.success });
|
||||
if (counts.pending) buckets.push({ status: "pending", count: counts.pending });
|
||||
|
||||
return (
|
||||
<>
|
||||
{buckets.map((bucket) => {
|
||||
const color = getCheckStatusColor({ status: bucket.status, theme });
|
||||
return (
|
||||
<View key={bucket.status} style={checksSummaryStyles.item}>
|
||||
<Text style={[checksSummaryStyles.count, { color }]}>{bucket.count}</Text>
|
||||
<CheckStatusIndicator status={bucket.status} size={12} />
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const checksSummaryStyles = StyleSheet.create((theme) => ({
|
||||
item: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 3,
|
||||
},
|
||||
count: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
},
|
||||
}));
|
||||
|
||||
function WorkspaceHoverCardContent({
|
||||
workspace,
|
||||
prHint,
|
||||
@@ -406,160 +410,136 @@ function WorkspaceHoverCardContent({
|
||||
</Text>
|
||||
</View>
|
||||
{prHint || workspace.diffStat ? (
|
||||
<Pressable
|
||||
style={styles.cardMetaRow}
|
||||
onPress={prHint ? () => void openExternalUrl(prHint.url) : undefined}
|
||||
disabled={!prHint}
|
||||
>
|
||||
{prHint ? (
|
||||
<>
|
||||
<GitPullRequest size={12} color={theme.colors.foregroundMuted} />
|
||||
<Text style={styles.prBadgeText} numberOfLines={1}>
|
||||
#{prHint.number} · {GITHUB_PR_STATE_LABELS[prHint.state]}
|
||||
</Text>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<View style={styles.cardMetaRow}>
|
||||
{workspace.diffStat ? (
|
||||
<>
|
||||
<View style={styles.diffStatRow}>
|
||||
<Text style={styles.diffStatAdditions}>+{workspace.diffStat.additions}</Text>
|
||||
<Text style={styles.diffStatDeletions}>-{workspace.diffStat.deletions}</Text>
|
||||
</>
|
||||
</View>
|
||||
) : null}
|
||||
</Pressable>
|
||||
) : null}
|
||||
{prHint?.checks && prHint.checks.length > 0 ? (
|
||||
<>
|
||||
<View style={styles.separator} />
|
||||
<Text style={styles.sectionLabel}>Checks</Text>
|
||||
<View style={styles.checksList}>
|
||||
{prHint.checks.map((check) => (
|
||||
<Pressable
|
||||
key={check.name}
|
||||
style={({ hovered }) => [
|
||||
styles.serviceRow,
|
||||
hovered && check.url && styles.serviceRowHovered,
|
||||
]}
|
||||
onPress={check.url ? () => void openExternalUrl(check.url!) : undefined}
|
||||
disabled={!check.url}
|
||||
>
|
||||
<CheckStatusIndicator status={check.status} size={14} />
|
||||
<Text
|
||||
style={styles.checkName}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{check.name}
|
||||
</Text>
|
||||
{check.url ? (
|
||||
<ExternalLink size={11} color={theme.colors.foregroundMuted} />
|
||||
) : null}
|
||||
</Pressable>
|
||||
))}
|
||||
{prHint ? <PrBadge hint={prHint} /> : null}
|
||||
</View>
|
||||
</>
|
||||
) : null}
|
||||
{workspace.services.length > 0 ? (
|
||||
<>
|
||||
<View style={styles.separator} />
|
||||
<Text style={styles.sectionLabel}>Services</Text>
|
||||
<View style={styles.serviceList} testID="hover-card-service-list">
|
||||
{workspace.services.map((service) => (
|
||||
<Pressable
|
||||
key={service.hostname}
|
||||
accessibilityRole={service.lifecycle === "running" && service.url ? "link" : undefined}
|
||||
accessibilityLabel={`${service.serviceName} service`}
|
||||
testID={`hover-card-service-${service.serviceName}`}
|
||||
style={({ hovered }) => [
|
||||
styles.serviceRow,
|
||||
hovered &&
|
||||
service.lifecycle === "running" &&
|
||||
service.url &&
|
||||
styles.serviceRowHovered,
|
||||
]}
|
||||
onPress={() => {
|
||||
if (service.lifecycle === "running" && service.url) {
|
||||
void openExternalUrl(service.url);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={[
|
||||
styles.serviceName,
|
||||
{
|
||||
color:
|
||||
service.lifecycle === "running"
|
||||
? theme.colors.foreground
|
||||
: theme.colors.foregroundMuted,
|
||||
},
|
||||
<View style={styles.sectionList} testID="hover-card-service-list">
|
||||
{workspace.services.map((service) => {
|
||||
const isRunning = service.lifecycle === "running";
|
||||
const isLinkable = isRunning && !!service.url;
|
||||
return (
|
||||
<Pressable
|
||||
key={service.hostname}
|
||||
accessibilityRole={isLinkable ? "link" : undefined}
|
||||
accessibilityLabel={`${service.serviceName} service — ${isRunning ? getServiceHealthLabel(service.health) : "Stopped"}`}
|
||||
testID={`hover-card-service-${service.serviceName}`}
|
||||
style={({ hovered }) => [
|
||||
styles.listRow,
|
||||
hovered && isLinkable && styles.listRowHovered,
|
||||
]}
|
||||
numberOfLines={1}
|
||||
onPress={isLinkable ? () => void openExternalUrl(service.url!) : undefined}
|
||||
disabled={!isLinkable}
|
||||
>
|
||||
{service.serviceName}
|
||||
</Text>
|
||||
<View style={styles.serviceMeta}>
|
||||
<Text
|
||||
testID={`hover-card-service-status-${service.serviceName}`}
|
||||
accessibilityLabel={service.lifecycle === "running" ? "Running" : "Stopped"}
|
||||
style={styles.serviceLifecycleText}
|
||||
>
|
||||
{service.lifecycle === "running" ? "Running" : "Stopped"}
|
||||
</Text>
|
||||
<View
|
||||
testID={`hover-card-service-health-${service.serviceName}`}
|
||||
accessibilityLabel={getServiceHealthLabel(service.health)}
|
||||
style={[
|
||||
styles.statusDot,
|
||||
{
|
||||
backgroundColor: getServiceHealthColor({
|
||||
health: service.health,
|
||||
theme,
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Text style={styles.serviceHealthText}>{getServiceHealthLabel(service.health)}</Text>
|
||||
</View>
|
||||
{service.lifecycle === "running" && service.url ? (
|
||||
<Text style={styles.serviceUrl} numberOfLines={1}>
|
||||
{service.url.replace(/^https?:\/\//, "")}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={styles.serviceUrlSpacer} />
|
||||
)}
|
||||
{service.lifecycle === "running" && service.url ? (
|
||||
<ExternalLink size={11} color={theme.colors.foregroundMuted} />
|
||||
) : (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Start ${service.serviceName} service`}
|
||||
testID={`hover-card-service-start-${service.serviceName}`}
|
||||
style={({ hovered, pressed }) => [
|
||||
styles.startServiceButton,
|
||||
(hovered || pressed) && styles.startServiceButtonHovered,
|
||||
]}
|
||||
disabled={startServiceMutation.isPending}
|
||||
onPress={(event) => {
|
||||
event.stopPropagation();
|
||||
startServiceMutation.mutate(service.serviceName);
|
||||
}}
|
||||
>
|
||||
{startServiceMutation.isPending &&
|
||||
startServiceMutation.variables === service.serviceName ? (
|
||||
<LoaderCircle
|
||||
size={12}
|
||||
color={theme.colors.foregroundMuted}
|
||||
style={styles.startServiceSpinner}
|
||||
{({ hovered }) => (
|
||||
<>
|
||||
<View
|
||||
testID={`hover-card-service-health-${service.serviceName}`}
|
||||
style={[
|
||||
styles.statusDot,
|
||||
{
|
||||
backgroundColor: isRunning
|
||||
? getServiceHealthColor({ health: service.health, theme })
|
||||
: theme.colors.foregroundMuted,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<Play size={12} color={theme.colors.foregroundMuted} fill="transparent" />
|
||||
)}
|
||||
</Pressable>
|
||||
)}
|
||||
</Pressable>
|
||||
))}
|
||||
<Text
|
||||
style={[
|
||||
styles.listRowLabel,
|
||||
{
|
||||
color: isRunning
|
||||
? theme.colors.foreground
|
||||
: theme.colors.foregroundMuted,
|
||||
},
|
||||
]}
|
||||
numberOfLines={1}
|
||||
>
|
||||
{service.serviceName}
|
||||
</Text>
|
||||
{isRunning && service.url ? (
|
||||
<Text style={styles.listRowSecondary} numberOfLines={1}>
|
||||
{service.url.replace(/^https?:\/\//, "")}
|
||||
</Text>
|
||||
) : (
|
||||
<View style={styles.listRowSpacer} />
|
||||
)}
|
||||
{isRunning ? (
|
||||
service.url ? (
|
||||
<ExternalLink
|
||||
size={12}
|
||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
) : null
|
||||
) : (
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={`Start ${service.serviceName} service`}
|
||||
testID={`hover-card-service-start-${service.serviceName}`}
|
||||
hitSlop={4}
|
||||
disabled={startServiceMutation.isPending}
|
||||
onPress={(event) => {
|
||||
event.stopPropagation();
|
||||
startServiceMutation.mutate(service.serviceName);
|
||||
}}
|
||||
>
|
||||
{({ hovered: actionHovered }) =>
|
||||
startServiceMutation.isPending &&
|
||||
startServiceMutation.variables === service.serviceName ? (
|
||||
<LoaderCircle size={12} color={theme.colors.foregroundMuted} />
|
||||
) : (
|
||||
<Play
|
||||
size={12}
|
||||
color={actionHovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
fill="transparent"
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Pressable>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</>
|
||||
) : null}
|
||||
{prHint?.checks && prHint.checks.length > 0 ? (
|
||||
<>
|
||||
<View style={styles.separator} />
|
||||
<Pressable
|
||||
style={({ hovered }) => [
|
||||
styles.checksSummaryRow,
|
||||
hovered && styles.listRowHovered,
|
||||
]}
|
||||
onPress={() => void openExternalUrl(`${prHint.url}/checks`)}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<>
|
||||
<Text style={styles.checksSummaryLabel}>Checks</Text>
|
||||
<View style={styles.checksSummaryCounts}>
|
||||
<ChecksSummary checks={prHint.checks!} />
|
||||
</View>
|
||||
<ExternalLink
|
||||
size={12}
|
||||
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
</>
|
||||
) : null}
|
||||
</Animated.View>
|
||||
</View>
|
||||
</Portal>
|
||||
@@ -609,6 +589,11 @@ const styles = StyleSheet.create((theme) => ({
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingBottom: theme.spacing[2],
|
||||
},
|
||||
diffStatRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
},
|
||||
diffStatAdditions: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
@@ -619,76 +604,10 @@ const styles = StyleSheet.create((theme) => ({
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.palette.red[500],
|
||||
},
|
||||
prBadgeText: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: theme.colors.border,
|
||||
},
|
||||
serviceList: {
|
||||
paddingTop: theme.spacing[1],
|
||||
},
|
||||
serviceRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingVertical: theme.spacing[2],
|
||||
minHeight: 32,
|
||||
},
|
||||
serviceRowHovered: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
statusDot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
flexShrink: 0,
|
||||
},
|
||||
serviceName: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
flexShrink: 0,
|
||||
},
|
||||
serviceMeta: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[1],
|
||||
flexShrink: 0,
|
||||
},
|
||||
serviceLifecycleText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
serviceHealthText: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
},
|
||||
serviceUrl: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
serviceUrlSpacer: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
startServiceButton: {
|
||||
width: 22,
|
||||
height: 22,
|
||||
borderRadius: 11,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
startServiceButtonHovered: {
|
||||
backgroundColor: theme.colors.surface3,
|
||||
},
|
||||
startServiceSpinner: {
|
||||
transform: [{ rotate: "0deg" }],
|
||||
},
|
||||
sectionLabel: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
@@ -697,13 +616,59 @@ const styles = StyleSheet.create((theme) => ({
|
||||
paddingTop: theme.spacing[2],
|
||||
paddingBottom: theme.spacing[1],
|
||||
},
|
||||
checksList: {
|
||||
sectionList: {
|
||||
paddingBottom: theme.spacing[1],
|
||||
},
|
||||
checkName: {
|
||||
listRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingVertical: 6,
|
||||
minHeight: 28,
|
||||
},
|
||||
listRowHovered: {
|
||||
backgroundColor: theme.colors.surface2,
|
||||
},
|
||||
listRowLabel: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
flexShrink: 0,
|
||||
},
|
||||
listRowSecondary: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.xs,
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
textAlign: "right",
|
||||
},
|
||||
listRowSpacer: {
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
},
|
||||
statusDot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: 4,
|
||||
flexShrink: 0,
|
||||
},
|
||||
checksSummaryRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingVertical: 6,
|
||||
minHeight: 28,
|
||||
},
|
||||
checksSummaryLabel: {
|
||||
fontSize: theme.fontSize.xs,
|
||||
fontWeight: theme.fontWeight.medium,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
checksSummaryCounts: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
flex: 1,
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user