mirror of
https://github.com/dataelement/bisheng.git
synced 2026-09-21 12:43:36 +08:00
fix(client): register semantic font-size tokens in tailwind-merge (cn)
extendTailwindMerge teaches cn() the custom text-caption/body/h1... tokens as a font-size group (v1.x config shape) so a size + a color in the same cn() call no longer drop the size; gallery Modal/ConfirmDialog sections + kit updates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
9faf41afd5
commit
6bfedd60f4
@@ -77,7 +77,7 @@ export default function GalleryApp() {
|
||||
key={p.id}
|
||||
onClick={() => setActiveId(p.id)}
|
||||
className={cn(
|
||||
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-body-sm transition-colors',
|
||||
'flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-body transition-colors',
|
||||
activeId === p.id
|
||||
? 'bg-blue-500/[0.08] font-medium text-text-primary'
|
||||
: 'text-muted-foreground hover:bg-muted/60',
|
||||
|
||||
@@ -49,7 +49,7 @@ export function ComponentPage({
|
||||
<h2 className="mb-3 text-h3 text-text-primary">何时使用 / 规则</h2>
|
||||
<ul className="space-y-2 rounded-xl border border-border-light bg-muted/20 p-5">
|
||||
{whenToUse.map((item, i) => (
|
||||
<li key={i} className="flex gap-2 text-body-sm text-text-primary">
|
||||
<li key={i} className="flex gap-2 text-body text-text-primary">
|
||||
<span className="mt-2 size-1.5 shrink-0 rounded-full bg-blue-500" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
@@ -168,9 +168,9 @@ export function CompareTable({
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ *
|
||||
* Legacy aliases — kept so not-yet-migrated sections still compile.
|
||||
* New pages should use ComponentPage / ExampleGroup / ExampleCard / ExampleGrid.
|
||||
* TODO(gallery): migrate Modal / ConfirmDialog sections to the new template.
|
||||
* Legacy aliases — Demo / DemoGrid are still used by migrated pages
|
||||
* (they now forward to ExampleCard / ExampleGrid). Section is unused by
|
||||
* gallery pages (all migrated to ComponentPage) but kept as a safety net.
|
||||
* ------------------------------------------------------------------ */
|
||||
|
||||
/** @deprecated use ExampleGroup inside a ComponentPage. */
|
||||
|
||||
@@ -12,7 +12,7 @@ import { OGDialog, OGDialogTrigger } from '~/components/ui/OriginalDialog';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { Label } from '~/components/ui/Label';
|
||||
import { useConfirm } from '~/Providers';
|
||||
import { Section, Demo, DemoGrid, CompareTable } from '../components/kit';
|
||||
import { ComponentPage, ExampleGroup, Demo, DemoGrid, CompareTable } from '../components/kit';
|
||||
|
||||
/** Demos for the app-wide `useConfirm()` service (ConfirmContext, AlertDialog-based). */
|
||||
function UseConfirmDemos() {
|
||||
@@ -87,7 +87,7 @@ function ConfirmDemo({
|
||||
title={title}
|
||||
className="max-w-[450px]"
|
||||
main={
|
||||
<Label className="text-left text-sm font-medium">{body}</Label>
|
||||
<Label className="text-left text-body font-medium">{body}</Label>
|
||||
}
|
||||
selection={{
|
||||
selectHandler: () => undefined,
|
||||
@@ -103,10 +103,10 @@ function ConfirmDemo({
|
||||
|
||||
export function ConfirmDialogSection() {
|
||||
return (
|
||||
<Section
|
||||
id="confirm"
|
||||
<ComponentPage
|
||||
title="二次确认弹窗"
|
||||
subtitle={
|
||||
eng="Confirm Dialog"
|
||||
description={
|
||||
<>
|
||||
删除/危险操作时的「确认 / 取消」小弹窗。两套体系:旧页面走{' '}
|
||||
<code>OGDialogTemplate selection</code>(剩 13 文件:7 处死 UI 确认 + 6 处表单弹窗),新页面走{' '}
|
||||
@@ -116,9 +116,14 @@ export function ConfirmDialogSection() {
|
||||
(另有 9 个文件手拼 <code>AlertDialog</code> —— 属于普通弹窗,归 Modal 改造范围,本期不动。)
|
||||
</>
|
||||
}
|
||||
whenToUse={[
|
||||
<>危险操作(删除等)用 <code>useConfirm()</code>:<code>variant: destructive</code>(红)/ <code>default</code>(橙色警示)。</>,
|
||||
<>确认按钮只两档:danger <code>#f53f3f</code> / primary 主色(<code>selectVariant</code> 指定);特例走 <code>selectClasses</code> 口子。</>,
|
||||
<>壳:圆角 16 / p-5 / 灰底毛玻璃;取消按钮白底描边。B 套已对齐 C 套基准。</>,
|
||||
]}
|
||||
>
|
||||
{/* The three coexisting confirm-dialog systems */}
|
||||
<div className="mb-6">
|
||||
{/* The two coexisting confirm-dialog systems */}
|
||||
<ExampleGroup title="现状:两套确认体系">
|
||||
<CompareTable
|
||||
head={['体系', '实现', '业务文件数', '用在哪', '样式一致性']}
|
||||
rows={[
|
||||
@@ -142,10 +147,10 @@ export function ConfirmDialogSection() {
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ExampleGroup>
|
||||
|
||||
{/* Inventory table: every selectClasses variant found in business code */}
|
||||
<div className="mb-6">
|
||||
<ExampleGroup title="确认按钮 selectClasses 清单(9 种历史写法)">
|
||||
<CompareTable
|
||||
head={['#', '确认按钮 selectClasses(原文)', '用处', '文件数']}
|
||||
rows={[
|
||||
@@ -209,10 +214,10 @@ export function ConfirmDialogSection() {
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ExampleGroup>
|
||||
|
||||
{/* Anatomy after step-1 convergence — B shell/buttons now mirror the C look */}
|
||||
<div className="mb-6">
|
||||
<ExampleGroup title="对齐后的规格(B 套 = C 套)">
|
||||
<CompareTable
|
||||
head={['部位', '对齐后的值(B 套 = C 套)', '备注']}
|
||||
rows={[
|
||||
@@ -251,9 +256,10 @@ export function ConfirmDialogSection() {
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ExampleGroup>
|
||||
|
||||
<DemoGrid cols={3}>
|
||||
<ExampleGroup title="逐个打开对比(旧写法现应呈现统一外观)">
|
||||
<DemoGrid cols={3}>
|
||||
<UseConfirmDemos />
|
||||
<ConfirmDemo
|
||||
label="① red-700 系(6 处,原 8)"
|
||||
@@ -317,6 +323,7 @@ export function ConfirmDialogSection() {
|
||||
selectClasses="btn btn-primary"
|
||||
/>
|
||||
</DemoGrid>
|
||||
</Section>
|
||||
</ExampleGroup>
|
||||
</ComponentPage>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,12 +34,12 @@ import {
|
||||
import DialogTemplate from '~/components/ui/DialogTemplate';
|
||||
import OGDialogTemplate from '~/components/ui/OGDialogTemplate';
|
||||
import { useConfirm } from '~/Providers';
|
||||
import { Section, Demo, DemoGrid, CompareTable } from '../components/kit';
|
||||
import { ComponentPage, ExampleGroup, Demo, DemoGrid, CompareTable } from '../components/kit';
|
||||
|
||||
/** Identical body for every shell so only the shell itself differs. */
|
||||
const demoBody = (
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="text-sm text-text-primary">
|
||||
<p className="text-body text-text-primary">
|
||||
这里是弹窗正文示例。观察内边距、行距与正文和标题/按钮的间距。
|
||||
</p>
|
||||
<Input placeholder="示例输入框" />
|
||||
@@ -68,22 +68,24 @@ function ConfirmReferenceDemo() {
|
||||
|
||||
export function ModalSection() {
|
||||
return (
|
||||
<Section
|
||||
id="modal"
|
||||
<ComponentPage
|
||||
title="Modal 弹窗"
|
||||
subtitle={
|
||||
eng="Modal"
|
||||
description={
|
||||
<>
|
||||
2026-07-09 重新盘点:全站普通弹窗共 <b>5 个并行体系、约 64 个业务文件</b>
|
||||
。二次确认期已把 B 套壳对齐 C 套视觉,但 A 套(原语直接拼 22 处 + 模板 3
|
||||
处)与手拼 AlertDialog(7 处)仍是另外两种壳。逐个打开对比,定统一标准。
|
||||
</>
|
||||
}
|
||||
whenToUse={[
|
||||
<>本页是<b>现状盘点</b>,统一标准尚未定稿(见文末"④ 待设计师定夺")。</>,
|
||||
<>已定:二次确认(C 套)壳为基准候选——圆角 16 / p-5 / 灰底毛玻璃;B 套已对齐。</>,
|
||||
<>待定:A 套(最大人群 22 处)的遮罩 / 圆角 / 标题字重是否并入基准。</>,
|
||||
]}
|
||||
>
|
||||
{/* ① Population overview */}
|
||||
<h3 className="mb-3 mt-2 text-base font-semibold text-text-primary">
|
||||
① 用量盘子(当前精确扫描,排除 ui/ 与画廊)
|
||||
</h3>
|
||||
<div className="mb-8">
|
||||
<ExampleGroup title="① 用量盘子(当前精确扫描,排除 ui/ 与画廊)">
|
||||
<CompareTable
|
||||
head={['体系', '实现', '业务文件数', '用在哪 / 备注']}
|
||||
rows={[
|
||||
@@ -125,13 +127,10 @@ export function ModalSection() {
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ExampleGroup>
|
||||
|
||||
{/* ② Shell anatomy */}
|
||||
<h3 className="mb-3 text-base font-semibold text-text-primary">
|
||||
② 壳样式解剖(源码当前真实值)
|
||||
</h3>
|
||||
<div className="mb-8">
|
||||
<ExampleGroup title="② 壳样式解剖(源码当前真实值)">
|
||||
<CompareTable
|
||||
head={['维度', 'A 套 Dialog', 'B 套 OriginalDialog(已对齐 C 套)', 'AlertDialog(手拼底座)']}
|
||||
rows={[
|
||||
@@ -182,13 +181,10 @@ export function ModalSection() {
|
||||
],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</ExampleGroup>
|
||||
|
||||
{/* ③ Side-by-side demos — identical content, different shells */}
|
||||
<h3 className="mb-3 text-base font-semibold text-text-primary">
|
||||
③ 同一内容装进各个壳(逐个打开对比)
|
||||
</h3>
|
||||
<div className="mb-8">
|
||||
<ExampleGroup title="③ 同一内容装进各个壳(逐个打开对比)">
|
||||
<DemoGrid cols={3}>
|
||||
{/* A-set raw primitives — the largest population */}
|
||||
<Demo label="A 套 · 原语直接拼(22 处)" note="ui/Dialog.tsx · 浅黑毛玻璃 · 圆角8 · p-5">
|
||||
@@ -278,7 +274,7 @@ export function ModalSection() {
|
||||
</AlertDialogTrigger>
|
||||
<AlertDialogContent className="max-w-md">
|
||||
{/* Business pages hand-roll header/footer like this (ChannelMemberDialog etc.) */}
|
||||
<h3 className="text-base font-medium text-text-primary">弹窗标题</h3>
|
||||
<h3 className="text-h4 text-text-primary">弹窗标题</h3>
|
||||
{demoBody}
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline">取消</Button>
|
||||
@@ -296,11 +292,11 @@ export function ModalSection() {
|
||||
<ConfirmReferenceDemo />
|
||||
</Demo>
|
||||
</DemoGrid>
|
||||
</div>
|
||||
</ExampleGroup>
|
||||
|
||||
{/* ④ Decision checklist */}
|
||||
<h3 className="mb-3 text-base font-semibold text-text-primary">④ 待设计师定夺</h3>
|
||||
<div className="rounded-xl border border-border-light bg-muted/20 p-5 text-sm leading-7 text-text-primary">
|
||||
<ExampleGroup title="④ 待设计师定夺">
|
||||
<div className="rounded-xl border border-border-light bg-muted/20 p-5 text-body text-text-primary">
|
||||
<ol className="list-decimal space-y-1 pl-5">
|
||||
<li>
|
||||
<b>遮罩</b>:A 套浅黑毛玻璃(black/40+blur) vs B/C 套灰白毛玻璃(gray-500/90+blur)?
|
||||
@@ -330,6 +326,7 @@ export function ModalSection() {
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</Section>
|
||||
</ExampleGroup>
|
||||
</ComponentPage>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,29 @@
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
import { extendTailwindMerge } from 'tailwind-merge';
|
||||
import { type ClassValue, clsx } from 'clsx';
|
||||
|
||||
/**
|
||||
* twMerge taught about the project's custom semantic font-size tokens
|
||||
* (see docs 基础-字体规范.md §7 / theme.extend.fontSize in tailwind.config.cjs).
|
||||
*
|
||||
* Why this is required: tailwind-merge's `text-*` handling treats any UNKNOWN
|
||||
* `text-X` as a text COLOR (catch-all). So `text-body` / `text-h1` / `text-caption`
|
||||
* … are mis-grouped as colors, and when a real color (e.g. `text-text-primary`)
|
||||
* appears in the SAME cn() call, the font size is silently dropped → element
|
||||
* falls back to the inherited 16px. Registering them in the `font-size` group
|
||||
* makes cn() preserve size + color together, and collapse size-vs-size correctly.
|
||||
*
|
||||
* NOTE: this uses the tailwind-merge **v1.x** config shape (`classGroups` at the
|
||||
* top level). v2+ nests it under `extend` — do not copy a v2 snippet here while
|
||||
* the dependency is pinned to ^1.14.0.
|
||||
*/
|
||||
const twMerge = extendTailwindMerge({
|
||||
classGroups: {
|
||||
'font-size': [
|
||||
{ text: ['caption', 'body-sm', 'body', 'h1', 'h2', 'h3', 'h4', 'display', 'metric'] },
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Merges the tailwind clases (using twMerge). Conditionally removes false values
|
||||
* @param inputs The tailwind classes to merge
|
||||
|
||||
Reference in New Issue
Block a user