-
-
Save andrewmd5/f0be891c8a2b8c2ab3ad31517f423740 to your computer and use it in GitHub Desktop.
hako exampl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Modern Finance Tracking App | |
| // Real-time portfolio tracking with dark/light mode | |
| // Initial stock data | |
| const initialStocks = { | |
| AAPL: { | |
| symbol: "AAPL", | |
| name: "Apple Inc.", | |
| price: 182.52, | |
| change: 2.34, | |
| percent: 1.3, | |
| shares: 50, | |
| }, | |
| GOOGL: { | |
| symbol: "GOOGL", | |
| name: "Alphabet Inc.", | |
| price: 139.78, | |
| change: -1.23, | |
| percent: -0.87, | |
| shares: 20, | |
| }, | |
| MSFT: { | |
| symbol: "MSFT", | |
| name: "Microsoft Corp.", | |
| price: 378.91, | |
| change: 5.67, | |
| percent: 1.52, | |
| shares: 30, | |
| }, | |
| AMZN: { | |
| symbol: "AMZN", | |
| name: "Amazon.com Inc.", | |
| price: 145.32, | |
| change: 3.21, | |
| percent: 2.26, | |
| shares: 25, | |
| }, | |
| TSLA: { | |
| symbol: "TSLA", | |
| name: "Tesla Inc.", | |
| price: 243.84, | |
| change: -4.56, | |
| percent: -1.84, | |
| shares: 15, | |
| }, | |
| }; | |
| // Theme definitions | |
| const themes = { | |
| dark: { | |
| bg: "#0a0a0a", | |
| surface: "#1a1a1a", | |
| card: "#242424", | |
| cardHover: "#2a2a2a", | |
| border: "#333333", | |
| borderLight: "#404040", | |
| text: "#ffffff", | |
| textSecondary: "#999999", | |
| textMuted: "#666666", | |
| accent: "#4a9eff", | |
| success: "#10b981", | |
| danger: "#ef4444", | |
| warning: "#f59e0b", | |
| highlight: "#8b5cf6", | |
| iconColor: "#ffffff", | |
| iconMuted: "#999999", | |
| }, | |
| light: { | |
| bg: "#f8f9fa", | |
| surface: "#ffffff", | |
| card: "#ffffff", | |
| cardHover: "#f3f4f6", | |
| border: "#e5e7eb", | |
| borderLight: "#d1d5db", | |
| text: "#111827", | |
| textSecondary: "#6b7280", | |
| textMuted: "#9ca3af", | |
| accent: "#3b82f6", | |
| success: "#10b981", | |
| danger: "#ef4444", | |
| warning: "#f59e0b", | |
| highlight: "#8b5cf6", | |
| iconColor: "#374151", | |
| iconMuted: "#9ca3af", | |
| }, | |
| }; | |
| export function render() { | |
| const [darkMode, setDarkMode] = useState(true); | |
| const [selectedView, setSelectedView] = useState("overview"); | |
| const [stocks, setStocks] = useState(initialStocks); | |
| const [cashBalance, setCashBalance] = useState(15000); | |
| const [refreshCount, setRefreshCount] = useState(0); | |
| const [showSettings, setShowSettings] = useState(false); | |
| const [showMarketTooltip, setShowMarketTooltip] = useState(false); | |
| const [autoRefresh, setAutoRefresh] = useState(true); | |
| const [notifications, setNotifications] = useState(true); | |
| const [timeoutStarted, setTimeoutStarted] = useState(false); | |
| const theme = darkMode ? themes.dark : themes.light; | |
| // Automatically update stock prices every 5 seconds if enabled | |
| if (autoRefresh && !timeoutStarted) { | |
| setTimeoutStarted(true); | |
| setTimeout(() => { | |
| UpdatePrices(); | |
| setTimeoutStarted(false); // Reset so next render can set another timeout | |
| }, 1000); | |
| } | |
| // Update stock prices | |
| const UpdatePrices = () => { | |
| setStocks((prevStocks) => { | |
| const newStocks = {}; | |
| for (let symbol in prevStocks) { | |
| const stock = prevStocks[symbol]; | |
| const movement = (Math.random() - 0.5) * 4; | |
| const priceChange = movement; | |
| const newPrice = stock.price + priceChange; | |
| newStocks[symbol] = { | |
| ...stock, | |
| price: newPrice, | |
| change: stock.change + priceChange, | |
| percent: ((stock.change + priceChange) / stock.price) * 100, | |
| }; | |
| } | |
| return newStocks; | |
| }); | |
| setRefreshCount((c) => c + 1); | |
| }; | |
| // Calculate portfolio metrics | |
| const stocksValue = Object.values(stocks).reduce( | |
| (sum, stock) => sum + stock.price * stock.shares, | |
| 0, | |
| ); | |
| const portfolioValue = stocksValue + cashBalance; | |
| const dayChange = Object.values(stocks).reduce( | |
| (sum, stock) => sum + stock.change * stock.shares, | |
| 0, | |
| ); | |
| const dayChangePercent = (dayChange / portfolioValue) * 100; | |
| return VStack( | |
| { | |
| spacing: 0, | |
| width: "fill", | |
| height: "fill", | |
| background: theme.bg, | |
| }, | |
| [ | |
| // Header | |
| HStack( | |
| { | |
| background: theme.surface, | |
| padding: 16, | |
| spacing: 16, | |
| height: 64, | |
| width: "fill", | |
| alignment: "center", | |
| border: { width: { bottom: 2 }, color: theme.accent }, | |
| }, | |
| [ | |
| HStack({ spacing: 12, alignment: "center" }, [ | |
| VStack( | |
| { | |
| background: theme.accent, | |
| width: 36, | |
| height: 36, | |
| cornerRadius: 8, | |
| alignment: "center", | |
| border: { width: 2, color: theme.highlight }, | |
| }, | |
| [Text("FT", { fontSize: 18, color: "white" })], | |
| ), | |
| VStack({ spacing: 2 }, [ | |
| Text("FinanceTracker", { fontSize: 18, color: theme.text }), | |
| Text("Professional", { fontSize: 11, color: theme.accent }), | |
| ]), | |
| ]), | |
| Spacer(), | |
| HStack({ spacing: 12 }, [ | |
| // Market status with hover tooltip | |
| ZStack([ | |
| // Market status with hover tooltip | |
| Button( | |
| { | |
| onClick: () => setShowMarketTooltip(!showMarketTooltip), | |
| background: theme.card, | |
| hoverBackground: theme.cardHover, | |
| padding: 8, | |
| cornerRadius: 20, | |
| border: { width: 1, color: theme.success }, | |
| }, | |
| [ | |
| HStack({ spacing: 8 }, [ | |
| VStack({ | |
| background: theme.success, | |
| width: 8, | |
| height: 8, | |
| cornerRadius: 4, | |
| }), | |
| Text("Markets Open", { fontSize: 12, color: theme.text }), | |
| ]), | |
| // Tooltip as a sibling to button content, not nested in HStack | |
| Tooltip( | |
| { | |
| attachment: "below", | |
| background: darkMode ? "#333333" : "#374151", | |
| padding: 10, | |
| cornerRadius: 6, | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| VStack({ spacing: 4 }, [ | |
| Text("NYSE Trading Hours", { | |
| fontSize: 12, | |
| color: "white", | |
| }), | |
| Text("9:30 AM - 4:00 PM EST", { | |
| fontSize: 11, | |
| color: "#cccccc", | |
| }), | |
| Divider({ color: "#555555", thickness: 1 }), | |
| Text( | |
| "Last update: " + new Date().toLocaleTimeString(), | |
| { fontSize: 10, color: "#aaaaaa" }, | |
| ), | |
| ]), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ]), | |
| // Refresh button | |
| Button( | |
| { | |
| onClick: UpdatePrices, | |
| background: theme.card, | |
| hoverBackground: theme.cardHover, | |
| padding: 10, | |
| cornerRadius: 8, | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| Icon("refresh", { size: 18, fill: theme.iconColor }), | |
| ), | |
| // Settings button | |
| Button( | |
| { | |
| onClick: () => setShowSettings(!showSettings), | |
| background: showSettings ? theme.accent : theme.card, | |
| hoverBackground: showSettings ? theme.accent : theme.cardHover, | |
| padding: 10, | |
| cornerRadius: 8, | |
| border: { | |
| width: 1, | |
| color: showSettings ? theme.highlight : theme.border, | |
| }, | |
| }, | |
| Icon("settings", { | |
| size: 18, | |
| fill: showSettings ? "white" : theme.iconColor, | |
| }), | |
| ), | |
| ]), | |
| ], | |
| ), | |
| HStack({ spacing: 0, width: "fill", height: "fill" }, [ | |
| // Sidebar | |
| VStack( | |
| { | |
| width: 280, | |
| background: theme.surface, | |
| padding: 20, | |
| spacing: 16, | |
| height: "fill", | |
| border: { width: { right: 1 }, color: theme.border }, | |
| }, | |
| [ | |
| // Portfolio summary card | |
| VStack( | |
| { | |
| background: theme.accent, | |
| padding: 20, | |
| cornerRadius: 12, | |
| spacing: 12, | |
| width: "fill", | |
| border: { width: 2, color: theme.highlight }, | |
| }, | |
| [ | |
| Text("Portfolio Value", { fontSize: 13, color: "#ffffffcc" }), | |
| Text(`$${portfolioValue.toFixed(2)}`, { | |
| fontSize: 28, | |
| color: "white", | |
| }), | |
| HStack({ spacing: 8 }, [ | |
| VStack( | |
| { | |
| background: dayChange >= 0 ? theme.success : theme.danger, | |
| padding: 4, | |
| cornerRadius: 4, | |
| }, | |
| [ | |
| Text(dayChange >= 0 ? "↑" : "↓", { | |
| fontSize: 12, | |
| color: "white", | |
| }), | |
| ], | |
| ), | |
| Text(`$${Math.abs(dayChange).toFixed(2)}`, { | |
| fontSize: 14, | |
| color: "white", | |
| }), | |
| Text( | |
| `(${dayChangePercent >= 0 ? "+" : ""}${dayChangePercent.toFixed(2)}%)`, | |
| { fontSize: 12, color: "#ffffffaa" }, | |
| ), | |
| ]), | |
| ], | |
| ), | |
| // Navigation | |
| VStack({ spacing: 6, width: "fill" }, [ | |
| NavButton( | |
| "Overview", | |
| "overview", | |
| "dashboard", | |
| selectedView, | |
| setSelectedView, | |
| theme, | |
| ), | |
| NavButton( | |
| "Portfolio", | |
| "portfolio", | |
| "chart", | |
| selectedView, | |
| setSelectedView, | |
| theme, | |
| ), | |
| NavButton( | |
| "Activity", | |
| "activity", | |
| "refresh", | |
| selectedView, | |
| setSelectedView, | |
| theme, | |
| ), | |
| ]), | |
| Spacer(), | |
| // Quick stats | |
| VStack( | |
| { | |
| background: theme.card, | |
| padding: 16, | |
| cornerRadius: 10, | |
| spacing: 12, | |
| width: "fill", | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| StatRow("Cash Balance", `$${cashBalance.toFixed(2)}`, theme), | |
| StatRow("Stocks Value", `$${stocksValue.toFixed(2)}`, theme), | |
| StatRow( | |
| "Day Change", | |
| `${dayChangePercent >= 0 ? "+" : ""}${dayChangePercent.toFixed(2)}%`, | |
| theme, | |
| dayChangePercent >= 0 ? theme.success : theme.danger, | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| // Main content | |
| VStack( | |
| { | |
| background: theme.bg, | |
| width: "fill", | |
| height: "fill", | |
| }, | |
| [ | |
| ScrollView( | |
| { | |
| width: "fill", | |
| height: "fill", | |
| axis: "vertical", | |
| }, | |
| [ | |
| VStack( | |
| { | |
| padding: 24, | |
| spacing: 20, | |
| width: "fill", | |
| }, | |
| [ | |
| showSettings | |
| ? RenderSettings( | |
| theme, | |
| darkMode, | |
| setDarkMode, | |
| autoRefresh, | |
| setAutoRefresh, | |
| notifications, | |
| setNotifications, | |
| setShowSettings, | |
| ) | |
| : selectedView === "overview" | |
| ? RenderOverview( | |
| theme, | |
| stocks, | |
| portfolioValue, | |
| cashBalance, | |
| dayChange, | |
| ) | |
| : selectedView === "portfolio" | |
| ? RenderPortfolio(theme, stocks) | |
| : selectedView === "activity" | |
| ? RenderActivity(theme) | |
| : null, | |
| ], | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| ]), | |
| ], | |
| ); | |
| } | |
| function NavButton(label, id, icon, selectedView, setSelectedView, theme) { | |
| const isSelected = selectedView === id; | |
| return Button( | |
| { | |
| onClick: () => setSelectedView(id), | |
| background: isSelected ? theme.accent : theme.card, | |
| hoverBackground: isSelected ? theme.accent : theme.cardHover, | |
| width: "fill", | |
| padding: 12, | |
| cornerRadius: 8, | |
| border: { | |
| width: isSelected ? 2 : 1, | |
| color: isSelected ? theme.highlight : theme.border, | |
| }, | |
| }, | |
| HStack({ spacing: 10, alignment: "center" }, [ | |
| Icon(icon, { size: 18, fill: isSelected ? "white" : theme.iconColor }), | |
| Text(label, { fontSize: 14, color: isSelected ? "white" : theme.text }), | |
| ]), | |
| ); | |
| } | |
| function StatRow(label, value, theme, valueColor) { | |
| return HStack({ width: "fill" }, [ | |
| Text(label, { fontSize: 12, color: theme.textMuted }), | |
| Spacer(), | |
| Text(value, { fontSize: 12, color: valueColor || theme.text }), | |
| ]); | |
| } | |
| function RenderOverview(theme, stocks, portfolioValue, cashBalance, dayChange) { | |
| return VStack({ spacing: 20, width: "fill" }, [ | |
| Text("Dashboard Overview", { fontSize: 24, color: theme.text }), | |
| // Top cards | |
| HStack({ spacing: 16, width: "fill" }, [ | |
| MetricCard( | |
| "Total Assets", | |
| `$${portfolioValue.toFixed(2)}`, | |
| "+12.5%", | |
| theme, | |
| theme.accent, | |
| ), | |
| MetricCard( | |
| "Today's Gain", | |
| `$${dayChange.toFixed(2)}`, | |
| `${dayChange >= 0 ? "+" : ""}${((dayChange / portfolioValue) * 100).toFixed(2)}%`, | |
| theme, | |
| dayChange >= 0 ? theme.success : theme.danger, | |
| ), | |
| MetricCard( | |
| "Cash Available", | |
| `$${cashBalance.toFixed(2)}`, | |
| "Ready to invest", | |
| theme, | |
| theme.warning, | |
| ), | |
| ]), | |
| // Stocks table | |
| VStack( | |
| { | |
| background: theme.card, | |
| padding: 20, | |
| cornerRadius: 12, | |
| spacing: 16, | |
| width: "fill", | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| HStack({ width: "fill" }, [ | |
| Text("Your Holdings", { fontSize: 18, color: theme.text }), | |
| Spacer(), | |
| Text(`${Object.keys(stocks).length} positions`, { | |
| fontSize: 12, | |
| color: theme.textMuted, | |
| }), | |
| ]), | |
| VStack({ spacing: 8, width: "fill" }, [ | |
| // Header | |
| HStack( | |
| { | |
| padding: 8, | |
| width: "fill", | |
| background: theme.surface, | |
| cornerRadius: 6, | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| Text("Symbol", { fontSize: 12, color: theme.textMuted }), | |
| Spacer(), | |
| Text("Price", { fontSize: 12, color: theme.textMuted }), | |
| Spacer(), | |
| Text("Change", { fontSize: 12, color: theme.textMuted }), | |
| Spacer(), | |
| Text("Holdings", { fontSize: 12, color: theme.textMuted }), | |
| ], | |
| ), | |
| // Stock rows | |
| ...Object.values(stocks).map((stock) => StockRow(stock, theme)), | |
| ]), | |
| ], | |
| ), | |
| ]); | |
| } | |
| function MetricCard(title, value, subtitle, theme, accentColor) { | |
| return VStack( | |
| { | |
| background: theme.card, | |
| padding: 20, | |
| cornerRadius: 10, | |
| spacing: 8, | |
| width: "fill", | |
| border: { | |
| width: { left: 4, top: 1, right: 1, bottom: 1 }, | |
| color: accentColor, | |
| }, | |
| }, | |
| [ | |
| Text(title, { fontSize: 12, color: theme.textMuted }), | |
| Text(value, { fontSize: 20, color: theme.text }), | |
| Text(subtitle, { fontSize: 11, color: accentColor }), | |
| ], | |
| ); | |
| } | |
| function StockRow(stock, theme) { | |
| const changeColor = stock.change >= 0 ? theme.success : theme.danger; | |
| return HStack( | |
| { | |
| padding: 12, | |
| width: "fill", | |
| background: theme.surface, | |
| cornerRadius: 6, | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| VStack({ spacing: 2, width: 100 }, [ | |
| Text(stock.symbol, { fontSize: 14, color: theme.text }), | |
| Text(stock.shares + " shares", { | |
| fontSize: 10, | |
| color: theme.textMuted, | |
| }), | |
| ]), | |
| Spacer(), | |
| Text(`$${stock.price.toFixed(2)}`, { fontSize: 14, color: theme.text }), | |
| Spacer(), | |
| HStack({ spacing: 6, width: 120 }, [ | |
| Text(`${stock.change >= 0 ? "+" : ""}${stock.change.toFixed(2)}`, { | |
| fontSize: 13, | |
| color: changeColor, | |
| }), | |
| VStack( | |
| { | |
| background: changeColor, | |
| padding: 3, | |
| cornerRadius: 4, | |
| }, | |
| [ | |
| Text( | |
| `${stock.percent >= 0 ? "+" : ""}${stock.percent.toFixed(1)}%`, | |
| { fontSize: 10, color: "white" }, | |
| ), | |
| ], | |
| ), | |
| ]), | |
| Spacer(), | |
| Text(`$${(stock.price * stock.shares).toFixed(2)}`, { | |
| fontSize: 14, | |
| color: theme.text, | |
| }), | |
| ], | |
| ); | |
| } | |
| function RenderPortfolio(theme, stocks) { | |
| const totalValue = Object.values(stocks).reduce( | |
| (sum, s) => sum + s.price * s.shares, | |
| 0, | |
| ); | |
| return VStack({ spacing: 20, width: "fill" }, [ | |
| Text("Portfolio Breakdown", { fontSize: 24, color: theme.text }), | |
| HStack({ spacing: 16, width: "fill" }, [ | |
| // Allocation chart placeholder | |
| VStack( | |
| { | |
| background: theme.card, | |
| padding: 24, | |
| cornerRadius: 12, | |
| spacing: 16, | |
| width: "fill", | |
| height: 300, | |
| alignment: "center", | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| Icon("chart-big", { size: 64, fill: theme.iconMuted }), | |
| Text("Portfolio Allocation Chart", { | |
| fontSize: 14, | |
| color: theme.textMuted, | |
| }), | |
| ], | |
| ), | |
| // Holdings list | |
| VStack( | |
| { | |
| background: theme.card, | |
| padding: 20, | |
| cornerRadius: 12, | |
| spacing: 12, | |
| width: 300, | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| Text("Holdings", { fontSize: 16, color: theme.text }), | |
| Divider({ color: theme.border }), | |
| ...Object.values(stocks).map((stock) => | |
| HStack({ width: "fill", padding: 8 }, [ | |
| VStack({ | |
| background: theme.accent, | |
| width: 4, | |
| height: 30, | |
| }), | |
| HStack({ width: "fill", padding: 8 }, [ | |
| VStack({ spacing: 2 }, [ | |
| Text(stock.symbol, { fontSize: 13, color: theme.text }), | |
| Text( | |
| `${(((stock.price * stock.shares) / totalValue) * 100).toFixed(1)}%`, | |
| { fontSize: 10, color: theme.textMuted }, | |
| ), | |
| ]), | |
| Spacer(), | |
| Text(`$${(stock.price * stock.shares).toFixed(2)}`, { | |
| fontSize: 13, | |
| color: theme.text, | |
| }), | |
| ]), | |
| ]), | |
| ), | |
| ], | |
| ), | |
| ]), | |
| ]); | |
| } | |
| function RenderActivity(theme) { | |
| const activities = [ | |
| { type: "buy", symbol: "AAPL", amount: 1825.2, time: "2 hours ago" }, | |
| { type: "sell", symbol: "TSLA", amount: 1250.0, time: "1 day ago" }, | |
| { type: "dividend", symbol: "MSFT", amount: 45.0, time: "3 days ago" }, | |
| { type: "buy", symbol: "GOOGL", amount: 2795.6, time: "1 week ago" }, | |
| ]; | |
| return VStack({ spacing: 20, width: "fill" }, [ | |
| Text("Recent Activity", { fontSize: 24, color: theme.text }), | |
| VStack( | |
| { | |
| background: theme.card, | |
| padding: 20, | |
| cornerRadius: 12, | |
| spacing: 12, | |
| width: "fill", | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| ...activities.map((activity) => | |
| HStack( | |
| { | |
| padding: 12, | |
| width: "fill", | |
| background: theme.surface, | |
| cornerRadius: 8, | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| VStack( | |
| { | |
| background: | |
| activity.type === "buy" | |
| ? theme.success | |
| : activity.type === "sell" | |
| ? theme.danger | |
| : theme.warning, | |
| width: 40, | |
| height: 40, | |
| cornerRadius: 8, | |
| alignment: "center", | |
| }, | |
| [ | |
| Icon( | |
| activity.type === "buy" | |
| ? "plus" | |
| : activity.type === "sell" | |
| ? "minus" | |
| : "info", | |
| { size: 20, fill: "white" }, | |
| ), | |
| ], | |
| ), | |
| VStack({ spacing: 2, width: "fill", padding: 8 }, [ | |
| Text(`${activity.type.toUpperCase()} ${activity.symbol}`, { | |
| fontSize: 14, | |
| color: theme.text, | |
| }), | |
| Text(activity.time, { fontSize: 11, color: theme.textMuted }), | |
| ]), | |
| Text(`$${activity.amount.toFixed(2)}`, { | |
| fontSize: 14, | |
| color: theme.text, | |
| }), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ]); | |
| } | |
| function RenderSettings(theme, darkMode, setDarkMode, autoRefresh, setAutoRefresh, notifications, setNotifications, setShowSettings) { | |
| return VStack({ spacing: 20, width: "fill" }, [ | |
| HStack({ width: "fill", alignment: "center" }, [ | |
| Text("Settings", { fontSize: 24, color: theme.text }), | |
| Spacer(), | |
| Button( | |
| { | |
| onClick: () => setShowSettings(false), | |
| background: theme.card, | |
| hoverBackground: theme.cardHover, | |
| padding: 8, | |
| cornerRadius: 6, | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| Icon("close", { size: 16, fill: theme.iconColor }), | |
| ), | |
| ]), | |
| VStack( | |
| { | |
| background: theme.card, | |
| padding: 20, | |
| cornerRadius: 12, | |
| spacing: 16, | |
| width: "fill", | |
| border: { width: 1, color: theme.border }, | |
| }, | |
| [ | |
| SettingRow("Dark Mode", "Use dark theme", darkMode, setDarkMode, theme), | |
| SettingRow("Notifications", "Get price alerts", notifications, setNotifications, theme), | |
| SettingRow( | |
| "Auto-refresh", | |
| "Update prices automatically", | |
| autoRefresh, | |
| setAutoRefresh, | |
| theme, | |
| ), | |
| ], | |
| ), | |
| ]); | |
| } | |
| function SettingRow(title, subtitle, value, setValue, theme) { | |
| return HStack({ padding: 12, width: "fill" }, [ | |
| VStack({ spacing: 4, width: "fill" }, [ | |
| Text(title, { fontSize: 14, color: theme.text }), | |
| Text(subtitle, { fontSize: 11, color: theme.textMuted }), | |
| ]), | |
| Button( | |
| { | |
| onClick: () => setValue(!value), | |
| background: value ? theme.success : theme.border, | |
| padding: 8, | |
| cornerRadius: 6, | |
| border: { width: 1, color: value ? theme.success : theme.border }, | |
| }, | |
| Text(value ? "ON" : "OFF", { | |
| fontSize: 11, | |
| color: value ? "white" : theme.text, | |
| }), | |
| ), | |
| ]); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment