@@ -9,11 +23,12 @@ export function Table({ children }: { children: ReactNode }) {
)
}
-export function Th({ children, className }: { children?: ReactNode; className?: string }) {
+export function Th({ children, className, size = 'md' }: { children?: ReactNode; className?: string; size?: TableSize }) {
return (
@@ -22,9 +37,9 @@ export function Th({ children, className }: { children?: ReactNode; className?:
)
}
-export function Td({ children, className }: { children?: ReactNode; className?: string }) {
+export function Td({ children, className, size = 'md' }: { children?: ReactNode; className?: string; size?: TableSize }) {
return (
- |
+ |
{children}
|
)
diff --git a/web/src/components/ui/tabs.tsx b/web/src/components/ui/tabs.tsx
index 9243429..a6011a3 100644
--- a/web/src/components/ui/tabs.tsx
+++ b/web/src/components/ui/tabs.tsx
@@ -5,12 +5,19 @@ export function Tabs({
onValueChange,
items,
className,
+ size = 'md',
}: {
value: string
onValueChange: (v: string) => void
items: { value: string; label: string }[]
className?: string
+ size?: 'sm' | 'md' | 'lg'
}) {
+ const tabSizes = {
+ sm: 'px-2.5 py-1 text-[12px]',
+ md: 'px-3.5 py-1.5 text-[13px]',
+ lg: 'px-4 py-2 text-[14px]',
+ }
return (
) {
- return
+const sizes: Record = {
+ sm: 'text-[length:var(--ui-font-sm)] px-[var(--ui-px-sm)] py-1.5 min-h-16',
+ md: 'text-[length:var(--ui-font-md)] px-[var(--ui-px-md)] py-2 min-h-24',
+ lg: 'text-[length:var(--ui-font-lg)] px-[var(--ui-px-lg)] py-2.5 min-h-32',
+}
+
+export function Textarea({ className, size = 'md', ...props }: TextareaHTMLAttributes & { size?: 'sm' | 'md' | 'lg' }) {
+ return
}
diff --git a/web/src/index.css b/web/src/index.css
index 37a99e4..ea7397d 100644
--- a/web/src/index.css
+++ b/web/src/index.css
@@ -78,6 +78,44 @@
--info: #2563eb;
}
+/* ---- 尺寸系统:默认 md ---- */
+:root {
+ --ui-height-sm: 1.75rem; /* 28px */
+ --ui-height-md: 2.25rem; /* 36px */
+ --ui-height-lg: 2.75rem; /* 44px */
+ --ui-font-sm: 12px;
+ --ui-font-md: 14px;
+ --ui-font-lg: 15px;
+ --ui-px-sm: 0.5rem;
+ --ui-px-md: 0.75rem;
+ --ui-px-lg: 1rem;
+ --ui-gap-sm: 0.25rem;
+ --ui-gap-md: 0.375rem;
+ --ui-gap-lg: 0.5rem;
+ --ui-table-py-sm: 0.375rem;
+ --ui-table-py-md: 0.625rem;
+ --ui-table-py-lg: 0.75rem;
+}
+
+/* 紧凑模式 */
+[data-density='compact'] {
+ --ui-height-sm: 1.5rem; /* 24px */
+ --ui-height-md: 1.875rem; /* 30px */
+ --ui-height-lg: 2.25rem; /* 36px */
+ --ui-font-sm: 11px;
+ --ui-font-md: 13px;
+ --ui-font-lg: 14px;
+ --ui-px-sm: 0.375rem;
+ --ui-px-md: 0.5rem;
+ --ui-px-lg: 0.75rem;
+ --ui-gap-sm: 0.125rem;
+ --ui-gap-md: 0.25rem;
+ --ui-gap-lg: 0.375rem;
+ --ui-table-py-sm: 0.25rem;
+ --ui-table-py-md: 0.375rem;
+ --ui-table-py-lg: 0.5rem;
+}
+
*,
*::before,
*::after {
@@ -234,10 +272,10 @@ pre,
display: inline-flex;
align-items: center;
justify-content: center;
- gap: 0.375rem;
+ gap: var(--ui-gap-md);
border: 1px solid transparent;
border-radius: var(--radius-md);
- font-size: 14px;
+ font-size: var(--ui-font-md);
font-weight: 500;
line-height: 1.2;
transition:
@@ -310,19 +348,38 @@ pre,
}
.ui-btn-sm {
- height: 2rem;
- padding: 0 0.625rem;
- font-size: 12px;
+ height: var(--ui-height-sm);
+ padding: 0 var(--ui-px-sm);
+ font-size: var(--ui-font-sm);
}
.ui-btn-md {
- height: 2.25rem;
- padding: 0 0.75rem;
+ height: var(--ui-height-md);
+ padding: 0 var(--ui-px-md);
+ font-size: var(--ui-font-md);
+}
+
+.ui-btn-lg {
+ height: var(--ui-height-lg);
+ padding: 0 var(--ui-px-lg);
+ font-size: var(--ui-font-lg);
}
.ui-btn-icon {
- height: 2.25rem;
- width: 2.25rem;
+ height: var(--ui-height-md);
+ width: var(--ui-height-md);
+ padding: 0;
+}
+
+.ui-btn-icon-sm {
+ height: var(--ui-height-sm);
+ width: var(--ui-height-sm);
+ padding: 0;
+}
+
+.ui-btn-icon-lg {
+ height: var(--ui-height-lg);
+ width: var(--ui-height-lg);
padding: 0;
}
@@ -357,9 +414,9 @@ pre,
background: transparent;
color: var(--text-primary);
cursor: pointer;
- font-size: 13px;
+ font-size: var(--ui-font-sm);
line-height: 1.35;
- min-height: 2rem;
+ min-height: var(--ui-height-sm);
padding: 0.5rem 0.75rem;
text-align: left;
}