Files
handler/app/src/components/Button.tsx
T
Claude daecaa7e6b Implement Handler mobile app (React Native + Expo iOS)
Build the committed 2a interactive prototype from Handler Mobile.dc.html as
a real Expo app: six wired screens (Fleet, Agent detail, Answer, Spawn, Log,
Settings) with the exact state logic ported from the design's renderVals().

- Port Leeworks tokens (colors light/dark, typography, spacing, radii,
  shadows) to typed RN values in src/theme.
- Reimplement the design-system components used by the screens (Button,
  Badge, Icon, Switch, Select, TextField, segmented control, chip, tab bar).
- Shared store mirrors the prototype's single-screen navigation and the
  Waiting -> Running flip when agt-7a1d is answered.
- Real OS status bar / home indicator (safe-area insets) replace the mock
  phone chrome; dark mode follows system appearance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PdF2hHVQ4UUBFPtpeMQ81o
2026-07-20 18:48:30 +00:00

112 lines
2.7 KiB
TypeScript

import React from "react";
import {
Pressable,
StyleSheet,
Text,
View,
type StyleProp,
type ViewStyle,
} from "react-native";
import { fonts, radius } from "../theme/tokens";
import { useTheme } from "../theme/useTheme";
/**
* Leeworks Button, ported from components/forms/Button.jsx. Hover collapses
* into press on touch; the primary/secondary/danger press tints are kept.
*/
export type ButtonVariant = "primary" | "secondary" | "ghost" | "danger";
export type ButtonSize = "sm" | "md" | "lg";
interface ButtonProps {
children: string;
variant?: ButtonVariant;
size?: ButtonSize;
onPress?: () => void;
style?: StyleProp<ViewStyle>;
}
const HEIGHT: Record<ButtonSize, number> = { sm: 32, md: 40, lg: 48 };
const PAD_X: Record<ButtonSize, number> = { sm: 12, md: 16, lg: 20 };
const FONT: Record<ButtonSize, number> = { sm: 13, md: 14, lg: 15 };
export function Button({
children,
variant = "primary",
size = "md",
onPress,
style,
}: ButtonProps) {
const { colors } = useTheme();
return (
<Pressable
onPress={onPress}
style={({ pressed }) => {
const base: ViewStyle = {
height: HEIGHT[size],
paddingHorizontal: PAD_X[size],
borderRadius: radius.md,
};
const byVariant: Record<ButtonVariant, ViewStyle> = {
primary: {
backgroundColor: pressed
? colors.interactivePress
: colors.interactive,
},
secondary: {
backgroundColor: pressed ? "rgba(20,20,19,0.05)" : colors.surfaceCard,
borderWidth: 1,
borderColor: colors.borderDefault,
},
ghost: {
backgroundColor: pressed ? "rgba(20,20,19,0.05)" : "transparent",
},
danger: {
backgroundColor: pressed ? "#8c2f25" : colors.danger,
},
};
return [styles.base, base, byVariant[variant], style];
}}
>
<View style={styles.inner}>
<Text
numberOfLines={1}
style={[
styles.label,
{
fontSize: FONT[size],
color:
variant === "primary"
? colors.textInverse
: variant === "danger"
? "#ffffff"
: variant === "secondary"
? colors.textHeading
: colors.ink9,
},
]}
>
{children}
</Text>
</View>
</Pressable>
);
}
const styles = StyleSheet.create({
base: {
alignItems: "center",
justifyContent: "center",
},
inner: {
flexDirection: "row",
alignItems: "center",
justifyContent: "center",
gap: 8,
},
label: {
fontFamily: fonts.bodySemiBold,
},
});