1
0
forked from boos/BoosAPI

feat: add mobile card components

This commit is contained in:
cita
2026-03-10 15:09:41 +08:00
parent d91a160455
commit 37e325d3cf
3 changed files with 114 additions and 0 deletions
+33
View File
@@ -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 (
<div className="mobile-card">
<div className="mobile-card-header">
<div className="mobile-card-title">{title}</div>
{actions ? <div className="mobile-card-actions">{actions}</div> : null}
</div>
<div className="mobile-card-body">{children}</div>
</div>
);
}
export function MobileField({ label, value }: MobileFieldProps) {
return (
<div className="mobile-field">
<div className="mobile-field-label">{label}</div>
<div className="mobile-field-value">{value}</div>
</div>
);
}
+22
View File
@@ -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(
<MobileCard title="CardTitle">
<MobileField label="Status" value="OK" />
</MobileCard>,
);
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');
});
});
+59
View File
@@ -964,6 +964,65 @@ body {
transform: translateY(-2px); 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 (New API style) ===== */
.stat-card { .stat-card {
background: var(--color-bg-card); background: var(--color-bg-card);