@starci/skills 1.1.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/INDEX.md +74 -0
- package/INDEX.vi.md +75 -0
- package/README.md +44 -0
- package/README.vi.md +43 -0
- package/SKILL.md +135 -0
- package/SKILL.vi.md +128 -0
- package/alias/INDEX.md +104 -0
- package/alias/INDEX.vi.md +104 -0
- package/alias/alias.json +297 -0
- package/bin/starci-skills.mjs +219 -0
- package/knowledge/INDEX.md +22 -0
- package/knowledge/INDEX.vi.md +22 -0
- package/knowledge/grammars/starci/DNA.md +146 -0
- package/knowledge/grammars/starci/DNA.vi.md +146 -0
- package/knowledge/grammars/starci/INDEX.md +25 -0
- package/knowledge/grammars/starci/INDEX.vi.md +25 -0
- package/knowledge/grammars/starci/family.md +50 -0
- package/knowledge/grammars/starci/family.vi.md +50 -0
- package/knowledge/grammars/starci/idioms.md +120 -0
- package/knowledge/grammars/starci/idioms.vi.md +121 -0
- package/knowledge/grammars/starci/playbook.md +36 -0
- package/knowledge/grammars/starci/playbook.vi.md +35 -0
- package/knowledge/patterns/be/INDEX.md +29 -0
- package/knowledge/patterns/be/INDEX.vi.md +29 -0
- package/knowledge/patterns/be/comment.md +80 -0
- package/knowledge/patterns/be/comment.vi.md +80 -0
- package/knowledge/patterns/be/error.md +86 -0
- package/knowledge/patterns/be/error.vi.md +86 -0
- package/knowledge/patterns/be/folder.md +87 -0
- package/knowledge/patterns/be/folder.vi.md +86 -0
- package/knowledge/patterns/be/function.md +80 -0
- package/knowledge/patterns/be/function.vi.md +80 -0
- package/knowledge/patterns/be/imports.md +79 -0
- package/knowledge/patterns/be/imports.vi.md +79 -0
- package/knowledge/patterns/be/naming.md +87 -0
- package/knowledge/patterns/be/naming.vi.md +87 -0
- package/knowledge/patterns/be/test.md +79 -0
- package/knowledge/patterns/be/test.vi.md +79 -0
- package/knowledge/patterns/be/typing.md +73 -0
- package/knowledge/patterns/be/typing.vi.md +73 -0
- package/knowledge/patterns/fe/INDEX.md +29 -0
- package/knowledge/patterns/fe/INDEX.vi.md +29 -0
- package/knowledge/patterns/fe/comment.md +70 -0
- package/knowledge/patterns/fe/comment.vi.md +70 -0
- package/knowledge/patterns/fe/error.md +61 -0
- package/knowledge/patterns/fe/error.vi.md +61 -0
- package/knowledge/patterns/fe/folder.md +98 -0
- package/knowledge/patterns/fe/folder.vi.md +95 -0
- package/knowledge/patterns/fe/function.md +70 -0
- package/knowledge/patterns/fe/function.vi.md +70 -0
- package/knowledge/patterns/fe/imports.md +87 -0
- package/knowledge/patterns/fe/imports.vi.md +87 -0
- package/knowledge/patterns/fe/naming.md +77 -0
- package/knowledge/patterns/fe/naming.vi.md +77 -0
- package/knowledge/patterns/fe/test.md +73 -0
- package/knowledge/patterns/fe/test.vi.md +73 -0
- package/knowledge/patterns/fe/typing.md +67 -0
- package/knowledge/patterns/fe/typing.vi.md +67 -0
- package/knowledge/ui/INDEX.md +101 -0
- package/knowledge/ui/INDEX.vi.md +101 -0
- package/knowledge/ui/composition/INDEX.md +60 -0
- package/knowledge/ui/composition/INDEX.vi.md +63 -0
- package/knowledge/ui/composition/accent.md +73 -0
- package/knowledge/ui/composition/accent.vi.md +74 -0
- package/knowledge/ui/composition/action.md +55 -0
- package/knowledge/ui/composition/action.vi.md +56 -0
- package/knowledge/ui/composition/coverage.md +37 -0
- package/knowledge/ui/composition/coverage.vi.md +37 -0
- package/knowledge/ui/composition/cta.md +79 -0
- package/knowledge/ui/composition/cta.vi.md +79 -0
- package/knowledge/ui/composition/feedback.md +55 -0
- package/knowledge/ui/composition/feedback.vi.md +56 -0
- package/knowledge/ui/composition/hierarchy.md +83 -0
- package/knowledge/ui/composition/hierarchy.vi.md +82 -0
- package/knowledge/ui/composition/layout.md +91 -0
- package/knowledge/ui/composition/layout.vi.md +93 -0
- package/knowledge/ui/composition/responsive.md +67 -0
- package/knowledge/ui/composition/responsive.vi.md +67 -0
- package/knowledge/ui/composition/state.md +105 -0
- package/knowledge/ui/composition/state.vi.md +106 -0
- package/knowledge/ui/presentation/INDEX.md +94 -0
- package/knowledge/ui/presentation/INDEX.vi.md +69 -0
- package/knowledge/ui/presentation/boundary.md +170 -0
- package/knowledge/ui/presentation/boundary.vi.md +169 -0
- package/knowledge/ui/presentation/font.md +155 -0
- package/knowledge/ui/presentation/font.vi.md +156 -0
- package/knowledge/ui/presentation/gap.md +181 -0
- package/knowledge/ui/presentation/gap.vi.md +181 -0
- package/knowledge/ui/presentation/margin.md +168 -0
- package/knowledge/ui/presentation/margin.vi.md +171 -0
- package/knowledge/ui/presentation/measure.md +174 -0
- package/knowledge/ui/presentation/measure.vi.md +178 -0
- package/knowledge/ui/presentation/overflow.md +142 -0
- package/knowledge/ui/presentation/overflow.vi.md +142 -0
- package/knowledge/ui/presentation/padding.md +293 -0
- package/knowledge/ui/presentation/padding.vi.md +292 -0
- package/knowledge/ui/presentation/surface.md +166 -0
- package/knowledge/ui/presentation/surface.vi.md +166 -0
- package/knowledge/ui/presentation/text-flow.md +123 -0
- package/knowledge/ui/presentation/text-flow.vi.md +123 -0
- package/knowledge/ui/presentation/tone.md +114 -0
- package/knowledge/ui/presentation/tone.vi.md +113 -0
- package/knowledge/ui/proof/INDEX.md +55 -0
- package/knowledge/ui/proof/INDEX.vi.md +55 -0
- package/knowledge/ui/proof/accessibility.md +67 -0
- package/knowledge/ui/proof/accessibility.vi.md +68 -0
- package/knowledge/ui/proof/contrast.md +58 -0
- package/knowledge/ui/proof/contrast.vi.md +57 -0
- package/knowledge/ui/proof/focus.md +76 -0
- package/knowledge/ui/proof/focus.vi.md +76 -0
- package/knowledge/ui/proof/motion.md +63 -0
- package/knowledge/ui/proof/motion.vi.md +64 -0
- package/knowledge/ui/proof/render-truth.md +63 -0
- package/knowledge/ui/proof/render-truth.vi.md +63 -0
- package/operators/INDEX.md +199 -0
- package/operators/INDEX.vi.md +199 -0
- package/operators/architecture-decide/errors.json +115 -0
- package/operators/architecture-decide/operator.json +20 -0
- package/operators/architecture-decide/operator.md +133 -0
- package/operators/architecture-decide/operator.vi.md +130 -0
- package/operators/architecture-decide/self-test.mjs +221 -0
- package/operators/architecture-decide/validate.mjs +112 -0
- package/operators/backend-source-apply/errors.json +66 -0
- package/operators/backend-source-apply/operator.json +20 -0
- package/operators/backend-source-apply/operator.md +189 -0
- package/operators/backend-source-apply/operator.vi.md +185 -0
- package/operators/backend-source-apply/self-test.mjs +257 -0
- package/operators/backend-source-apply/validate.mjs +240 -0
- package/operators/business-decide/errors.json +78 -0
- package/operators/business-decide/operator.json +20 -0
- package/operators/business-decide/operator.md +176 -0
- package/operators/business-decide/operator.vi.md +174 -0
- package/operators/business-decide/self-test.mjs +225 -0
- package/operators/business-decide/validate.mjs +277 -0
- package/operators/content-generate/errors.json +106 -0
- package/operators/content-generate/operator.json +21 -0
- package/operators/content-generate/operator.md +155 -0
- package/operators/content-generate/operator.vi.md +155 -0
- package/operators/content-generate/self-test.mjs +288 -0
- package/operators/content-generate/validate.mjs +196 -0
- package/operators/errors.json +178 -0
- package/operators/frontend-direction-decide/errors.json +135 -0
- package/operators/frontend-direction-decide/operator.json +21 -0
- package/operators/frontend-direction-decide/operator.md +167 -0
- package/operators/frontend-direction-decide/operator.vi.md +167 -0
- package/operators/frontend-direction-decide/self-test.mjs +226 -0
- package/operators/frontend-direction-decide/validate.mjs +146 -0
- package/operators/frontend-presentation-resolve/errors.json +42 -0
- package/operators/frontend-presentation-resolve/operator.json +19 -0
- package/operators/frontend-presentation-resolve/operator.md +142 -0
- package/operators/frontend-presentation-resolve/operator.vi.md +140 -0
- package/operators/frontend-presentation-resolve/self-test.mjs +163 -0
- package/operators/frontend-presentation-resolve/validate.mjs +143 -0
- package/operators/frontend-source-apply/errors.json +30 -0
- package/operators/frontend-source-apply/operator.json +20 -0
- package/operators/frontend-source-apply/operator.md +127 -0
- package/operators/frontend-source-apply/operator.vi.md +126 -0
- package/operators/frontend-source-apply/self-test.mjs +214 -0
- package/operators/frontend-source-apply/validate.mjs +133 -0
- package/operators/frontend-surface-audit/errors.json +5 -0
- package/operators/frontend-surface-audit/operator.json +22 -0
- package/operators/frontend-surface-audit/operator.md +121 -0
- package/operators/frontend-surface-audit/operator.vi.md +121 -0
- package/operators/frontend-surface-audit/self-test.mjs +185 -0
- package/operators/frontend-surface-audit/validate.mjs +127 -0
- package/operators/git-publish/errors.json +54 -0
- package/operators/git-publish/operator.json +20 -0
- package/operators/git-publish/operator.md +180 -0
- package/operators/git-publish/operator.vi.md +178 -0
- package/operators/git-publish/self-test.mjs +177 -0
- package/operators/git-publish/validate.mjs +106 -0
- package/operators/platform-operate/errors.json +90 -0
- package/operators/platform-operate/operator.json +22 -0
- package/operators/platform-operate/operator.md +162 -0
- package/operators/platform-operate/operator.vi.md +160 -0
- package/operators/platform-operate/self-test.mjs +202 -0
- package/operators/platform-operate/validate.mjs +193 -0
- package/operators/quality-verify/errors.json +54 -0
- package/operators/quality-verify/operator.json +20 -0
- package/operators/quality-verify/operator.md +185 -0
- package/operators/quality-verify/operator.vi.md +177 -0
- package/operators/quality-verify/self-test.mjs +210 -0
- package/operators/quality-verify/validate.mjs +197 -0
- package/operators/release-deploy/errors.json +158 -0
- package/operators/release-deploy/operator.json +23 -0
- package/operators/release-deploy/operator.md +183 -0
- package/operators/release-deploy/operator.vi.md +181 -0
- package/operators/release-deploy/self-test.mjs +235 -0
- package/operators/release-deploy/validate.mjs +129 -0
- package/operators/uat-verify/errors.json +78 -0
- package/operators/uat-verify/operator.json +25 -0
- package/operators/uat-verify/operator.md +162 -0
- package/operators/uat-verify/operator.vi.md +161 -0
- package/operators/uat-verify/self-test.mjs +270 -0
- package/operators/uat-verify/validate.mjs +202 -0
- package/operators/workspace-bind/errors.json +90 -0
- package/operators/workspace-bind/operator.json +21 -0
- package/operators/workspace-bind/operator.md +148 -0
- package/operators/workspace-bind/operator.vi.md +148 -0
- package/operators/workspace-bind/self-test.mjs +205 -0
- package/operators/workspace-bind/validate.mjs +152 -0
- package/package.json +50 -0
- package/readiness/initialization/workspaces/commit-policy.json +91 -0
- package/readiness/initialization/workspaces/config.schema.json +35 -0
- package/readiness/initialization/workspaces/device-state.schema.json +54 -0
- package/readiness/initialization/workspaces/local-route.schema.json +206 -0
- package/readiness/initialization/workspaces/portable-route.schema.json +200 -0
- package/resources/INDEX.md +96 -0
- package/resources/INDEX.vi.md +99 -0
- package/resources/agents/profiles/claude.json +131 -0
- package/resources/agents/profiles/openai.json +131 -0
- package/resources/orchestrator.json +71 -0
- package/resources/tools.json +85 -0
- package/routing.json +272 -0
- package/scripts/alias-registry.mjs +31 -0
- package/scripts/device-state.mjs +497 -0
- package/scripts/device-state.spec.mjs +18 -0
- package/scripts/errors-registry.mjs +68 -0
- package/scripts/generate-alias-doc.mjs +66 -0
- package/scripts/generate-grammar-dna.mjs +387 -0
- package/scripts/generate-operators-index.mjs +95 -0
- package/scripts/generate-presentation-owned.mjs +681 -0
- package/scripts/install-cli.spec.mjs +74 -0
- package/scripts/json-schema.mjs +94 -0
- package/scripts/operator-md.mjs +96 -0
- package/scripts/run-operator-self-tests.mjs +36 -0
- package/scripts/validate-alias.mjs +165 -0
- package/scripts/validate-defaults.mjs +72 -0
- package/scripts/validate-knowledge-citations.mjs +90 -0
- package/scripts/validate-operator.mjs +125 -0
- package/scripts/validate-request.mjs +80 -0
- package/scripts/validate-resources.mjs +117 -0
- package/scripts/validate-response.mjs +141 -0
- package/scripts/validate-routing.mjs +91 -0
- package/scripts/validate-step.mjs +50 -0
- package/scripts/validate-templates.mjs +226 -0
- package/scripts/validate-templates.spec.mjs +144 -0
- package/scripts/validate-workflows.mjs +106 -0
- package/scripts/workspace-portable.mjs +389 -0
- package/scripts/workspace-portable.spec.mjs +246 -0
- package/templates/README.md +47 -0
- package/templates/README.vi.md +45 -0
- package/templates/changes.example.md +27 -0
- package/templates/grammars.template.md +34 -0
- package/templates/kinds/architecture-decision.contract.json +14 -0
- package/templates/kinds/architecture-decision.skeleton.md +52 -0
- package/templates/kinds/backend-source-application.contract.json +10 -0
- package/templates/kinds/backend-source-application.skeleton.md +34 -0
- package/templates/kinds/business-promise-authority.contract.json +13 -0
- package/templates/kinds/business-promise-authority.skeleton.md +67 -0
- package/templates/kinds/capture.schema.json +33 -0
- package/templates/kinds/changes.contract.json +10 -0
- package/templates/kinds/changes.skeleton.md +26 -0
- package/templates/kinds/checks.schema.json +44 -0
- package/templates/kinds/claims.schema.json +165 -0
- package/templates/kinds/conformance.schema.json +15 -0
- package/templates/kinds/content-brief.contract.json +12 -0
- package/templates/kinds/content-brief.skeleton.md +45 -0
- package/templates/kinds/content-generation-receipt.contract.json +11 -0
- package/templates/kinds/content-generation-receipt.skeleton.md +40 -0
- package/templates/kinds/content-review.contract.json +11 -0
- package/templates/kinds/content-review.skeleton.md +45 -0
- package/templates/kinds/contract.schema.json +28 -0
- package/templates/kinds/coverage-matrix.schema.json +86 -0
- package/templates/kinds/coverage.schema.json +62 -0
- package/templates/kinds/current-state.schema.json +40 -0
- package/templates/kinds/delta.schema.json +95 -0
- package/templates/kinds/e2e.schema.json +63 -0
- package/templates/kinds/frontend-direction-decision.contract.json +93 -0
- package/templates/kinds/frontend-direction-decision.skeleton.md +68 -0
- package/templates/kinds/frontend-presentation-resolution.contract.json +11 -0
- package/templates/kinds/frontend-presentation-resolution.skeleton.md +32 -0
- package/templates/kinds/frontend-source-application.contract.json +10 -0
- package/templates/kinds/frontend-source-application.skeleton.md +30 -0
- package/templates/kinds/frontend-surface-audit.contract.json +52 -0
- package/templates/kinds/frontend-surface-audit.skeleton.md +32 -0
- package/templates/kinds/gate-result.schema.json +64 -0
- package/templates/kinds/git-publication.contract.json +82 -0
- package/templates/kinds/git-publication.skeleton.md +61 -0
- package/templates/kinds/independent-critique.contract.json +9 -0
- package/templates/kinds/independent-critique.skeleton.md +28 -0
- package/templates/kinds/inventory.schema.json +35 -0
- package/templates/kinds/model.schema.json +99 -0
- package/templates/kinds/mutations.schema.json +65 -0
- package/templates/kinds/platform-operation-receipt.contract.json +76 -0
- package/templates/kinds/platform-operation-receipt.skeleton.md +54 -0
- package/templates/kinds/probes.schema.json +130 -0
- package/templates/kinds/proof.schema.json +17 -0
- package/templates/kinds/quality-verification.contract.json +87 -0
- package/templates/kinds/quality-verification.skeleton.md +58 -0
- package/templates/kinds/release-deployment.contract.json +85 -0
- package/templates/kinds/release-deployment.skeleton.md +67 -0
- package/templates/kinds/route.schema.json +293 -0
- package/templates/kinds/stack-model.schema.json +90 -0
- package/templates/kinds/uat-capture.schema.json +35 -0
- package/templates/kinds/uat-flow-verification.contract.json +11 -0
- package/templates/kinds/uat-flow-verification.skeleton.md +47 -0
- package/templates/kinds/uat-snapshot.schema.json +96 -0
- package/templates/kinds/uat-verdicts.schema.json +41 -0
- package/templates/kinds/ui-coverage.schema.json +77 -0
- package/templates/kinds/verdicts.schema.json +39 -0
- package/templates/kinds/workspace-route-binding.contract.json +12 -0
- package/templates/kinds/workspace-route-binding.skeleton.md +60 -0
- package/templates/kinds/writes.schema.json +37 -0
- package/templates/operator.template.md +73 -0
- package/templates/patterns.template.md +31 -0
- package/templates/step/request.schema.json +42 -0
- package/templates/step/response.schema.json +144 -0
- package/templates/ui-composition.template.md +36 -0
- package/templates/ui-presentation.template.md +57 -0
- package/templates/ui-proof.template.md +34 -0
- package/workflows/README.md +37 -0
- package/workflows/README.vi.md +37 -0
- package/workflows/backend-feature.json +59 -0
- package/workflows/content-unit.json +19 -0
- package/workflows/frontend-new-surface.json +81 -0
- package/workflows/frontend-reconstruct.json +67 -0
- package/workflows/frontend-refine.json +67 -0
- package/workflows/frontend-with-uat.json +78 -0
- package/workflows/full-feature.json +104 -0
- package/workflows/release.json +29 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Padding presentation
|
|
2
|
+
|
|
3
|
+
File này trả lời đúng một câu hỏi: một ranh giới do ứng dụng sở hữu thì cách nội dung của nó bao xa.
|
|
4
|
+
|
|
5
|
+
Bước composition đã chọn xong cây DOM và các Grammar object. Padding presentation chỉ giải quyết
|
|
6
|
+
phần inset của page, section và container do ứng dụng sở hữu. Inset bên trong một card, input,
|
|
7
|
+
button hay bất kỳ Grammar object nào khác đều thuộc về Grammar.
|
|
8
|
+
|
|
9
|
+
## Thang giá trị
|
|
10
|
+
|
|
11
|
+
`COMMON_SPACING_SCALE` là thang đóng. Số của rule là thứ tự trên thang đó. Nó không phải số bậc
|
|
12
|
+
Tailwind, và hai số này lệch nhau từ PADDING-5 trở đi.
|
|
13
|
+
|
|
14
|
+
| Rule | Class | Giá trị | Token Common |
|
|
15
|
+
| --- | --- | --- | --- |
|
|
16
|
+
| PADDING-0 | `p-0` | `0` | không có |
|
|
17
|
+
| PADDING-1 | `p-1` | `.25rem` | không có |
|
|
18
|
+
| PADDING-2 | `p-2` | `.5rem` | không có |
|
|
19
|
+
| PADDING-3 | `p-3` | `.75rem` | không có |
|
|
20
|
+
| PADDING-4 | `p-4` | `1rem` | không có |
|
|
21
|
+
| PADDING-5 | `p-6` | `1.5rem` | không có |
|
|
22
|
+
| PADDING-6 | `p-8` | `2rem` | không có |
|
|
23
|
+
|
|
24
|
+
Common không publish token padding nào. Các inset bên dưới chỉ đến được qua chính component sở hữu
|
|
25
|
+
chúng, chứ không qua một biến mà ứng dụng được phép đặt.
|
|
26
|
+
|
|
27
|
+
Page inset là ngoại lệ duy nhất và không nằm trên thang này: `--grammar-page-inset` cho ra
|
|
28
|
+
`clamp(1rem, 3vw, 2rem)`, một giá trị responsive do `PageContainer` sở hữu. Ứng dụng không bao giờ
|
|
29
|
+
dựng lại nó bằng một class cố định.
|
|
30
|
+
|
|
31
|
+
Các giá trị rem chỉ quy ra `0 / 4 / 8 / 12 / 16 / 24 / 32` pixel CSS khi root font size tính được là
|
|
32
|
+
`16px`. Khi kiểm tra lúc chạy thì dùng `expectedPx = remFactor * observedRootFontPx`.
|
|
33
|
+
|
|
34
|
+
## Owner
|
|
35
|
+
|
|
36
|
+
Mỗi case gọi tên ai sở hữu inset. Owner quyết định ứng dụng có được viết class hay không.
|
|
37
|
+
|
|
38
|
+
| Owner | Nghĩa | Ứng dụng viết |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `App` | Ranh giới thuộc về ứng dụng | Viết class |
|
|
41
|
+
| Tên component | Common đã áp inset bên trong component đó | Không viết gì, chỉ ghép component |
|
|
42
|
+
| `—` | Common chưa có đường dùng công khai cho ranh giới này | Viết class, ghi nhận là workaround |
|
|
43
|
+
|
|
44
|
+
Viết class ở chỗ owner là một component chính là `APP_REIMPLEMENTATION`. Thò vào ruột một Grammar
|
|
45
|
+
component bằng selector hoặc bằng class truyền vào để đổi inset của nó là `APP_OVERRIDE`.
|
|
46
|
+
|
|
47
|
+
## Padding mà Common đã sở hữu
|
|
48
|
+
|
|
49
|
+
Sinh từ claim của `@grammar/core` bằng `scripts/generate-presentation-owned.mjs`; muốn đổi thì sửa component, đừng sửa bảng này.
|
|
50
|
+
|
|
51
|
+
| Component | Phần tử hoặc điều kiện | Rule |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| `ChatWorkspace` | drawer body, hasRail, isCompactRail | PADDING-0 |
|
|
54
|
+
| `ChatWorkspace` | overlay rail, hasRail, isCompactRail | PADDING-4 |
|
|
55
|
+
| `ChatWorkspace` | overlay rail, hasRail, not isCompactRail | PADDING-4 |
|
|
56
|
+
| `ChatWorkspace` | rail trigger boundary, hasRail, isCompactRail | PADDING-2 |
|
|
57
|
+
| `ChatWorkspace` | rail trigger boundary, hasRail, isCompactRail | PADDING-4 |
|
|
58
|
+
| `ChatWorkspace` | rail trigger, hasRail, isCompactRail | PADDING-3 |
|
|
59
|
+
| `EmptyNotice` | root | PADDING-4 |
|
|
60
|
+
| `HorizontalScrollRegion` | root | PADDING-1 |
|
|
61
|
+
| `NavigationFeatureNav` | primary | PADDING-3 |
|
|
62
|
+
| `OtpInput` | root | PADDING-1 |
|
|
63
|
+
| `Rail` | body, inset="content" | PADDING-3 |
|
|
64
|
+
| `Rail` | body, inset="content" | PADDING-5 |
|
|
65
|
+
| `Sidebar` | footer, not (collapsed || footer == null) | PADDING-0 |
|
|
66
|
+
| `Sidebar` | footer, not (collapsed || footer == null) | PADDING-3 |
|
|
67
|
+
| `Sidebar` | group label, group.label!=undefined, not collapsed | PADDING-1 |
|
|
68
|
+
| `Sidebar` | group label, group.label!=undefined, not collapsed | PADDING-2 |
|
|
69
|
+
| `Sidebar` | group label, group.label!=undefined, not collapsed | PADDING-3 |
|
|
70
|
+
| `Sidebar` | header, not (collapsed || header == null) | PADDING-3 |
|
|
71
|
+
| `Sidebar` | icon button wrapper, canToggle | PADDING-2 |
|
|
72
|
+
| `Sidebar` | list box item, not collapsed | PADDING-2 |
|
|
73
|
+
| `Sidebar` | list box item, not collapsed | PADDING-3 |
|
|
74
|
+
| `Sidebar` | list box, collapsed | PADDING-2 |
|
|
75
|
+
| `Sidebar` | list box, not collapsed | PADDING-3 |
|
|
76
|
+
| `StaticStateRow` | root | PADDING-4 |
|
|
77
|
+
| `Subnav` | root | PADDING-3 |
|
|
78
|
+
| `SurfaceAccordionCard` | accordion body | PADDING-8 |
|
|
79
|
+
| `SurfaceAccordionCard` | accordion body wrapper | PADDING-0 |
|
|
80
|
+
| `SurfaceAccordionCard` | accordion trigger | PADDING-4 |
|
|
81
|
+
| `SurfaceCard` | card content, composition!="joined" | PADDING-4 |
|
|
82
|
+
| `SurfaceCard` | card content, composition="joined" | PADDING-0 |
|
|
83
|
+
| `SurfaceListCard` | root | PADDING-0 |
|
|
84
|
+
| `Tabs` | hero tabs tab | PADDING-3 |
|
|
85
|
+
| `Tabs` | root, inset="page" | PADDING-5 |
|
|
86
|
+
| `TextAction` | root, appearance="choice" | PADDING-1 |
|
|
87
|
+
| `TextAction` | root, appearance="choice" | PADDING-2 |
|
|
88
|
+
| `TextAction` | root, appearance="route" | PADDING-2 |
|
|
89
|
+
| `TextAction` | root, appearance="route" | PADDING-3 |
|
|
90
|
+
| `TextAction` | root, appearance="section" | PADDING-2 |
|
|
91
|
+
| `TextAction` | root, appearance="section" | PADDING-3 |
|
|
92
|
+
| `TextAction` | root, appearance="tab" | PADDING-3 |
|
|
93
|
+
| `Tooltip` | content | PADDING-1 |
|
|
94
|
+
| `Tooltip` | content | PADDING-2 |
|
|
95
|
+
|
|
96
|
+
## Tiếp xúc theo cạnh
|
|
97
|
+
|
|
98
|
+
Một ranh giới không phải lúc nào cũng lấy một giá trị cho cả bốn cạnh. Khi một surface để phẳng và
|
|
99
|
+
các dải con tự sở hữu inset của mình, mỗi cạnh lấy giá trị theo thứ mà nó chạm vào.
|
|
100
|
+
|
|
101
|
+
| Cạnh chạm vào | Giá trị | Rule |
|
|
102
|
+
| --- | --- | --- |
|
|
103
|
+
| Mép ngoài của surface | `1rem` | PADDING-4 |
|
|
104
|
+
| Separator giữa hai dải | `.75rem` | PADDING-3 |
|
|
105
|
+
| Không chạm gì, vì con đã sở hữu mép | `0` | PADDING-0 |
|
|
106
|
+
|
|
107
|
+
Separator đã tự vẽ ra đường ranh, nên cạnh chạm vào nó cần ít khoảng hở hơn cạnh chạm mép trần. Hai
|
|
108
|
+
cạnh ngang của một dải luôn chạm mép ngoài, nên luôn giữ `1rem`.
|
|
109
|
+
|
|
110
|
+
Với một chồng dải thì quy ra `px-4` xuyên suốt, `pt-4` ở dải đầu, `pb-4` ở dải cuối, và `.75rem` cho
|
|
111
|
+
mọi cạnh chạm separator. Một dải nằm giữa hai separator là `px-4 py-3`.
|
|
112
|
+
|
|
113
|
+
Common sở hữu bản thân cái surface phẳng qua `composition="joined"`, thứ đặt inset nội dung về `0`.
|
|
114
|
+
Nó chưa có đường nào cho inset của các dải, nên phần đó vẫn là workaround của ứng dụng.
|
|
115
|
+
|
|
116
|
+
## PADDING-0 — `p-0` / `0`
|
|
117
|
+
|
|
118
|
+
Ranh giới không đóng góp khoảng cách nào, vì nội dung hoặc phần tử con đã tự mang đường bao của nó.
|
|
119
|
+
|
|
120
|
+
| Case | Dùng khi | Owner | Render |
|
|
121
|
+
| --- | --- | --- | --- |
|
|
122
|
+
| Case 1 | Container chỉ gom nhóm, mà mỗi con đã tự mang ranh giới riêng | `App` | `<section className="p-0">` |
|
|
123
|
+
| Case 2 | Các dải trong surface phải chạy sát mép card | `SurfaceCard` | `composition="joined"` đã đặt sẵn `0` |
|
|
124
|
+
| Case 3 | Danh sách mà mỗi hàng tự sở hữu inset của nó | `SurfaceListCard` | Ghép card, không viết padding |
|
|
125
|
+
|
|
126
|
+
Không phải rule này: ranh giới duy nhất bảo vệ nội dung đọc được thì phải giữ inset. Dùng PADDING-4.
|
|
127
|
+
|
|
128
|
+
## PADDING-1 — `p-1` / `.25rem`
|
|
129
|
+
|
|
130
|
+
Inset đều nhỏ nhất, dành cho một container cố ý làm nhỏ mà nội dung gần như lấp đầy.
|
|
131
|
+
|
|
132
|
+
| Case | Dùng khi | Owner | Render |
|
|
133
|
+
| --- | --- | --- | --- |
|
|
134
|
+
| Case 1 | Một nhãn hoặc chú giải nhỏ nổi trên canvas do app sở hữu | `App` | `<aside className="p-1">` với `<Text size="xs">` |
|
|
135
|
+
| Case 2 | Vùng cuộn cần chừa chỗ để focus ring không bị cắt | `HorizontalScrollRegion` | Ghép region, không viết padding |
|
|
136
|
+
| Case 3 | Một gợi ý ngắn gắn vào một control | `Tooltip` | Ghép tooltip, không viết padding |
|
|
137
|
+
|
|
138
|
+
## PADDING-2 — `p-2` / `.5rem`
|
|
139
|
+
|
|
140
|
+
Container nhỏ gọn do app sở hữu, cần khoảng mép rõ ràng nhưng tiết kiệm.
|
|
141
|
+
|
|
142
|
+
| Case | Dùng khi | Owner | Render |
|
|
143
|
+
| --- | --- | --- | --- |
|
|
144
|
+
| Case 1 | Một dải phụ nhỏ mà cả bốn cạnh dùng chung một inset | `App` | `<aside className="p-2">` với `<Text size="sm">` |
|
|
145
|
+
| Case 2 | Một thanh control gọn nằm trên nội dung do app sở hữu | `App` | `<div className="flex items-center gap-2 p-2">` |
|
|
146
|
+
|
|
147
|
+
Không phải rule này: khoảng cách giữa các phần tử con bên trong container thuộc về gap của cha, không
|
|
148
|
+
thuộc padding. Dùng rule GAP tương ứng.
|
|
149
|
+
|
|
150
|
+
## PADDING-3 — `p-3` / `.75rem`
|
|
151
|
+
|
|
152
|
+
Inset dày dặn nhưng vẫn đọc được, cho một section hoặc container lồng do app sở hữu.
|
|
153
|
+
|
|
154
|
+
| Case | Dùng khi | Owner | Render |
|
|
155
|
+
| --- | --- | --- | --- |
|
|
156
|
+
| Case 1 | Một container lồng bên trong, sở hữu ranh giới khác với ranh giới của cha | `App` | `<aside className="p-3">` nằm trong section `p-4` |
|
|
157
|
+
| Case 2 | Inset ngang của một thanh tab hoặc navigation phụ | `Tabs`, `Subnav` | Ghép component, không viết padding |
|
|
158
|
+
| Case 3 | Cạnh của một dải chạm separator bên trong surface phẳng | `—` | `<div className="px-4 py-3">` nằm giữa hai separator |
|
|
159
|
+
| Case 4 | Cạnh hướng về separator của một ô metric hoặc một hàng danh sách | `—` | `<div className="p-4 pb-3">`, hoặc `pt-3` khi separator nằm trên |
|
|
160
|
+
|
|
161
|
+
Không phải rule này: lồng cùng một inset cho cùng một ranh giới và cùng mục đích là nhân đôi nó.
|
|
162
|
+
Padding của cha và của con cộng dồn trên đường từ mép ngoài tới nội dung cuối.
|
|
163
|
+
|
|
164
|
+
## PADDING-4 — `p-4` / `1rem`
|
|
165
|
+
|
|
166
|
+
Inset đều chuẩn cho nội dung đọc được bên trong một ranh giới do app sở hữu.
|
|
167
|
+
|
|
168
|
+
| Case | Dùng khi | Owner | Render |
|
|
169
|
+
| --- | --- | --- | --- |
|
|
170
|
+
| Case 1 | Một section của app sở hữu trọn khoảng cách từ đường viền tới nội dung | `App` | `<section className="p-4">` với `<Heading level={2}>` |
|
|
171
|
+
| Case 2 | Nội dung bên trong một card | `SurfaceCard` | Ghép card, không viết padding |
|
|
172
|
+
| Case 3 | Trigger của disclosure và panel xổ ra của nó | `SurfaceAccordionCard` | Ghép card, không viết padding |
|
|
173
|
+
| Case 4 | Một thông báo trạng thái rỗng | `EmptyNotice` | Ghép notice, không viết padding |
|
|
174
|
+
| Case 5 | Một khối code có khung | `FencedCodeBlock` | Ghép khối, không viết padding |
|
|
175
|
+
| Case 6 | Cạnh của một dải chạm mép ngoài của surface phẳng | `—` | `pt-4` ở dải đầu, `pb-4` ở dải cuối |
|
|
176
|
+
| Case 7 | Hai cạnh ngang của mọi dải bên trong surface phẳng | `—` | `px-4` cho mọi dải, không đổi theo vị trí |
|
|
177
|
+
|
|
178
|
+
## PADDING-5 — `p-6` / `1.5rem`
|
|
179
|
+
|
|
180
|
+
Một section lớn do app sở hữu, cố ý thoáng hơn mức inset chuẩn.
|
|
181
|
+
|
|
182
|
+
| Case | Dùng khi | Owner | Render |
|
|
183
|
+
| --- | --- | --- | --- |
|
|
184
|
+
| Case 1 | Section mà cả bốn cạnh dùng chung một quyết định ranh giới rộng rãi | `App` | `<section className="p-6">` |
|
|
185
|
+
| Case 2 | Khung tab mang inset ngang ở cấp trang | `Tabs` | `inset="page"` đã đặt sẵn `1.5rem` |
|
|
186
|
+
|
|
187
|
+
Không phải rule này: khoảng cách giữa các section con là gap chứ không phải inset. Dùng GAP-5.
|
|
188
|
+
|
|
189
|
+
## PADDING-6 — `p-8` / `2rem`
|
|
190
|
+
|
|
191
|
+
Một khung rộng rãi có chủ ý bao quanh vùng lớn, mật độ thấp.
|
|
192
|
+
|
|
193
|
+
| Case | Dùng khi | Owner | Render |
|
|
194
|
+
| --- | --- | --- | --- |
|
|
195
|
+
| Case 1 | Vùng trang cần khoảng trống đều và mạnh ở mọi cạnh | `App` | `<section className="p-8">` |
|
|
196
|
+
| Case 2 | Vùng rỗng hoặc đang chờ, chiếm gần hết viewport | `App` | `<section className="p-8">` với `<EmptyNotice>` |
|
|
197
|
+
|
|
198
|
+
Không phải rule này: lấy làm mặc định cho container thường là phí viewport. Dùng PADDING-4.
|
|
199
|
+
|
|
200
|
+
## Inset ghép
|
|
201
|
+
|
|
202
|
+
`PADDING-0` tới `PADDING-6` là thang: một rule, một giá trị. Từ `PADDING-7` trở đi, rule là những
|
|
203
|
+
công thức có tên mà các cạnh cố ý lấy giá trị khác nhau. Số vẫn đếm tiếp, nhưng thôi mang nghĩa vị
|
|
204
|
+
trí trên thang, nên một rule ghép không bao giờ xuất hiện trong bảng thang và không tự đẻ ra giá trị
|
|
205
|
+
mới. Mọi cạnh của một công thức ghép đều quy về một rule gốc.
|
|
206
|
+
|
|
207
|
+
Thêm số tiếp theo khi xuất hiện một pattern lệch cạnh thật sự mới. Phải nêu lý do vì sao từng cạnh
|
|
208
|
+
khác nhau; một công thức không có lý do thì là quyết định riêng của sản phẩm chứ không phải rule.
|
|
209
|
+
|
|
210
|
+
## PADDING-7 — Nhích inset lên ở breakpoint rộng
|
|
211
|
+
|
|
212
|
+
Cùng một ranh giới giữ nguyên ý nghĩa qua các khổ màn hình nhưng lấy rộng hơn khi có chỗ. Giá trị ở
|
|
213
|
+
khổ hẹp và khổ rộng là hai rule liền kề, không bao giờ nhảy hai bậc, và trục block chỉ nhích theo ở
|
|
214
|
+
những cạnh mà chính ranh giới đó làm chủ.
|
|
215
|
+
|
|
216
|
+
Ghép từ: hai cạnh ngang của surface `1rem` → `1.5rem` (PADDING-4 → PADDING-5); cạnh trục block chạm
|
|
217
|
+
mép ngoài `1rem` → `1.5rem` (PADDING-4 → PADDING-5); cạnh trục block chạm separator giữ `.75rem` ở cả
|
|
218
|
+
hai khổ (PADDING-3).
|
|
219
|
+
|
|
220
|
+
| Case | Dùng khi | Owner | Render |
|
|
221
|
+
| --- | --- | --- | --- |
|
|
222
|
+
| Case 1 | Một surface quyết định, inset ngang mở rộng ra ở viewport lớn hơn | `—` | `<div className="px-4 sm:px-6">` |
|
|
223
|
+
| Case 2 | Cạnh trục block chạm mép ngoài của chính surface đó, nhích theo cùng | `—` | `<div className="px-4 pt-4 pb-4 sm:px-6 sm:pt-6 sm:pb-6">` |
|
|
224
|
+
| Case 3 | Cạnh hướng về separator bên trong surface đó | `—` | `pt-3` giữ `.75rem` ở cả hai khổ |
|
|
225
|
+
|
|
226
|
+
Không phải rule này: cạnh chạm separator thì không nhích, vì separator vẫn vẽ đúng một đường ở mọi
|
|
227
|
+
khổ màn hình.
|
|
228
|
+
|
|
229
|
+
## PADDING-8 — Nội dung con thụt vào ở đầu trục inline
|
|
230
|
+
|
|
231
|
+
Nội dung thuộc về hàng nằm trên nó, chứ không phải đứng ngang với nó, thì thụt vào một bậc ở đầu
|
|
232
|
+
trục inline. Chỉ cạnh đầu mang nghĩa phân cấp. Cạnh cuối không mang nghĩa đó nên giữ thẳng hàng với
|
|
233
|
+
cha, và khổ đọc không bị bóp từ cả hai phía.
|
|
234
|
+
|
|
235
|
+
Ghép từ: đầu trục inline cao hơn inset của surface một bậc, `1.5rem` khi surface là `1rem`
|
|
236
|
+
(PADDING-5); cuối trục inline đúng inset của surface, giữ nguyên (PADDING-4); hai cạnh trục block theo
|
|
237
|
+
thứ mà cạnh đó chạm, dùng luật tiếp xúc (PADDING-3, PADDING-4).
|
|
238
|
+
|
|
239
|
+
| Case | Dùng khi | Owner | Render |
|
|
240
|
+
| --- | --- | --- | --- |
|
|
241
|
+
| Case 1 | Phần thân disclosure, đọc lên là câu trả lời thuộc về trigger phía trên | `—` | `<div className="ps-6 pe-4 py-3">` |
|
|
242
|
+
| Case 2 | Một nhóm lồng mà các hàng thuộc về một hàng cha có tên | `—` | Cùng công thức, mỗi cấp lồng thêm một bậc |
|
|
243
|
+
|
|
244
|
+
Không phải rule này: thụt vào cả hai bên, vì như vậy là canh giữa nội dung và đọc lên thành một khối
|
|
245
|
+
inset riêng chứ không phải nội dung con.
|
|
246
|
+
|
|
247
|
+
Disclosure của Common hiện áp `1rem` cho cả hai cạnh ngang, nên dạng thụt vào chưa có đường công
|
|
248
|
+
khai. Việc sửa thuộc về chính component disclosure, không phải một override ở tầng ứng dụng.
|
|
249
|
+
|
|
250
|
+
## PADDING-9 — Inset trục dọc của một block được route ở breakpoint rộng
|
|
251
|
+
|
|
252
|
+
Một block được route tự cho `main` của nó một inset dọc, nhích lên khi chiều rộng cho phép, còn
|
|
253
|
+
inset trục ngang vẫn thuộc `PageContainer` (`--grammar-page-inset`). Hai giá trị là hai rule kề nhau
|
|
254
|
+
trên thang, không bao giờ nhảy hai bậc, và ở đây chỉ viết trục dọc.
|
|
255
|
+
|
|
256
|
+
Ghép từ: hai cạnh trục dọc của block được route `1.5rem` → `2rem` (PADDING-5 → PADDING-6); trục ngang
|
|
257
|
+
không có, vì `PageContainer` sở hữu (MEASURE-1).
|
|
258
|
+
|
|
259
|
+
| Case | Dùng khi | Owner | Render |
|
|
260
|
+
| --- | --- | --- | --- |
|
|
261
|
+
| Case 1 | `main` của một block được route nằm ngay dưới shell của trang | `App` | `<main className="py-6 sm:py-8">` |
|
|
262
|
+
|
|
263
|
+
Bằng chứng, bốn block viết cùng một cặp: `blocks/commerce/ProSubscriptionBlock/classNames.ts`
|
|
264
|
+
(`proPageClassName`), `blocks/learn/CoursePlaygroundCatalog/classNames.ts`,
|
|
265
|
+
`blocks/learn/PlaygroundSession/classNames.ts`, `blocks/learn/PlaygroundSetup/classNames.ts`.
|
|
266
|
+
|
|
267
|
+
Không phải rule này: inset trục ngang trên cùng phần tử, vì nó lặp lại inset trang mà container đã
|
|
268
|
+
vẽ. Dùng MEASURE-1 và để yên trục ngang.
|
|
269
|
+
|
|
270
|
+
## Biến thể theo trục
|
|
271
|
+
|
|
272
|
+
`px-*` và `py-*` không phải rule riêng. Chúng áp một rule đã có lên một trục, khi hai trục mang hai
|
|
273
|
+
quyết định ranh giới khác nhau.
|
|
274
|
+
|
|
275
|
+
| Trục | Class | Nghĩa |
|
|
276
|
+
| --- | --- | --- |
|
|
277
|
+
| Inline | `px-*` | Rule đã chọn chỉ áp cho cạnh đầu và cuối theo chiều ngang |
|
|
278
|
+
| Block | `py-*` | Rule đã chọn chỉ áp cho cạnh trên và dưới |
|
|
279
|
+
| Một cạnh | `pt-*`, `pb-*`, `ps-*`, `pe-*` | Rule đã chọn chỉ áp cho đúng cạnh đó |
|
|
280
|
+
|
|
281
|
+
Class theo từng cạnh chính là thứ luật tiếp xúc cần đến, vì hai cạnh trục block của cùng một dải
|
|
282
|
+
thường lấy hai giá trị khác nhau. `px-4 pt-4 pb-3` là PADDING-4 ở ba cạnh và PADDING-3 ở cạnh chạm
|
|
283
|
+
separator, và mỗi cạnh vẫn phải gọi tên case của riêng nó.
|
|
284
|
+
|
|
285
|
+
Trên cùng một phần tử, class hẹp hơn thắng ở những cạnh nó gọi tên chứ không cộng vào nhau, nên
|
|
286
|
+
`p-4 pb-3` cho ra `1rem` ở ba cạnh và `.75rem` ở cạnh dưới. Giữa hai phần tử lồng nhau thì padding
|
|
287
|
+
cộng dồn, và đó là lý do một wrapper thêm vào chỉ để sửa một cạnh lại tạo ra inset không ai muốn.
|
|
288
|
+
|
|
289
|
+
## File này không quyết định
|
|
290
|
+
|
|
291
|
+
Khoảng cách giữa các phần tử ngang hàng thuộc về [Gap](gap.md). Khoảng lệch ra ngoài thuộc về
|
|
292
|
+
[Margin](margin.md). Page inset thuộc về `PageContainer` và không phải quyết định của ứng dụng.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Surface presentation
|
|
2
|
+
|
|
3
|
+
This file answers one question: given a region the application already decided to render, which
|
|
4
|
+
semantic surface token does that region take, and which foreground token pairs with it?
|
|
5
|
+
|
|
6
|
+
A surface and its foreground always travel together. The application never picks a background alone,
|
|
7
|
+
because a background chosen without its paired foreground leaves the copy on top of it unmeasured,
|
|
8
|
+
which is a contrast defect rather than a preference. Paint inside a Grammar object belongs to
|
|
9
|
+
Grammar; this file resolves only the faces the application owns.
|
|
10
|
+
|
|
11
|
+
## Catalog
|
|
12
|
+
|
|
13
|
+
Surfaces have no value ramp, so the rule ID is an address over semantic faces rather than a position
|
|
14
|
+
on a scale. Prefer the earliest rule that works: no surface before the card face, the card face
|
|
15
|
+
before a secondary band, a neutral band before a meaningful one.
|
|
16
|
+
|
|
17
|
+
| Rule | Surface | Paired foreground | Carries |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| SURFACE-1 | none, transparent | inherited | Content that already sits on someone else's face |
|
|
20
|
+
| SURFACE-2 | `--surface` | `--surface-foreground` | The face of one bounded object |
|
|
21
|
+
| SURFACE-3 | `--surface-secondary` | `--foreground` | A neutral band inside a joined surface |
|
|
22
|
+
| SURFACE-4 | `--accent-soft` | `--accent-soft-foreground` | A band the product deliberately raises |
|
|
23
|
+
| SURFACE-5 | `--success-soft` | the foreground of the band it replaces | A proven outcome |
|
|
24
|
+
| SURFACE-6 | `--accent` | none, because it carries no copy | A decorative slab |
|
|
25
|
+
|
|
26
|
+
`--surface`, `--surface-secondary` and `--accent` are base theme tokens; `--accent-soft` and
|
|
27
|
+
`--success-soft` and their `-foreground` partners are derived from `--accent` and `--success` on the
|
|
28
|
+
same element, so overriding a base moves the whole family. Nothing on this list may be reproduced as
|
|
29
|
+
a hex value, an `oklch()` literal, or a palette step, even when the literal matches the token.
|
|
30
|
+
|
|
31
|
+
## Owner
|
|
32
|
+
|
|
33
|
+
Each case names who owns the face. The owner decides whether the application writes a class at all.
|
|
34
|
+
|
|
35
|
+
| Owner | Meaning | Application writes |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `App` | The region belongs to the application | The class pair |
|
|
38
|
+
| A component name | Grammar already paints this face | Nothing. Pass the prop |
|
|
39
|
+
| `—` | Grammar exposes no public path for this face | The class pair, recorded as a workaround |
|
|
40
|
+
|
|
41
|
+
Writing a class where a component is the owner is `APP_REIMPLEMENTATION`. Writing one where the owner
|
|
42
|
+
is `—` stays linked to `COMMON_CAPABILITY_MISSING`. Reaching into a Grammar component with a selector
|
|
43
|
+
or a passed class to repaint it is `APP_OVERRIDE`.
|
|
44
|
+
|
|
45
|
+
## Surfaces Common already owns
|
|
46
|
+
|
|
47
|
+
Generated from `@grammar/core` claims by `scripts/generate-presentation-owned.mjs`; edit the component, not this table.
|
|
48
|
+
|
|
49
|
+
| Component | Element or condition | Rule |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `ChatWorkspace` | drawer close, hasRail, isCompactRail | SURFACE-2 |
|
|
52
|
+
| `ChatWorkspace` | rail trigger, hasRail, isCompactRail | SURFACE-2 |
|
|
53
|
+
| `IconTile` | root, not (isSkeleton || showsArtwork), tone="accent" | SURFACE-4 |
|
|
54
|
+
| `IconTile` | root, not (isSkeleton || showsArtwork), tone="success" | SURFACE-5 |
|
|
55
|
+
| `MediaFrame` | root, treatment!="plain" | SURFACE-3 |
|
|
56
|
+
| `MediaFrame` | root, treatment="plain" | SURFACE-1 |
|
|
57
|
+
| `Sidebar` | list box item | SURFACE-4 |
|
|
58
|
+
| `SurfaceAccordionCard` | accordion shell, bounded | SURFACE-2 |
|
|
59
|
+
| `SurfaceAccordionCard` | accordion shell, not bounded | SURFACE-1 |
|
|
60
|
+
| `SurfaceCard` | card content, frame!="frameless" | SURFACE-2 |
|
|
61
|
+
| `SurfaceCard` | card content, frame="frameless" | SURFACE-1 |
|
|
62
|
+
| `SurfaceCard` | root, wholeAction!=undefined | SURFACE-4 |
|
|
63
|
+
| `SurfaceListCard` | root | SURFACE-2 |
|
|
64
|
+
| `TextAction` | root, isCurrent, appearance="choice"|"route"|"section" | SURFACE-4 |
|
|
65
|
+
|
|
66
|
+
## SURFACE-1 — no surface / inherited foreground
|
|
67
|
+
|
|
68
|
+
The region paints nothing, because the face underneath it is already the right one and the copy
|
|
69
|
+
inherits the foreground that was measured against it.
|
|
70
|
+
|
|
71
|
+
| Case | When | Owner | Render |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| Case 1 | Content whose visible boundaries are already drawn by its children | `SurfaceCard` | `frame="frameless"` already sets transparent and `color: inherit` |
|
|
74
|
+
| Case 2 | Media that must sit on the page canvas with no inset frame | `MediaFrame` | `treatment="plain"` already clears the fill and the border |
|
|
75
|
+
| Case 3 | An app-owned grouping container that only stacks children which each own a face | `App` | `<div className="flex min-w-0 flex-col">` with no background class |
|
|
76
|
+
|
|
77
|
+
Not this rule: a region that needs its own readable face uses SURFACE-2.
|
|
78
|
+
|
|
79
|
+
## SURFACE-2 — `bg-surface` / `text-surface-foreground`
|
|
80
|
+
|
|
81
|
+
The face of one bounded object. This is the surface a card presents to the page, and Grammar paints
|
|
82
|
+
it as a pair so the copy inside never has to state a colour.
|
|
83
|
+
|
|
84
|
+
| Case | When | Owner | Render |
|
|
85
|
+
| --- | --- | --- | --- |
|
|
86
|
+
| Case 1 | One bounded content object on the page canvas | `SurfaceCard` | Compose the card; no background class |
|
|
87
|
+
| Case 2 | A list or disclosure presenting the same bounded face | `SurfaceListCard`, `SurfaceAccordionCard` | Compose the card; no background class |
|
|
88
|
+
| Case 3 | Rows that must show the card face through a joined list at reduced alpha | `—` | `<li className="bg-surface/90">` inside a joined card body |
|
|
89
|
+
|
|
90
|
+
Not this rule: a band that must read as recessed against the card face uses SURFACE-3.
|
|
91
|
+
|
|
92
|
+
Case 3 composes two layers, so contrast is measured against the resulting pixels rather than against
|
|
93
|
+
`--surface` alone.
|
|
94
|
+
|
|
95
|
+
## SURFACE-3 — `bg-surface-secondary` / `text-foreground`
|
|
96
|
+
|
|
97
|
+
A neutral band inside a joined surface. It reads as recessed against the card face without claiming
|
|
98
|
+
any meaning of its own, so the copy on it stays at ordinary reading attention.
|
|
99
|
+
|
|
100
|
+
| Case | When | Owner | Render |
|
|
101
|
+
| --- | --- | --- | --- |
|
|
102
|
+
| Case 1 | A summary or explanatory band running edge to edge inside a joined card | `—` | `<div className="bg-surface-secondary px-4 py-3 text-foreground">` |
|
|
103
|
+
| Case 2 | A full-bleed illustration band sharing that neutral face | `—` | `<div className="min-w-0 bg-surface-secondary">` with the image inside |
|
|
104
|
+
| Case 3 | The recessed backdrop behind framed media | `MediaFrame` | Compose the frame; no background class |
|
|
105
|
+
|
|
106
|
+
Not this rule: a band the product means to raise uses SURFACE-4, and a band reporting a proven
|
|
107
|
+
outcome uses SURFACE-5.
|
|
108
|
+
|
|
109
|
+
The theme also publishes `--surface-secondary-foreground`, but the bands above pair the secondary
|
|
110
|
+
face with `--foreground`. The two resolve to the same value in both themes, so the pair is recorded
|
|
111
|
+
as it is written rather than corrected here.
|
|
112
|
+
|
|
113
|
+
## SURFACE-4 — `bg-accent-soft` / `text-accent-soft-foreground`
|
|
114
|
+
|
|
115
|
+
A band the product deliberately raises. Accent is scarce: repeated across every peer band it stops
|
|
116
|
+
marking anything, and the raised band still has to say in words why it is raised.
|
|
117
|
+
|
|
118
|
+
| Case | When | Owner | Render |
|
|
119
|
+
| --- | --- | --- | --- |
|
|
120
|
+
| Case 1 | One summary band inside a joined card that the product ranked above its peers | `—` | `<div className="bg-accent-soft px-4 py-3 text-accent-soft-foreground">` |
|
|
121
|
+
| Case 2 | A leading marker inside a raised region, taking the paired foreground alone | `—` | `<Icon className="text-accent-soft-foreground" />` on the raised band |
|
|
122
|
+
| Case 3 | A card whose whole face responds to hover or keyboard focus | `SurfaceCard` | `interaction="whole-action"` already swaps the face |
|
|
123
|
+
|
|
124
|
+
Not this rule: a full-strength accent slab under copy. Use SURFACE-6 only where no copy sits on it.
|
|
125
|
+
|
|
126
|
+
A raised band that omits `text-accent-soft-foreground` leaves its copy on the inherited foreground of
|
|
127
|
+
whatever face it replaced, which is the defect this rule exists to prevent.
|
|
128
|
+
|
|
129
|
+
`--accent-soft` is a `color-mix` of `--accent` toward transparent, so the band is translucent and the
|
|
130
|
+
face beneath it shows through. Contrast on this rule is therefore measured against the composed
|
|
131
|
+
pixels, never against the token.
|
|
132
|
+
|
|
133
|
+
## SURFACE-5 — `bg-success-soft` / the foreground of the band it replaces
|
|
134
|
+
|
|
135
|
+
A band reports a proven outcome by exchanging its neutral face for the status face, and keeps the
|
|
136
|
+
foreground the neutral band already established.
|
|
137
|
+
|
|
138
|
+
| Case | When | Owner | Render |
|
|
139
|
+
| --- | --- | --- | --- |
|
|
140
|
+
| Case 1 | A band whose outcome is settled and evidenced, not merely promised | `—` | The neutral band class followed by `bg-success-soft` |
|
|
141
|
+
| Case 2 | The same band before the outcome is proven | `—` | Stay on SURFACE-3; do not pre-paint the result |
|
|
142
|
+
|
|
143
|
+
Not this rule: colour as the only carrier of the outcome. The band still states the outcome in words,
|
|
144
|
+
and status colour is never chosen as a text tone.
|
|
145
|
+
|
|
146
|
+
The status face is exchanged without a matching foreground swap, so the pair is `--success-soft` under
|
|
147
|
+
`--foreground`. That composition has to be measured, not assumed, in both themes.
|
|
148
|
+
|
|
149
|
+
## SURFACE-6 — `bg-accent` / no paired foreground
|
|
150
|
+
|
|
151
|
+
A decorative slab at full accent strength. It is admissible only because nothing readable sits on it,
|
|
152
|
+
which is also why it names no foreground.
|
|
153
|
+
|
|
154
|
+
| Case | When | Owner | Render |
|
|
155
|
+
| --- | --- | --- | --- |
|
|
156
|
+
| Case 1 | A hero band carrying one decorative image and no copy | `—` | `<div className="relative isolate min-h-32 overflow-hidden bg-accent">` with an `aria-hidden` image |
|
|
157
|
+
|
|
158
|
+
Not this rule: any slab carrying text, a label, or a control. Put copy on SURFACE-4 instead, where a
|
|
159
|
+
paired foreground exists.
|
|
160
|
+
|
|
161
|
+
## What this file does not decide
|
|
162
|
+
|
|
163
|
+
Which foreground a single line of copy takes, once its surface is settled, is [Tone](tone.md). The
|
|
164
|
+
edges that separate two faces are [Boundary](boundary.md). The space between a face and its content
|
|
165
|
+
is [Padding](padding.md), and the clipping that keeps a band inside its card is
|
|
166
|
+
[Overflow](overflow.md).
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Surface presentation
|
|
2
|
+
|
|
3
|
+
File này trả lời đúng một câu hỏi: một vùng mà ứng dụng đã quyết định render thì nhận token surface
|
|
4
|
+
ngữ nghĩa nào, và token foreground nào đi kèm với nó.
|
|
5
|
+
|
|
6
|
+
Surface và foreground luôn đi thành cặp. Ứng dụng không bao giờ chọn riêng phần nền, vì một nền chọn
|
|
7
|
+
mà thiếu foreground đi kèm sẽ để phần copy nằm trên đó không được đo, và đó là lỗi tương phản chứ
|
|
8
|
+
không phải chuyện sở thích. Phần sơn bên trong một Grammar object thuộc về Grammar; file này chỉ giải
|
|
9
|
+
quyết những mặt do ứng dụng sở hữu.
|
|
10
|
+
|
|
11
|
+
## Danh mục
|
|
12
|
+
|
|
13
|
+
Surface không có thang giá trị, nên số của rule là địa chỉ trên tập các mặt ngữ nghĩa chứ không phải
|
|
14
|
+
vị trí trên một dải. Ưu tiên rule đứng trước nếu nó đủ dùng: không surface trước mặt card, mặt card
|
|
15
|
+
trước dải phụ, dải trung tính trước dải mang nghĩa.
|
|
16
|
+
|
|
17
|
+
| Rule | Surface | Foreground đi kèm | Mang nghĩa |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| SURFACE-1 | không có, trong suốt | kế thừa | Nội dung đã nằm trên mặt của người khác |
|
|
20
|
+
| SURFACE-2 | `--surface` | `--surface-foreground` | Mặt của một đối tượng có ranh giới |
|
|
21
|
+
| SURFACE-3 | `--surface-secondary` | `--foreground` | Dải trung tính bên trong một surface nối liền |
|
|
22
|
+
| SURFACE-4 | `--accent-soft` | `--accent-soft-foreground` | Dải mà sản phẩm cố ý nâng lên |
|
|
23
|
+
| SURFACE-5 | `--success-soft` | foreground của dải mà nó thay thế | Một kết quả đã được chứng minh |
|
|
24
|
+
| SURFACE-6 | `--accent` | không có, vì không có copy nằm trên | Một mảng trang trí |
|
|
25
|
+
|
|
26
|
+
`--surface`, `--surface-secondary` và `--accent` là token nền của theme; `--accent-soft`,
|
|
27
|
+
`--success-soft` cùng các bản `-foreground` của chúng được dẫn xuất từ `--accent` và `--success` trên
|
|
28
|
+
cùng một element, nên ghi đè token gốc là dịch cả họ. Không token nào trong danh sách này được viết
|
|
29
|
+
lại bằng giá trị hex, literal `oklch()`, hay một bậc palette, kể cả khi literal đó trùng giá trị.
|
|
30
|
+
|
|
31
|
+
## Owner
|
|
32
|
+
|
|
33
|
+
Mỗi case gọi tên ai sở hữu cái mặt đó. Owner quyết định ứng dụng có được viết class hay không.
|
|
34
|
+
|
|
35
|
+
| Owner | Nghĩa | Ứng dụng viết |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `App` | Vùng thuộc về ứng dụng | Viết cặp class |
|
|
38
|
+
| Tên component | Grammar đã sơn mặt này | Không viết gì, chỉ truyền prop |
|
|
39
|
+
| `—` | Grammar chưa có đường dùng công khai cho mặt này | Viết cặp class, ghi nhận là workaround |
|
|
40
|
+
|
|
41
|
+
Viết class ở chỗ owner là một component chính là `APP_REIMPLEMENTATION`. Viết class ở chỗ owner là `—`
|
|
42
|
+
thì gắn liền với `COMMON_CAPABILITY_MISSING`. Thò vào một Grammar component bằng selector hay class
|
|
43
|
+
truyền xuống để sơn lại nó là `APP_OVERRIDE`.
|
|
44
|
+
|
|
45
|
+
## Surface mà Common đã sở hữu
|
|
46
|
+
|
|
47
|
+
Sinh từ claim của `@grammar/core` bằng `scripts/generate-presentation-owned.mjs`; muốn đổi thì sửa component, đừng sửa bảng này.
|
|
48
|
+
|
|
49
|
+
| Component | Phần tử hoặc điều kiện | Rule |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `ChatWorkspace` | drawer close, hasRail, isCompactRail | SURFACE-2 |
|
|
52
|
+
| `ChatWorkspace` | rail trigger, hasRail, isCompactRail | SURFACE-2 |
|
|
53
|
+
| `IconTile` | root, not (isSkeleton || showsArtwork), tone="accent" | SURFACE-4 |
|
|
54
|
+
| `IconTile` | root, not (isSkeleton || showsArtwork), tone="success" | SURFACE-5 |
|
|
55
|
+
| `MediaFrame` | root, treatment!="plain" | SURFACE-3 |
|
|
56
|
+
| `MediaFrame` | root, treatment="plain" | SURFACE-1 |
|
|
57
|
+
| `Sidebar` | list box item | SURFACE-4 |
|
|
58
|
+
| `SurfaceAccordionCard` | accordion shell, bounded | SURFACE-2 |
|
|
59
|
+
| `SurfaceAccordionCard` | accordion shell, not bounded | SURFACE-1 |
|
|
60
|
+
| `SurfaceCard` | card content, frame!="frameless" | SURFACE-2 |
|
|
61
|
+
| `SurfaceCard` | card content, frame="frameless" | SURFACE-1 |
|
|
62
|
+
| `SurfaceCard` | root, wholeAction!=undefined | SURFACE-4 |
|
|
63
|
+
| `SurfaceListCard` | root | SURFACE-2 |
|
|
64
|
+
| `TextAction` | root, isCurrent, appearance="choice"|"route"|"section" | SURFACE-4 |
|
|
65
|
+
|
|
66
|
+
## SURFACE-1 — không có surface / foreground kế thừa
|
|
67
|
+
|
|
68
|
+
Vùng không sơn gì cả, vì cái mặt bên dưới nó đã đúng rồi và phần copy kế thừa đúng foreground đã được
|
|
69
|
+
đo trên mặt đó.
|
|
70
|
+
|
|
71
|
+
| Case | Dùng khi | Owner | Render |
|
|
72
|
+
| --- | --- | --- | --- |
|
|
73
|
+
| Case 1 | Nội dung mà các ranh giới nhìn thấy được đã do chính con của nó vẽ | `SurfaceCard` | `frame="frameless"` đã đặt sẵn trong suốt và `color: inherit` |
|
|
74
|
+
| Case 2 | Media phải nằm thẳng trên canvas trang, không khung lót | `MediaFrame` | `treatment="plain"` đã bỏ sẵn phần nền và border |
|
|
75
|
+
| Case 3 | Container gom nhóm do app sở hữu, chỉ xếp chồng các con mà mỗi con tự có mặt riêng | `App` | `<div className="flex min-w-0 flex-col">` không có class nền |
|
|
76
|
+
|
|
77
|
+
Không phải rule này: một vùng cần mặt đọc được của riêng nó thì dùng SURFACE-2.
|
|
78
|
+
|
|
79
|
+
## SURFACE-2 — `bg-surface` / `text-surface-foreground`
|
|
80
|
+
|
|
81
|
+
Mặt của một đối tượng có ranh giới. Đây là mặt mà một card trình ra với trang, và Grammar sơn nó theo
|
|
82
|
+
cặp để phần copy bên trong không bao giờ phải tự khai màu.
|
|
83
|
+
|
|
84
|
+
| Case | Dùng khi | Owner | Render |
|
|
85
|
+
| --- | --- | --- | --- |
|
|
86
|
+
| Case 1 | Một đối tượng nội dung có ranh giới nằm trên canvas trang | `SurfaceCard` | Ghép card, không viết class nền |
|
|
87
|
+
| Case 2 | Một list hay disclosure trình ra cùng cái mặt có ranh giới đó | `SurfaceListCard`, `SurfaceAccordionCard` | Ghép card, không viết class nền |
|
|
88
|
+
| Case 3 | Các hàng phải để lộ mặt card qua một list nối liền ở độ trong nhất định | `—` | `<li className="bg-surface/90">` bên trong thân card nối liền |
|
|
89
|
+
|
|
90
|
+
Không phải rule này: một dải cần đọc thành lõm xuống so với mặt card thì dùng SURFACE-3.
|
|
91
|
+
|
|
92
|
+
Case 3 chồng hai lớp lên nhau, nên tương phản được đo trên pixel kết quả chứ không phải trên riêng
|
|
93
|
+
`--surface`.
|
|
94
|
+
|
|
95
|
+
## SURFACE-3 — `bg-surface-secondary` / `text-foreground`
|
|
96
|
+
|
|
97
|
+
Dải trung tính bên trong một surface nối liền. Nó đọc thành lõm xuống so với mặt card mà không tự
|
|
98
|
+
nhận nghĩa nào, nên phần copy trên đó vẫn ở mức chú ý đọc bình thường.
|
|
99
|
+
|
|
100
|
+
| Case | Dùng khi | Owner | Render |
|
|
101
|
+
| --- | --- | --- | --- |
|
|
102
|
+
| Case 1 | Dải tóm tắt hoặc diễn giải chạy sát hai mép trong một card nối liền | `—` | `<div className="bg-surface-secondary px-4 py-3 text-foreground">` |
|
|
103
|
+
| Case 2 | Dải minh hoạ tràn viền dùng chung cái mặt trung tính đó | `—` | `<div className="min-w-0 bg-surface-secondary">` với ảnh bên trong |
|
|
104
|
+
| Case 3 | Nền lõm phía sau media có khung | `MediaFrame` | Ghép frame, không viết class nền |
|
|
105
|
+
|
|
106
|
+
Không phải rule này: dải mà sản phẩm cố ý nâng lên thì dùng SURFACE-4, còn dải báo một kết quả đã
|
|
107
|
+
chứng minh thì dùng SURFACE-5.
|
|
108
|
+
|
|
109
|
+
Theme cũng công bố `--surface-secondary-foreground`, nhưng các dải ở trên ghép mặt phụ với
|
|
110
|
+
`--foreground`. Hai token này quy về cùng một giá trị ở cả hai theme, nên cặp này được ghi đúng như nó
|
|
111
|
+
đang được viết chứ không sửa lại ở đây.
|
|
112
|
+
|
|
113
|
+
## SURFACE-4 — `bg-accent-soft` / `text-accent-soft-foreground`
|
|
114
|
+
|
|
115
|
+
Một dải mà sản phẩm cố ý nâng lên. Accent là thứ khan hiếm: lặp lại trên mọi dải ngang hàng thì nó
|
|
116
|
+
thôi đánh dấu điều gì, và dải được nâng vẫn phải nói bằng chữ vì sao nó được nâng.
|
|
117
|
+
|
|
118
|
+
| Case | Dùng khi | Owner | Render |
|
|
119
|
+
| --- | --- | --- | --- |
|
|
120
|
+
| Case 1 | Một dải tóm tắt trong card nối liền mà sản phẩm đã xếp trên các dải ngang hàng | `—` | `<div className="bg-accent-soft px-4 py-3 text-accent-soft-foreground">` |
|
|
121
|
+
| Case 2 | Một dấu dẫn đầu bên trong vùng được nâng, chỉ lấy riêng foreground đi kèm | `—` | `<Icon className="text-accent-soft-foreground" />` trên dải được nâng |
|
|
122
|
+
| Case 3 | Một card mà cả mặt của nó phản hồi khi hover hoặc focus bàn phím | `SurfaceCard` | `interaction="whole-action"` đã đổi sẵn cái mặt |
|
|
123
|
+
|
|
124
|
+
Không phải rule này: một mảng accent nguyên độ nằm dưới copy. Chỉ dùng SURFACE-6 ở chỗ không có copy
|
|
125
|
+
nào nằm trên.
|
|
126
|
+
|
|
127
|
+
Một dải được nâng mà bỏ `text-accent-soft-foreground` sẽ để copy của nó nằm trên foreground kế thừa
|
|
128
|
+
của cái mặt mà nó vừa thay, và đó chính là lỗi mà rule này sinh ra để chặn.
|
|
129
|
+
|
|
130
|
+
`--accent-soft` là một `color-mix` của `--accent` pha về phía trong suốt, nên dải này không đục và mặt
|
|
131
|
+
bên dưới vẫn lộ qua. Vì vậy tương phản ở rule này được đo trên pixel đã chồng, không bao giờ đo trên
|
|
132
|
+
token.
|
|
133
|
+
|
|
134
|
+
## SURFACE-5 — `bg-success-soft` / foreground của dải mà nó thay thế
|
|
135
|
+
|
|
136
|
+
Một dải báo kết quả đã được chứng minh bằng cách đổi mặt trung tính của nó lấy mặt trạng thái, và giữ
|
|
137
|
+
nguyên foreground mà dải trung tính đã thiết lập.
|
|
138
|
+
|
|
139
|
+
| Case | Dùng khi | Owner | Render |
|
|
140
|
+
| --- | --- | --- | --- |
|
|
141
|
+
| Case 1 | Dải mà kết quả đã ngã ngũ và có bằng chứng, không phải mới chỉ hứa | `—` | Class dải trung tính rồi tới `bg-success-soft` |
|
|
142
|
+
| Case 2 | Chính dải đó khi kết quả chưa được chứng minh | `—` | Ở lại SURFACE-3, không sơn trước kết quả |
|
|
143
|
+
|
|
144
|
+
Không phải rule này: lấy màu làm phương tiện duy nhất chở kết quả. Dải vẫn phải nói kết quả bằng chữ,
|
|
145
|
+
và màu trạng thái không bao giờ được chọn như một tone chữ.
|
|
146
|
+
|
|
147
|
+
Mặt trạng thái được đổi mà không đổi foreground kèm theo, nên cặp thực tế là `--success-soft` dưới
|
|
148
|
+
`--foreground`. Tổ hợp đó phải được đo ở cả hai theme, không được suy đoán.
|
|
149
|
+
|
|
150
|
+
## SURFACE-6 — `bg-accent` / không có foreground đi kèm
|
|
151
|
+
|
|
152
|
+
Một mảng trang trí ở độ accent nguyên. Nó chỉ được chấp nhận vì không có gì đọc được nằm trên đó, và
|
|
153
|
+
cũng chính vì thế nó không gọi tên foreground nào.
|
|
154
|
+
|
|
155
|
+
| Case | Dùng khi | Owner | Render |
|
|
156
|
+
| --- | --- | --- | --- |
|
|
157
|
+
| Case 1 | Dải hero chỉ mang một ảnh trang trí và không mang copy | `—` | `<div className="relative isolate min-h-32 overflow-hidden bg-accent">` với một ảnh `aria-hidden` |
|
|
158
|
+
|
|
159
|
+
Không phải rule này: bất kỳ mảng nào mang chữ, nhãn hay control. Đặt copy lên SURFACE-4, nơi có sẵn
|
|
160
|
+
một foreground đi kèm.
|
|
161
|
+
|
|
162
|
+
## File này không quyết định
|
|
163
|
+
|
|
164
|
+
Một dòng copy lấy foreground nào sau khi surface của nó đã chốt là việc của [Tone](tone.md). Các cạnh
|
|
165
|
+
tách hai mặt là [Boundary](boundary.md). Khoảng cách giữa một mặt và nội dung của nó là
|
|
166
|
+
[Padding](padding.md), còn việc cắt để giữ một dải nằm gọn trong card là [Overflow](overflow.md).
|