@haposoft/cafekit 0.16.7 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -22
- package/bin/__tests__/codex-hooks-ownership.test.js +2 -2
- package/bin/__tests__/codex-hooks.test.js +0 -36
- package/bin/__tests__/codex-native.test.js +46 -88
- package/bin/__tests__/develop-contract.test.js +26 -57
- package/bin/__tests__/docs-sync-on-demand.test.js +93 -0
- package/bin/__tests__/grok-envelope.test.js +0 -56
- package/bin/__tests__/hook-payload.test.js +4 -6
- package/bin/__tests__/legacy-authority-decoupled.test.js +159 -0
- package/bin/__tests__/legacy-hooks-retired.test.js +221 -0
- package/bin/__tests__/omp-bridge.test.js +20 -17
- package/bin/__tests__/omp-hooks.test.js +0 -19
- package/bin/__tests__/optional-skill-inventory.test.js +112 -4
- package/bin/__tests__/orca-session.test.js +0 -2
- package/bin/__tests__/package-inventory.test.js +100 -541
- package/bin/__tests__/runtime-dir.test.js +0 -3
- package/bin/__tests__/runtime-schema.test.js +3 -2
- package/bin/__tests__/skill-routing-source.test.js +27 -15
- package/bin/__tests__/spec-narrowing.test.js +0 -27
- package/bin/__tests__/specs-v2-execution-closeout.test.js +4 -69
- package/bin/__tests__/ui-ux-skill.test.js +101 -0
- package/bin/__tests__/usage-hook-retired.test.js +114 -0
- package/bin/__tests__/validator-grounding.test.js +9 -28
- package/bin/phases/codex-runtime.js +24 -0
- package/bin/phases/omp-runtime.js +22 -0
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +3 -10
- package/src/claude/agents/brainstormer.md +7 -3
- package/src/claude/agents/code-auditor.md +35 -50
- package/src/claude/agents/debugger.md +5 -79
- package/src/claude/agents/deployer.md +3 -5
- package/src/claude/agents/docs-keeper.md +2 -7
- package/src/claude/agents/implementer.md +6 -9
- package/src/claude/agents/inspector.md +2 -3
- package/src/claude/agents/project-manager.md +7 -15
- package/src/claude/agents/researcher.md +9 -3
- package/src/claude/agents/spec-maker.md +11 -11
- package/src/claude/hooks/__tests__/fixtures/statusline-default.golden +1 -1
- package/src/claude/hooks/__tests__/spec-gate.test.js +74 -3
- package/src/claude/hooks/__tests__/spec-state-touched.test.js +134 -0
- package/src/claude/hooks/__tests__/state.test.js +68 -0
- package/src/claude/hooks/__tests__/statusline.test.js +140 -11
- package/src/claude/hooks/agent.cjs +2 -1
- package/src/claude/hooks/lib/context.cjs +0 -89
- package/src/claude/hooks/lib/hook-payload.cjs +2 -2
- package/src/claude/hooks/rules.cjs +2 -1
- package/src/claude/hooks/session.cjs +0 -1
- package/src/claude/hooks/spec-gate.cjs +17 -28
- package/src/claude/hooks/spec-state.cjs +95 -1
- package/src/claude/hooks/state.cjs +21 -2
- package/src/claude/migration-manifest.json +30 -15
- package/src/claude/rules/ai-dev-rules.md +1 -1
- package/src/claude/rules/hook-protocols.md +8 -15
- package/src/claude/rules/manage-docs.md +9 -23
- package/src/claude/rules/orchestrator.md +3 -4
- package/src/claude/rules/review-audit-self-decision.md +3 -3
- package/src/claude/rules/skill-domain-routing.md +1 -3
- package/src/claude/rules/skill-workflow-routing.md +4 -5
- package/src/claude/rules/state-sync.md +3 -12
- package/src/claude/rules/workflow.md +10 -18
- package/src/claude/runtime.schema.json +5 -3
- package/src/claude/scripts/generate-skill-catalog.cjs +7 -6
- package/src/claude/scripts/provenance.cjs +23 -5
- package/src/claude/scripts/spec-receipt.cjs +24 -7
- package/src/claude/scripts/spec-resolver.cjs +1 -2
- package/src/claude/scripts/validate-spec-output.cjs +5 -32
- package/src/claude/settings/settings.json +1 -32
- package/src/claude/skills/{question → ask}/SKILL.md +9 -75
- package/src/claude/skills/brainstorm/SKILL.md +43 -131
- package/src/claude/skills/code-review/SKILL.md +48 -17
- package/src/claude/skills/code-review/references/spec-compliance-review.md +2 -3
- package/src/claude/skills/code-review/references/verification-gate.md +22 -27
- package/src/claude/skills/debug/SKILL.md +29 -149
- package/src/claude/skills/develop/SKILL.md +27 -36
- package/src/claude/skills/develop/references/parallel-waves.md +0 -8
- package/src/claude/skills/develop/references/quality-gate.md +15 -13
- package/src/claude/skills/docs/SKILL.md +2 -2
- package/src/claude/skills/docs/references/update-workflow.md +1 -1
- package/src/claude/skills/fix/SKILL.md +153 -0
- package/src/claude/skills/{hotfix → fix}/references/diagnosis-protocol.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/parallel-patterns.md +2 -2
- package/src/claude/skills/{hotfix → fix}/references/prevention-gate.md +3 -3
- package/src/claude/skills/{hotfix → fix}/references/review-cycle.md +1 -1
- package/src/claude/skills/git/SKILL.md +48 -9
- package/src/claude/skills/git/references/commit-protocols.md +35 -16
- package/src/claude/skills/git/references/worktree-blueprint.md +42 -6
- package/src/claude/skills/orca/SKILL.md +9 -8
- package/src/claude/skills/research/SKILL.md +2 -0
- package/src/claude/skills/scout/SKILL.md +61 -0
- package/src/claude/skills/{inspect → scout}/references/internal-inspection.md +26 -10
- package/src/claude/skills/specs/SKILL.md +22 -29
- package/src/claude/skills/specs/references/review.md +8 -6
- package/src/claude/skills/specs/references/templates.md +34 -41
- package/src/claude/skills/sync/SKILL.md +8 -15
- package/src/claude/skills/sync/references/rebind-and-audit.md +59 -0
- package/src/claude/skills/sync/references/sync-protocols.md +4 -13
- package/src/claude/skills/test/SKILL.md +22 -3
- package/src/claude/skills/test/references/execution-strategy.md +83 -0
- package/src/claude/skills/test/references/failure-triage.md +1 -1
- package/src/claude/skills/ui-ux/LICENSE +21 -0
- package/src/claude/skills/ui-ux/SKILL.md +497 -0
- package/src/claude/skills/ui-ux/references/brand-tokens.md +90 -0
- package/src/claude/skills/ui-ux/references/budgets.md +113 -0
- package/src/claude/skills/ui-ux/references/checklist.md +271 -0
- package/src/claude/skills/ui-ux/references/components/accordion.md +246 -0
- package/src/claude/skills/ui-ux/references/components/avatar.md +153 -0
- package/src/claude/skills/ui-ux/references/components/banner.md +43 -0
- package/src/claude/skills/ui-ux/references/components/breadcrumb.md +201 -0
- package/src/claude/skills/ui-ux/references/components/button.md +142 -0
- package/src/claude/skills/ui-ux/references/components/card.md +113 -0
- package/src/claude/skills/ui-ux/references/components/charts.md +376 -0
- package/src/claude/skills/ui-ux/references/components/chat.md +132 -0
- package/src/claude/skills/ui-ux/references/components/choice-controls.md +369 -0
- package/src/claude/skills/ui-ux/references/components/comment-thread.md +17 -0
- package/src/claude/skills/ui-ux/references/components/description-list.md +116 -0
- package/src/claude/skills/ui-ux/references/components/empty-state.md +98 -0
- package/src/claude/skills/ui-ux/references/components/file-upload.md +107 -0
- package/src/claude/skills/ui-ux/references/components/inline-edit.md +128 -0
- package/src/claude/skills/ui-ux/references/components/input.md +161 -0
- package/src/claude/skills/ui-ux/references/components/list-row.md +56 -0
- package/src/claude/skills/ui-ux/references/components/loading.md +198 -0
- package/src/claude/skills/ui-ux/references/components/logo.md +147 -0
- package/src/claude/skills/ui-ux/references/components/otp-input.md +43 -0
- package/src/claude/skills/ui-ux/references/components/quantity-input.md +100 -0
- package/src/claude/skills/ui-ux/references/components/range-slider.md +16 -0
- package/src/claude/skills/ui-ux/references/components/small-controls.md +340 -0
- package/src/claude/skills/ui-ux/references/components/sortable-header.md +86 -0
- package/src/claude/skills/ui-ux/references/components/tag-input.md +14 -0
- package/src/claude/skills/ui-ux/references/components/timeline.md +38 -0
- package/src/claude/skills/ui-ux/references/components/tree.md +30 -0
- package/src/claude/skills/ui-ux/references/design-process.md +650 -0
- package/src/claude/skills/ui-ux/references/layouts/app-kanban.html +303 -0
- package/src/claude/skills/ui-ux/references/layouts/app.md +1369 -0
- package/src/claude/skills/ui-ux/references/layouts/form.md +430 -0
- package/src/claude/skills/ui-ux/references/layouts/overlay.md +552 -0
- package/src/claude/skills/ui-ux/references/layouts/pricing.md +204 -0
- package/src/claude/skills/ui-ux/references/locked-rules.md +35 -0
- package/src/claude/skills/ui-ux/references/principles.md +408 -0
- package/src/claude/skills/ui-ux/references/refactor.md +245 -0
- package/src/claude/skills/ui-ux/references/responsive.md +195 -0
- package/src/claude/skills/ui-ux/references/review.md +528 -0
- package/src/claude/skills/ui-ux/references/rules-color.md +623 -0
- package/src/claude/skills/ui-ux/references/rules-form.md +235 -0
- package/src/claude/skills/ui-ux/references/rules-state.md +680 -0
- package/src/claude/skills/ui-ux/references/rules-type.md +369 -0
- package/src/claude/skills/ui-ux/references/styles.md +311 -0
- package/src/claude/skills/ui-ux/references/system.md +219 -0
- package/src/claude/skills/ui-ux/references/tailwind-v4-traps.md +237 -0
- package/src/claude/skills/ui-ux/references/tokens.css +362 -0
- package/src/claude/skills/ui-ux/scripts/lint-skill.mjs +216 -0
- package/src/claude/skills/ui-ux/scripts/package.json +9 -0
- package/src/claude/skills/ui-ux/scripts/probe.mjs +4447 -0
- package/src/claude/skills/ui-ux-pro-max/SKILL.md +1 -1
- package/src/claude/status.cjs +149 -144
- package/src/codex/AGENTS.md +8 -13
- package/src/codex/hooks/agent.cjs +2 -1
- package/src/codex/hooks/lib/spec-receipt.cjs +2 -2
- package/src/codex/hooks/lib/spec-utils.cjs +0 -7
- package/src/codex/hooks/rules.cjs +2 -1
- package/src/codex/hooks/session.cjs +0 -2
- package/src/codex/hooks/spec-gate.cjs +7 -18
- package/src/codex/hooks.json +0 -20
- package/src/codex/rules/state-sync.md +3 -12
- package/src/common/AGENTS.md +2 -2
- package/src/omp/extensions/cafekit-bridge.mjs +6 -6
- package/src/claude/hooks/__tests__/completion-authority.test.js +0 -990
- package/src/claude/hooks/__tests__/semantic-review-authority.test.js +0 -259
- package/src/claude/hooks/completion-authority-check.cjs +0 -56
- package/src/claude/hooks/completion-authority-state.cjs +0 -527
- package/src/claude/hooks/completion-authority.cjs +0 -163
- package/src/claude/hooks/semantic-review-authority.cjs +0 -154
- package/src/claude/hooks/task-scaffold-guard.cjs +0 -114
- package/src/claude/hooks/usage.cjs +0 -197
- package/src/claude/skills/delegate/SKILL.md +0 -124
- package/src/claude/skills/delegate/references/codex-delegation.md +0 -71
- package/src/claude/skills/delegate/references/grok-delegation.md +0 -84
- package/src/claude/skills/hotfix/SKILL.md +0 -289
- package/src/claude/skills/inspect/SKILL.md +0 -142
- package/src/claude/skills/loop/SKILL.md +0 -114
- package/src/claude/skills/loop/references/bounded-loop-protocol.md +0 -104
- package/src/claude/skills/loop/references/metric-and-guard-contract.md +0 -76
- package/src/claude/skills/route/SKILL.md +0 -94
- package/src/claude/skills/route/references/agent-timing.md +0 -46
- package/src/claude/skills/route/references/chaining-patterns.md +0 -53
- package/src/claude/skills/route/references/task-taxonomy.md +0 -48
- package/src/codex/hooks/completion-authority-check.cjs +0 -67
- package/src/codex/hooks/completion-authority-state.cjs +0 -527
- package/src/codex/hooks/completion-authority.cjs +0 -109
- package/src/codex/hooks/semantic-review-authority.cjs +0 -130
- package/src/codex/hooks/task-scaffold-guard.cjs +0 -63
- /package/src/claude/skills/{question → ask}/templates/question.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/escalation-tactics.md +0 -0
- /package/src/claude/skills/{hotfix → fix}/references/workflow-specialized.md +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# Responsive
|
|
2
|
+
|
|
3
|
+
Mọi luật về màn hẹp gom hết ở đây. `rules-form.md` không lặp lại, chỉ trỏ sang.
|
|
4
|
+
|
|
5
|
+
**Ngưỡng kiểm là 375px.** Kiểm trước khi báo xong, không phải kiểm sau khi bị
|
|
6
|
+
chê. Vòng tra tấn bắt buộc nằm ở `checklist.md` cổng 3.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**R1. Trang không bao giờ được cuộn ngang.** Thân trang vừa khít mọi bề rộng, kể cả 375px.
|
|
11
|
+
|
|
12
|
+
Triệu chứng dễ nhận nhất: cuộn sang ngang thì thấy **một mảng trống** bên phải, vì có một phần tử nào đó rộng hơn màn đẩy cả trang ra. Thấy mảng trống đó là biết ngay có thứ gì đang tràn, đi tìm bằng cách bỏ dần `min-w-0` ra. Nội dung thật sự rộng thì cho cuộn trong khung riêng `overflow-x-auto`, đừng để nó đẩy cả trang. Kiểm ở 375px **trước khi** báo xong.
|
|
13
|
+
|
|
14
|
+
**R2. Lưới xuống một cột ở mobile.** Mặc định `grid-cols-1`, rồi mới `sm:grid-cols-2` và `lg:grid-cols-4`. Ngoại lệ: hàng ô số liệu 2×2 ở mobile khi mọi số vừa ô (`components/charts.md`, "Ô số liệu ở màn hẹp").
|
|
15
|
+
|
|
16
|
+
**R3. Không để phần tử rớt hàng lẻ.** Wrap mà còn dư một cái đứng một mình ở hàng dưới thì đọc ra là lỗi, không phải là thiết kế.
|
|
17
|
+
|
|
18
|
+
- **Cụm nút thì không bao giờ rớt hàng.** Tìm cách giữ trên một hàng: rút chữ, giảm `px`, hạ chiều cao. Không được nữa thì cho cả cụm xuống một cột, mỗi nút một hàng đầy đủ, chứ đừng để hai nút trên một nút dưới.
|
|
19
|
+
- **Lưới có số cột lẻ thì đi thẳng từ 1 lên số cột đó**, bỏ qua bậc giữa: `grid-cols-1 lg:grid-cols-3`, đừng chèn `sm:grid-cols-2` vì 3 mục chia 2 cột sẽ thành 2 trên 1 dưới.
|
|
20
|
+
- **Số cột chẵn thì wrap thoải mái**, 4 mục chia 2 cột ra 2 hàng đều nhau, không ai lẻ loi.
|
|
21
|
+
|
|
22
|
+
**R4. Nhịp ở mobile: card `p-4`, tối đa `p-5`.** Đừng mang `p-8` của màn rộng xuống màn hẹp, 32px lề mỗi bên trên màn 375px là ăn mất một phần năm bề ngang. Nút ở mobile hạ về `h-10` — **trừ nút nằm trong form**, cái đó đi cùng chiều cao
|
|
23
|
+
ô nhập (`h-11` ở màn hẹp), xem `references/budgets.md`.
|
|
24
|
+
|
|
25
|
+
**R5. Chật là hỏng, không phải là đã responsive.** Thu nhỏ mà nội dung dồn cục, chữ vỡ ba bốn dòng, ảnh méo thì đó là chưa xử, không phải là xong. Cách xử, theo thứ tự nên thử:
|
|
26
|
+
|
|
27
|
+
1. Đổi hàng ngang thành xếp dọc.
|
|
28
|
+
2. Ảnh chữ nhật đổi thành **vuông nhỏ**, `h-16 w-16` hoặc `h-20 w-20`, đừng giữ tỉ lệ ngang rồi bóp lại.
|
|
29
|
+
3. Giảm cỡ chữ một bậc.
|
|
30
|
+
4. Cắt tiêu đề còn tối đa hai dòng bằng `line-clamp-2`.
|
|
31
|
+
|
|
32
|
+
Và luôn giữ: **ngày tháng, nhãn phụ phải nhỏ hơn tiêu đề ít nhất một bậc.** Tiêu đề `text-sm` thì ngày `text-xs`. Bằng nhau là mắt không biết đọc cái nào trước.
|
|
33
|
+
|
|
34
|
+
**R6. Ngoại lệ của R1 và R2: thứ tự tuyến tính thì không được wrap.** Board trạng thái, các bước quy trình, dòng thời gian, hàng chip lọc, hàng tab đều thuộc loại này. Xếp thành hai hàng thì mắt đọc theo hình chữ Z và mất dòng chảy; bốn chip ở 375px thì ba cái một hàng và một cái rớt xuống đứng lẻ, nhìn như lỗi. Cho cuộn ngang trong khung, mỗi phần tử `shrink-0`, cột kanban `w-[248px] grow max-w-[320px]` (vì sao thì xem `layouts/app.md`).
|
|
35
|
+
|
|
36
|
+
**Chip đang lọc (bấm để gỡ) không phải thứ tự tuyến tính**, nên không thuộc R6: từ `sm` trở lên cho `flex-wrap`, đặt thành một hàng riêng dưới thanh lọc, "Xoá lọc" nằm cuối và luôn thấy. Dưới `sm` mới cuộn ngang, và "Xoá lọc" đứng **ngoài** khung cuộn (`shrink-0`), không nằm cuối hàng cuộn. Nằm trong khung cuộn thì nhiều chip là "Xoá lọc" khuất theo, đúng nút cần nhất lại không thấy.
|
|
37
|
+
|
|
38
|
+
**Lề của vùng cuộn đặt trên hàng bên trong, không đặt trên khung cuộn.** Padding bên phải của khung `overflow-x-auto` bị nhiều trình duyệt bỏ qua khi cuộn tới cuối, nên phần tử cuối dính sát mép trong khi phần tử đầu vẫn có lề.
|
|
39
|
+
|
|
40
|
+
```html
|
|
41
|
+
<!-- Sai: cột cuối dính mép -->
|
|
42
|
+
<div class="overflow-x-auto px-3"><div class="flex gap-4">...</div></div>
|
|
43
|
+
|
|
44
|
+
<!-- Đúng: khối cha chỉ padding dọc, các khối khác tự mang lề, khung cuộn chạm mép -->
|
|
45
|
+
<section class="py-3 sm:py-5">
|
|
46
|
+
<h2 class="px-3 sm:px-5">…</h2>
|
|
47
|
+
<div class="overflow-x-auto"><div class="flex gap-4 px-3 sm:px-5">...</div></div>
|
|
48
|
+
</section>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Không kéo khung cuộn ra bằng `-mx-3 sm:-mx-5` bù padding của cha (`N11`): đổi padding cha mà
|
|
52
|
+
quên số âm là khung cuộn hụt hoặc lòi. Nhiều khối liền nhau không cuộn thì
|
|
53
|
+
gom vào một `<div class="px-3 sm:px-5">`. Khối nằm giữa khung trang và khung cuộn mà có `max-w-*` thì
|
|
54
|
+
nới `max-w` thêm đúng phần lấn ra hai bên, không thì các khối anh em hẹp lại.
|
|
55
|
+
|
|
56
|
+
Cách khác cũng được: chèn phần tử đệm cuối hàng, `<div class="w-3 shrink-0 sm:w-5" aria-hidden="true"></div>`.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
**R7. Chữ trong card ở mobile: tiêu đề giữ cỡ, mô tả `text-sm`.** Tiêu đề card `text-base font-semibold` **ở mọi breakpoint** (`D8`), luôn lớn hơn chữ bên trong một bậc (`T8`); dài thì `text-balance`, không hạ cỡ. Mô tả `text-sm` để card không dài lê thê. Đừng hạ tiêu đề xuống `text-sm` ở mobile: bằng cỡ mô tả, trái cả `T8` lẫn `D8`.
|
|
61
|
+
|
|
62
|
+
**R8. Chữ GÕ ĐƯỢC không bao giờ xuống dưới 16px ở mobile.** `input`, `textarea`,
|
|
63
|
+
`select` — dưới 16px thì iOS **tự phóng to cả trang** khi chạm vào ô, và không
|
|
64
|
+
tự thu lại. Người dùng đang điền form bỗng thấy trang nhảy một cái rồi lệch hẳn.
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<input class="text-base md:text-sm" />
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Đây là **ngoại lệ của `R7`**: chữ chỉ để đọc trong card thì hạ về `text-sm` ở
|
|
71
|
+
mobile, chữ gõ được thì không. Luật này nói **cỡ chữ**, không nói chiều cao: ô
|
|
72
|
+
vẫn `h-11 md:h-10`, xem `budgets.md`.
|
|
73
|
+
|
|
74
|
+
**R9. Bảng ở màn hẹp thì cuộn ngang, đừng bóp cột.** Đây là lỗi hay gặp nhất với bảng: để nguyên `<table>` co lại theo bề rộng màn, kết quả là mỗi ô chỉ còn vài chục pixel, chữ vỡ ba bốn dòng, cột nọ dính cột kia, đọc không ra gì.
|
|
75
|
+
|
|
76
|
+
```html
|
|
77
|
+
<!-- Khung trang py-4 sm:py-6, các khối khác px-4 sm:px-6 (R6); khung cuộn không padding cha để kéo ra -->
|
|
78
|
+
<div class="overflow-x-auto">
|
|
79
|
+
<div class="min-w-[44rem] px-4 sm:px-6">
|
|
80
|
+
<table class="w-full">…</table>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **`min-w`** cho khối bọc bảng, đủ để mọi cột thở. Không có nó thì bảng vẫn co.
|
|
86
|
+
- **`whitespace-nowrap`** cho ô ngày tháng, số, trạng thái, **tên người**. Tên người xuống dòng thì cột co còn một chữ ("Nguyễn / Anh / Tuấn"), cắt đuôi thì mất tên gọi (`layouts/app.md`, bảng nhóm theo trạng thái). Chỉ mô tả, tiêu đề dài mới cho xuống dòng.
|
|
87
|
+
- Lề đặt trên **khối bên trong**, không đặt trên khung cuộn. Xem R6.
|
|
88
|
+
- Cuộn ngang thì **bắt buộc** ghim cột nhận diện (thường là cột đầu) bằng `sticky left-0` kèm nền `--surface`, cột ghim không quá ~40% khung. Cột đầu rộng hơn thế (tên + email) thì đừng cuộn: **dưới `sm`, bảng quản lý thành danh sách dòng** (`layouts/app.md`, mục Bảng dữ liệu). Cuộn mà không ghim thì cuộn một nhịp là mất tên, các ô còn lại không biết của ai.
|
|
89
|
+
|
|
90
|
+
Đây là ngoại lệ hợp lệ của R1, cùng loại với R6: cuộn trong khung chứ không phải cả trang.
|
|
91
|
+
|
|
92
|
+
**Ở desktop, phải cuộn ngang là dấu hiệu thừa cột, không phải lỗi bề ngang.** Thử theo
|
|
93
|
+
thứ tự: gộp cột (email xuống dưới tên trong cùng một ô), bỏ cột phụ, hoặc đẩy nó sang
|
|
94
|
+
drawer chi tiết. Đếm cột trước khi dựng: bảng trong khung app còn ~970px ở 1280px khi
|
|
95
|
+
sidebar mở, quá 6 cột là bắt đầu chật.
|
|
96
|
+
|
|
97
|
+
**R10. Tab hoặc chip quá nhiều ở màn hẹp thì rút chữ, không rút được thì gom vào dropdown.** Thứ tự nên thử:
|
|
98
|
+
|
|
99
|
+
1. **Rút chữ, nếu vẫn còn nghĩa.** "Đến hạn hôm nay" thành "Hôm nay". Mất nghĩa thì đừng rút.
|
|
100
|
+
2. **Cho cả hàng cuộn ngang**, xem R6. Hợp khi các mục ngang vai nhau.
|
|
101
|
+
3. **Gom vào một dropdown**, hiện mục đang chọn kèm mũi tên. Hợp khi có trên sáu mục, hoặc khi hàng cuộn ngang làm người ta không thấy hết lựa chọn.
|
|
102
|
+
|
|
103
|
+
Đừng để `flex-wrap`, đó là cách duy nhất sai trong ba cách trên.
|
|
104
|
+
|
|
105
|
+
**Hàng cuộn ngang thì mép mờ dần ở phía còn mục bị khuất**, cùng cách với vùng nav sidebar (`layouts/app.md`): `mask-image` 32px, mép trái mờ khi đã cuộn khỏi đầu, mép phải mờ khi còn mục phía sau, tính cờ từ `scrollLeft`/`scrollWidth`/`clientWidth`. Thanh cuộn đã ẩn (`scrollbar-clean`) nên mép cắt thẳng qua chữ không báo được gì: ở 375px hàng chip cắt ngang "Bán|", còn tab "Ngừng giao dịch 6" nằm hẳn ngoài khung, trông như chỉ có ba trạng thái. Áp cho hàng tab, hàng chip, và khung bảng cuộn ngang (`R9`).
|
|
106
|
+
|
|
107
|
+
**Ở máy có chuột, hàng cuộn ngang phải có nút mũi tên ở phía còn mục bị khuất.** Dựng sẵn, không hỏi. Chuột thường chỉ có bánh cuộn dọc; thanh cuộn đã ẩn (`scrollbar-clean`) thì người dùng chuột không có cách nào tới mục phía sau, mép mờ chỉ báo "còn", không cho kéo. Máy có trackpad hay chuột cuộn ngang thì kéo được nên người làm không thấy lỗi. Hàng tab, chip, dải card ở desktop đều giống nhau:
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
<div className="relative min-w-0">
|
|
111
|
+
<div ref={scrollerRef} className="scrollbar-clean overflow-x-auto …mask mép mờ…">{items}</div>
|
|
112
|
+
{canScrollLeft ? (
|
|
113
|
+
<div className="pointer-events-none absolute inset-y-0 left-0 hidden items-center any-pointer-fine:flex">
|
|
114
|
+
<IconButton label="Cuộn sang trái" onClick={() => scrollByPage(-1)}
|
|
115
|
+
className="pointer-events-auto size-8 rounded-full border border-border-strong bg-surface">
|
|
116
|
+
<ChevronLeft className="size-4" />
|
|
117
|
+
</IconButton>
|
|
118
|
+
</div>
|
|
119
|
+
) : null}
|
|
120
|
+
{/* canScrollRight: cùng khối, right-0, ChevronRight, scrollByPage(1) */}
|
|
121
|
+
</div>
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- **Cùng hai cờ với mép mờ** (`scrollLeft > 0`, `scrollLeft + clientWidth < scrollWidth - 1`): phía nào mờ thì phía đó có nút, cuộn tới đầu thì nút đó ẩn.
|
|
125
|
+
- **Bấm cuộn khoảng 80% bề rộng khung**: `scroller.scrollBy({ left: direction * scroller.clientWidth * 0.8, behavior: "smooth" })`. Cuộn đúng một mục thì hàng dài phải bấm mãi; cuộn đủ 100% thì mất mục đang nhìn ở mép.
|
|
126
|
+
- **Chỉ hiện ở máy có chuột** (`any-pointer-fine:`; không Tailwind v4 thì `@media (any-pointer: fine)`). Điện thoại vuốt được, thêm nút là che mất chip ở màn hẹp. Laptop màn cảm ứng có cả chuột nên vẫn hiện.
|
|
127
|
+
- **Viền `--border-strong`** như mọi nút có viền (`M14`). **Bo tròn hẳn là ngoại lệ của `F1`**: nút mũi tên tròn nổi trên dải mờ là quy ước chung của hàng cuộn ngang, không đọc lẫn với nút trong hàng.
|
|
128
|
+
- Nút nằm trên dải mờ 32px, căn giữa theo chiều dọc bằng `inset-y-0 flex items-center`, không `-translate-y-1/2` (`N11`). Không đổi bánh cuộn dọc thành cuộn ngang: cướp cuộn trang, người đang cuộn xuống bị kẹt ở hàng chip.
|
|
129
|
+
|
|
130
|
+
**Vạch chỉ vị trí tự vẽ: KHÔNG dựng mặc định, đề xuất một dòng lúc giao.** Mặc định hàng cuộn
|
|
131
|
+
ngang chỉ có mép mờ ở trên. Lúc giao màn có hàng tab/chip cuộn ngang ở màn hẹp thì báo một
|
|
132
|
+
dòng, **nói bằng vấn đề của người dùng cuối, không nhắc tên dự án hay sản phẩm tham khảo nào**:
|
|
133
|
+
_"Trên điện thoại không có thanh cuộn, người dùng không biết hàng [tab lọc] này kéo sang được.
|
|
134
|
+
Mình đang để mép phải mờ dần; có thể đổi sang dropdown, hoặc thêm một thanh cuộn mảnh luôn hiện
|
|
135
|
+
bên dưới hàng."_ Người dùng chọn cách nào mới dựng cách đó (chủ dự án chốt:
|
|
136
|
+
đây là thứ gợi ý cho người dùng, không áp sẵn). Công thức khi dựng (chủ dự án duyệt
|
|
137
|
+
ở một dự án thật): Mép mờ báo "phía này còn", vạch báo "còn bao
|
|
138
|
+
nhiêu và đang ở đâu". Thanh cuộn gốc của iOS/Android là thanh nổi, chỉ hiện **lúc đang
|
|
139
|
+
vuốt**, nên không báo trước được; thanh 4px tự ẩn theo `I18` cũng chỉ hiện khi rê chuột,
|
|
140
|
+
điện thoại không có rê chuột. Tab lấp ló ở mép cũng không chắc có: tab có thể kết thúc
|
|
141
|
+
khít mép khung.
|
|
142
|
+
|
|
143
|
+
```tsx
|
|
144
|
+
<div className="relative min-w-0">
|
|
145
|
+
<div ref={scrollerRef} className="flex overflow-x-auto scrollbar-clean …">{items}</div>
|
|
146
|
+
{isOverflowing ? (
|
|
147
|
+
<div aria-hidden className="pointer-events-none absolute inset-x-0 top-full mt-1 h-[3px] overflow-hidden rounded-full bg-foreground/5">
|
|
148
|
+
{/* Bề rộng và vị trí đổi theo từng pixel cuộn: style, không class. */}
|
|
149
|
+
<div className="absolute inset-y-0 rounded-full bg-foreground/15"
|
|
150
|
+
style={{ width: `${visibleRatio * 100}%`, left: `${offsetRatio * 100}%` }} />
|
|
151
|
+
</div>
|
|
152
|
+
) : null}
|
|
153
|
+
</div>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
- **Chỉ hiện khi hàng thật sự tràn** (`scrollWidth > clientWidth + 1`), ở mọi bề rộng, không riêng màn hẹp. `visibleRatio = clientWidth / scrollWidth`, `offsetRatio = scrollLeft / scrollWidth`, đo lúc cuộn (`passive`) và bằng `ResizeObserver` (xoay máy, font tải xong đổi bề rộng). Dùng chung hook với mép mờ.
|
|
157
|
+
- **Nằm NỔI (`absolute top-full mt-1`) dưới hàng, không nằm trong luồng**: vạch chỉ hiện sau khi đo xong lúc tải, nằm trong luồng thì cả trang nhảy xuống một nhịp. Nơi gọi chừa khoảng trống dưới hàng từ 8px trở lên (vạch chiếm 7px).
|
|
158
|
+
- **Hàng tab gạch chân có đường kẻ nền thì vạch chạy ĐÈ lên đường kẻ đó** (`bottom-0 h-0.5`, rãnh chính là đường kẻ nền), không vẽ rãnh riêng bên dưới: hai đường sát nhau đọc như gạch chân bị lệch. Hàng tab gạch chân không có đường kẻ nền thì dùng rãnh riêng như trên.
|
|
159
|
+
- **Màu nhạt, không nhạt hơn nữa**: thanh là gợi ý "kéo được", đậm hơn là kéo mắt khỏi mục đang chọn; nhạt hơn thì ngoài nắng mất hẳn (khoảng `/20` của màu xám chữ phụ, tương đương `bg-foreground/15` ở đây).
|
|
160
|
+
- **Mục đang chọn bị khuất thì tự cuộn nó vào giữa hàng** lúc tải và khi đổi mục. Tự tính `scrollLeft` rồi `scroller.scrollTo`, **không `scrollIntoView`**: hàng có thể đang nằm dưới màn lúc tải, `scrollIntoView` kéo cả trang xuống theo. Lần đầu `behavior: "auto"`, các lần sau `"smooth"`.
|
|
161
|
+
- Áp cho hàng chip, hàng tab còn cuộn ngang, dải card/carousel. **Tab trạng thái của bảng ở màn hẹp mà có tab nằm hẳn ngoài khung thì vẫn thành dropdown** (`layouts/app.md`): vạch báo được "còn", không báo được còn trạng thái nào.
|
|
162
|
+
|
|
163
|
+
**R11. Màn hẹp được ẩn cột phụ, không được mất hành động chính của dòng.** Ẩn badge, ẩn nút trên
|
|
164
|
+
dòng (`hidden sm:inline-flex`) và ẩn panel chi tiết (`hidden lg:block`) cùng lúc thì ở 375px bấm
|
|
165
|
+
dòng không ra gì, việc chính của màn không làm được trên điện thoại. Mỗi thứ ẩn đi phải có lối
|
|
166
|
+
khác: bấm dòng mở panel thành trang hay sheet từ dưới, nút chính vào menu ⋯ của dòng, hay nút
|
|
167
|
+
thu còn icon.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Bảng hạ bậc ở mobile, đừng bê nguyên nhịp desktop xuống
|
|
172
|
+
|
|
173
|
+
| | Mobile (dưới `sm`) | Từ `sm` trở lên |
|
|
174
|
+
| --- | --- | --- |
|
|
175
|
+
| Padding trang | `p-4` | `sm:p-6` |
|
|
176
|
+
| Padding card | `p-4`, tối đa `p-5` | xem `budgets.md` |
|
|
177
|
+
| Chiều cao nút | `h-10` | xem `budgets.md` |
|
|
178
|
+
| Tiêu đề trong card | `text-base` | `text-base` (không đổi theo breakpoint, `D8`) |
|
|
179
|
+
| Mô tả trong card | `text-sm` | `text-sm`: tiêu đề card `text-base` nên chữ bên trong tối đa `text-sm` (`T8`) |
|
|
180
|
+
| Ngày tháng, nhãn phụ | `text-xs`, luôn nhỏ hơn tiêu đề một bậc | như trên |
|
|
181
|
+
| Ô nhập, textarea, select | **`text-base`, không hạ.** Dưới 16px thì iOS tự zoom | `md:text-sm` |
|
|
182
|
+
|
|
183
|
+
`p-8` là nhịp của màn rộng. Bê xuống 375px thì riêng lề đã ăn
|
|
184
|
+
mất một phần năm bề ngang.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Thứ tự kiểm ở 375px
|
|
189
|
+
|
|
190
|
+
1. Trang có cuộn ngang không. Có là hỏng, trừ ngoại lệ R6.
|
|
191
|
+
2. Vùng nào cuộn ngang thì cuộn hết sang phải, phần tử cuối còn lề không.
|
|
192
|
+
3. Có cụm nút nào rớt hàng không, có phần tử nào đứng lẻ ở hàng dưới không.
|
|
193
|
+
4. Card còn `p-8` không.
|
|
194
|
+
5. Ngày tháng có nhỏ hơn tiêu đề không.
|
|
195
|
+
6. Nhìn tổng thể: có chỗ nào chật dồn cục không. Chật là chưa xong.
|