From 37e325d3cf25425d0d894534a75d66780f6ef940 Mon Sep 17 00:00:00 2001 From: cita Date: Tue, 10 Mar 2026 15:09:41 +0800 Subject: [PATCH] feat: add mobile card components --- src/web/components/MobileCard.tsx | 33 ++++++++++++++ src/web/components/mobile-card.test.tsx | 22 +++++++++ src/web/index.css | 59 +++++++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 src/web/components/MobileCard.tsx create mode 100644 src/web/components/mobile-card.test.tsx diff --git a/src/web/components/MobileCard.tsx b/src/web/components/MobileCard.tsx new file mode 100644 index 0000000..108f977 --- /dev/null +++ b/src/web/components/MobileCard.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +type MobileCardProps = { + title: React.ReactNode; + actions?: React.ReactNode; + children: React.ReactNode; +}; + +type MobileFieldProps = { + label: React.ReactNode; + value: React.ReactNode; +}; + +export function MobileCard({ title, actions, children }: MobileCardProps) { + return ( +
+
+
{title}
+ {actions ?
{actions}
: null} +
+
{children}
+
+ ); +} + +export function MobileField({ label, value }: MobileFieldProps) { + return ( +
+
{label}
+
{value}
+
+ ); +} diff --git a/src/web/components/mobile-card.test.tsx b/src/web/components/mobile-card.test.tsx new file mode 100644 index 0000000..45a3d84 --- /dev/null +++ b/src/web/components/mobile-card.test.tsx @@ -0,0 +1,22 @@ +import { describe, expect, it } from 'vitest'; +import { create } from 'react-test-renderer'; +import { MobileCard, MobileField } from './MobileCard.js'; + +describe('MobileCard', () => { + it('renders title and fields', () => { + const root = create( + + + , + ); + + const text = root.root.findAll(() => true) + .flatMap((instance) => instance.children) + .filter((child): child is string => typeof child === 'string') + .join(''); + + expect(text).toContain('CardTitle'); + expect(text).toContain('Status'); + expect(text).toContain('OK'); + }); +}); diff --git a/src/web/index.css b/src/web/index.css index a616567..6238769 100644 --- a/src/web/index.css +++ b/src/web/index.css @@ -964,6 +964,65 @@ body { transform: translateY(-2px); } +/* ===== Mobile Card ===== */ +.mobile-card-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.mobile-card { + background: var(--color-bg-card); + border-radius: var(--radius-md); + border: 1px solid var(--color-border-light); + box-shadow: var(--shadow-card); + padding: 14px; +} + +.mobile-card-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 8px; + margin-bottom: 10px; +} + +.mobile-card-title { + font-size: 14px; + font-weight: 600; + color: var(--color-text-primary); +} + +.mobile-card-actions { + display: flex; + gap: 6px; +} + +.mobile-card-body { + display: flex; + flex-direction: column; + gap: 8px; +} + +.mobile-field { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: 12px; +} + +.mobile-field-label { + font-size: 11px; + color: var(--color-text-muted); +} + +.mobile-field-value { + font-size: 12px; + font-weight: 500; + color: var(--color-text-primary); + text-align: right; +} + /* ===== Stat Card (New API style) ===== */ .stat-card { background: var(--color-bg-card);