feat(client): add component library and simplify routing (#66)

* feat(client): add component library and simplify routing

Remove tab layout, explore page, modal, and unused template
components. Replace with single-page layout and a dev component
showcase with per-component detail pages.

- Add Button with label prop, leading/trailing icon support
- Add FeedCard, SerifText, SansSerifText, MonospaceText
- Add colocated *.showcase.tsx files for each component
- Use Stack navigator with themed headers

Co-authored-by: Ona <no-reply@ona.com>

* fix(client): render showcase as JSX component

Co-authored-by: Ona <no-reply@ona.com>

* chore(client): remove dead code chain

Remove ThemedText, useThemeColor, useColorScheme hook,
Colors, and Fonts — none referenced by current screens.

Co-authored-by: Ona <no-reply@ona.com>

---------

Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
2026-03-13 00:23:06 +00:00
committed by GitHub
parent e07157eba0
commit c78ad25f0d
32 changed files with 396 additions and 721 deletions

View File

@@ -0,0 +1,43 @@
import Feather from "@expo/vector-icons/Feather"
import { View } from "react-native"
import tw from "twrnc"
import { Button } from "./button"
import { type Showcase, Section } from "../showcase"
function ButtonShowcase() {
return (
<View style={tw`gap-6`}>
<Section title="Default">
<Button style={tw`self-start`} label="Press me" />
</Section>
<Section title="Leading icon">
<Button
style={tw`self-start`}
label="Add item"
leadingIcon={<Feather name="plus" size={18} color="#e7e5e4" />}
/>
</Section>
<Section title="Trailing icon">
<Button
style={tw`self-start`}
label="Next"
trailingIcon={<Feather name="arrow-right" size={18} color="#e7e5e4" />}
/>
</Section>
<Section title="Both icons">
<Button
style={tw`self-start`}
label="Download"
leadingIcon={<Feather name="download" size={18} color="#e7e5e4" />}
trailingIcon={<Feather name="chevron-down" size={18} color="#e7e5e4" />}
/>
</Section>
</View>
)
}
export const buttonShowcase: Showcase = {
title: "Button",
component: ButtonShowcase,
}

View File

@@ -0,0 +1,30 @@
import { type PressableProps, Pressable, View } from "react-native"
import tw from "twrnc"
import { SansSerifText } from "./sans-serif-text"
type ButtonProps = Omit<PressableProps, "children"> & {
label: string
leadingIcon?: React.ReactNode
trailingIcon?: React.ReactNode
}
export function Button({ style, label, leadingIcon, trailingIcon, ...props }: ButtonProps) {
const hasIcons = leadingIcon != null || trailingIcon != null
const textElement = <SansSerifText style={tw`text-stone-200 font-medium`}>{label}</SansSerifText>
return (
<Pressable style={[tw`rounded-full bg-teal-600 px-4 py-3 w-fit`, style]} {...props}>
{hasIcons ? (
<View style={tw`flex-row items-center gap-1.5`}>
{leadingIcon}
{textElement}
{trailingIcon}
</View>
) : (
textElement
)}
</Pressable>
)
}

View File

@@ -1,46 +0,0 @@
import { PropsWithChildren, useState } from "react"
import { StyleSheet, TouchableOpacity } from "react-native"
import { ThemedText } from "@/components/themed-text"
import { ThemedView } from "@/components/themed-view"
import { IconSymbol } from "@/components/ui/icon-symbol"
import { Colors } from "@/constants/theme"
import { useColorScheme } from "@/hooks/use-color-scheme"
export function Collapsible({ children, title }: PropsWithChildren & { title: string }) {
const [isOpen, setIsOpen] = useState(false)
const theme = useColorScheme() ?? "light"
return (
<ThemedView>
<TouchableOpacity
style={styles.heading}
onPress={() => setIsOpen((value) => !value)}
activeOpacity={0.8}
>
<IconSymbol
name="chevron.right"
size={18}
weight="medium"
color={theme === "light" ? Colors.light.icon : Colors.dark.icon}
style={{ transform: [{ rotate: isOpen ? "90deg" : "0deg" }] }}
/>
<ThemedText type="defaultSemiBold">{title}</ThemedText>
</TouchableOpacity>
{isOpen && <ThemedView style={styles.content}>{children}</ThemedView>}
</ThemedView>
)
}
const styles = StyleSheet.create({
heading: {
flexDirection: "row",
alignItems: "center",
gap: 6,
},
content: {
marginTop: 6,
marginLeft: 24,
},
})

View File

@@ -0,0 +1,32 @@
import { View } from "react-native"
import tw from "twrnc"
import { Button } from "./button"
import { FeedCard } from "./feed-card"
import { SansSerifText } from "./sans-serif-text"
import { SerifText } from "./serif-text"
import { type Showcase, Section } from "../showcase"
function FeedCardShowcase() {
return (
<View style={tw`gap-6`}>
<Section title="Default">
<FeedCard style={tw`p-4`}>
<SansSerifText>Card content goes here</SansSerifText>
</FeedCard>
</Section>
<Section title="With mixed content">
<FeedCard style={tw`p-4 gap-2`}>
<SerifText style={tw`text-xl`}>Title</SerifText>
<SansSerifText>Body text inside a feed card.</SansSerifText>
<Button style={tw`self-start mt-2`} label="Action" />
</FeedCard>
</Section>
</View>
)
}
export const feedCardShowcase: Showcase = {
title: "FeedCard",
component: FeedCardShowcase,
}

View File

@@ -0,0 +1,6 @@
import { View, type ViewProps } from "react-native"
import tw from "twrnc"
export function FeedCard({ style, ...props }: ViewProps) {
return <View style={[tw`border border-stone-200 dark:border-stone-800 rounded-lg`, style]} {...props} />
}

View File

@@ -1,32 +0,0 @@
import { SymbolView, SymbolViewProps, SymbolWeight } from "expo-symbols"
import { StyleProp, ViewStyle } from "react-native"
export function IconSymbol({
name,
size = 24,
color,
style,
weight = "regular",
}: {
name: SymbolViewProps["name"]
size?: number
color: string
style?: StyleProp<ViewStyle>
weight?: SymbolWeight
}) {
return (
<SymbolView
weight={weight}
tintColor={color}
resizeMode="scaleAspectFit"
name={name}
style={[
{
width: size,
height: size,
},
style,
]}
/>
)
}

View File

@@ -1,41 +0,0 @@
// Fallback for using MaterialIcons on Android and web.
import MaterialIcons from "@expo/vector-icons/MaterialIcons"
import { SymbolWeight, SymbolViewProps } from "expo-symbols"
import { ComponentProps } from "react"
import { OpaqueColorValue, type StyleProp, type TextStyle } from "react-native"
type IconMapping = Record<SymbolViewProps["name"], ComponentProps<typeof MaterialIcons>["name"]>
type IconSymbolName = keyof typeof MAPPING
/**
* Add your SF Symbols to Material Icons mappings here.
* - see Material Icons in the [Icons Directory](https://icons.expo.fyi).
* - see SF Symbols in the [SF Symbols](https://developer.apple.com/sf-symbols/) app.
*/
const MAPPING = {
"house.fill": "home",
"paperplane.fill": "send",
"chevron.left.forwardslash.chevron.right": "code",
"chevron.right": "chevron-right",
} as IconMapping
/**
* An icon component that uses native SF Symbols on iOS, and Material Icons on Android and web.
* This ensures a consistent look across platforms, and optimal resource usage.
* Icon `name`s are based on SF Symbols and require manual mapping to Material Icons.
*/
export function IconSymbol({
name,
size = 24,
color,
style,
}: {
name: IconSymbolName
size?: number
color: string | OpaqueColorValue
style?: StyleProp<TextStyle>
weight?: SymbolWeight
}) {
return <MaterialIcons color={color} size={size} name={MAPPING[name]} style={style} />
}

View File

@@ -0,0 +1,31 @@
import { View } from "react-native"
import tw from "twrnc"
import { MonospaceText } from "./monospace-text"
import { type Showcase, Section } from "../showcase"
function MonospaceTextShowcase() {
return (
<View style={tw`gap-6`}>
<Section title="Sizes">
<View style={tw`gap-2`}>
<MonospaceText style={tw`text-sm`}>Small monospace text</MonospaceText>
<MonospaceText style={tw`text-base`}>Base monospace text</MonospaceText>
<MonospaceText style={tw`text-xl`}>Extra large monospace text</MonospaceText>
<MonospaceText style={tw`text-3xl`}>3XL monospace text</MonospaceText>
</View>
</Section>
<Section title="Code-like usage">
<View style={tw`bg-stone-200 dark:bg-stone-800 rounded-lg p-3`}>
<MonospaceText style={tw`text-sm`}>{"const x = 42;"}</MonospaceText>
<MonospaceText style={tw`text-sm`}>{"console.log(x);"}</MonospaceText>
</View>
</Section>
</View>
)
}
export const monospaceTextShowcase: Showcase = {
title: "MonospaceText",
component: MonospaceTextShowcase,
}

View File

@@ -0,0 +1,10 @@
import { Text, type TextProps } from "react-native"
import tw from "twrnc"
export function MonospaceText({ children, style, ...props }: TextProps) {
return (
<Text style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Menlo" }, style]} {...props}>
{children}
</Text>
)
}

View File

@@ -0,0 +1,34 @@
import { View } from "react-native"
import tw from "twrnc"
import { SansSerifText } from "./sans-serif-text"
import { type Showcase, Section } from "../showcase"
function SansSerifTextShowcase() {
return (
<View style={tw`gap-6`}>
<Section title="Sizes">
<View style={tw`gap-2`}>
<SansSerifText style={tw`text-sm`}>Small sans-serif text</SansSerifText>
<SansSerifText style={tw`text-base`}>Base sans-serif text</SansSerifText>
<SansSerifText style={tw`text-xl`}>Extra large sans-serif text</SansSerifText>
<SansSerifText style={tw`text-3xl`}>3XL sans-serif text</SansSerifText>
</View>
</Section>
<Section title="Weights">
<View style={tw`gap-2`}>
<SansSerifText style={tw`font-light`}>Light weight</SansSerifText>
<SansSerifText style={tw`font-normal`}>Normal weight</SansSerifText>
<SansSerifText style={tw`font-medium`}>Medium weight</SansSerifText>
<SansSerifText style={tw`font-semibold`}>Semibold weight</SansSerifText>
<SansSerifText style={tw`font-bold`}>Bold weight</SansSerifText>
</View>
</Section>
</View>
)
}
export const sansSerifTextShowcase: Showcase = {
title: "SansSerifText",
component: SansSerifTextShowcase,
}

View File

@@ -0,0 +1,10 @@
import { Text, type TextProps } from "react-native"
import tw from "twrnc"
export function SansSerifText({ children, style, ...props }: TextProps) {
return (
<Text style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Inter" }, style]} {...props}>
{children}
</Text>
)
}

View File

@@ -0,0 +1,25 @@
import { View } from "react-native"
import tw from "twrnc"
import { SerifText } from "./serif-text"
import { type Showcase, Section } from "../showcase"
function SerifTextShowcase() {
return (
<View style={tw`gap-6`}>
<Section title="Sizes">
<View style={tw`gap-2`}>
<SerifText style={tw`text-sm`}>Small serif text</SerifText>
<SerifText style={tw`text-base`}>Base serif text</SerifText>
<SerifText style={tw`text-xl`}>Extra large serif text</SerifText>
<SerifText style={tw`text-3xl`}>3XL serif text</SerifText>
</View>
</Section>
</View>
)
}
export const serifTextShowcase: Showcase = {
title: "SerifText",
component: SerifTextShowcase,
}

View File

@@ -0,0 +1,10 @@
import { Text, type TextProps } from "react-native"
import tw from "twrnc"
export function SerifText({ children, style, ...props }: TextProps) {
return (
<Text style={[tw`text-stone-800 dark:text-stone-200`, { fontFamily: "Source Serif 4" }, style]} {...props}>
{children}
</Text>
)
}