@tempoxyz/ds 0.3.10 → 0.3.13

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.
Files changed (210) hide show
  1. package/README.md +7 -6
  2. package/dist/docs/AGENTS.md +12 -0
  3. package/dist/docs/index.md +145 -0
  4. package/dist/docs/marketing/introduction/getting-started.md +241 -0
  5. package/dist/docs/product/components/accordion.md +489 -0
  6. package/dist/docs/product/components/address-card.md +147 -0
  7. package/dist/docs/product/components/agent-logo.md +194 -0
  8. package/dist/docs/product/components/alert-dialog.md +631 -0
  9. package/dist/docs/product/components/alert.md +448 -0
  10. package/dist/docs/product/components/amount-input.md +305 -0
  11. package/dist/docs/product/components/amount.md +794 -0
  12. package/dist/docs/product/components/app-shell.md +559 -0
  13. package/dist/docs/product/components/asset-chip.md +294 -0
  14. package/dist/docs/product/components/autocomplete.md +620 -0
  15. package/dist/docs/product/components/avatar.md +321 -0
  16. package/dist/docs/product/components/badge.md +339 -0
  17. package/dist/docs/product/components/badged-logo.md +116 -0
  18. package/dist/docs/product/components/banner.md +379 -0
  19. package/dist/docs/product/components/bar-chart.md +208 -0
  20. package/dist/docs/product/components/breadcrumbs.md +380 -0
  21. package/dist/docs/product/components/button-group.md +286 -0
  22. package/dist/docs/product/components/button.md +650 -0
  23. package/dist/docs/product/components/calendar.md +375 -0
  24. package/dist/docs/product/components/callout.md +96 -0
  25. package/dist/docs/product/components/checkbox-group.md +521 -0
  26. package/dist/docs/product/components/checkbox.md +467 -0
  27. package/dist/docs/product/components/choice-group.md +516 -0
  28. package/dist/docs/product/components/choicebox.md +641 -0
  29. package/dist/docs/product/components/code.md +100 -0
  30. package/dist/docs/product/components/combobox.md +939 -0
  31. package/dist/docs/product/components/container.md +257 -0
  32. package/dist/docs/product/components/copy-button.md +642 -0
  33. package/dist/docs/product/components/date-picker.md +514 -0
  34. package/dist/docs/product/components/dialog.md +1332 -0
  35. package/dist/docs/product/components/drawer.md +966 -0
  36. package/dist/docs/product/components/empty.md +340 -0
  37. package/dist/docs/product/components/field.md +570 -0
  38. package/dist/docs/product/components/fieldset.md +398 -0
  39. package/dist/docs/product/components/frame.md +434 -0
  40. package/dist/docs/product/components/gauge.md +299 -0
  41. package/dist/docs/product/components/get-started-visual.md +117 -0
  42. package/dist/docs/product/components/grid.md +288 -0
  43. package/dist/docs/product/components/hero-visual.md +155 -0
  44. package/dist/docs/product/components/hex.md +487 -0
  45. package/dist/docs/product/components/icon-tile.md +292 -0
  46. package/dist/docs/product/components/identicon.md +371 -0
  47. package/dist/docs/product/components/inline.md +233 -0
  48. package/dist/docs/product/components/inset.md +225 -0
  49. package/dist/docs/product/components/kbd.md +423 -0
  50. package/dist/docs/product/components/list-group.md +981 -0
  51. package/dist/docs/product/components/loading-dots.md +237 -0
  52. package/dist/docs/product/components/menu.md +934 -0
  53. package/dist/docs/product/components/native-select.md +204 -0
  54. package/dist/docs/product/components/network-logo.md +248 -0
  55. package/dist/docs/product/components/number-field.md +543 -0
  56. package/dist/docs/product/components/otp-field.md +616 -0
  57. package/dist/docs/product/components/pagination.md +516 -0
  58. package/dist/docs/product/components/popover.md +544 -0
  59. package/dist/docs/product/components/progress.md +388 -0
  60. package/dist/docs/product/components/qr.md +314 -0
  61. package/dist/docs/product/components/radio-group.md +528 -0
  62. package/dist/docs/product/components/secret-value.md +406 -0
  63. package/dist/docs/product/components/select.md +751 -0
  64. package/dist/docs/product/components/separator.md +379 -0
  65. package/dist/docs/product/components/sidebar-menu.md +922 -0
  66. package/dist/docs/product/components/skeleton.md +517 -0
  67. package/dist/docs/product/components/slider.md +539 -0
  68. package/dist/docs/product/components/snippet.md +424 -0
  69. package/dist/docs/product/components/spinner.md +216 -0
  70. package/dist/docs/product/components/split-bar.md +139 -0
  71. package/dist/docs/product/components/split-button.md +464 -0
  72. package/dist/docs/product/components/stack.md +299 -0
  73. package/dist/docs/product/components/status-chip.md +226 -0
  74. package/dist/docs/product/components/status-indicator.md +119 -0
  75. package/dist/docs/product/components/status-row.md +174 -0
  76. package/dist/docs/product/components/stepper.md +451 -0
  77. package/dist/docs/product/components/success-hero.md +157 -0
  78. package/dist/docs/product/components/surface.md +639 -0
  79. package/dist/docs/product/components/switch.md +472 -0
  80. package/dist/docs/product/components/table.md +967 -0
  81. package/dist/docs/product/components/tabs.md +627 -0
  82. package/dist/docs/product/components/tempo-logo.md +185 -0
  83. package/dist/docs/product/components/text-input.md +624 -0
  84. package/dist/docs/product/components/textarea.md +352 -0
  85. package/dist/docs/product/components/toast.md +535 -0
  86. package/dist/docs/product/components/token-logo.md +371 -0
  87. package/dist/docs/product/components/token.md +247 -0
  88. package/dist/docs/product/components/tooltip.md +490 -0
  89. package/dist/docs/product/components/verification-code.md +116 -0
  90. package/dist/docs/product/components/visual.md +249 -0
  91. package/dist/docs/product/components.md +203 -0
  92. package/dist/docs/product/foundations/accessibility.md +377 -0
  93. package/dist/docs/product/foundations/agents.md +150 -0
  94. package/dist/docs/product/foundations/color.md +522 -0
  95. package/dist/docs/product/foundations/content/tone.md +194 -0
  96. package/dist/docs/product/foundations/content/voice.md +194 -0
  97. package/dist/docs/product/foundations/content/writing-style.md +406 -0
  98. package/dist/docs/product/foundations/iconography.md +282 -0
  99. package/dist/docs/product/foundations/intent.md +360 -0
  100. package/dist/docs/product/foundations/layout.md +508 -0
  101. package/dist/docs/product/foundations/linter.md +496 -0
  102. package/dist/docs/product/foundations/motion.md +151 -0
  103. package/dist/docs/product/foundations/radius-and-padding.md +86 -0
  104. package/dist/docs/product/foundations/typography.md +266 -0
  105. package/dist/docs/product/introduction/faq.md +133 -0
  106. package/dist/docs/product/introduction/getting-started.md +377 -0
  107. package/dist/docs/product/patterns/blockchain-values.md +709 -0
  108. package/dist/docs/product/patterns/dashboards.md +638 -0
  109. package/dist/docs/product/patterns/destructive-actions.md +626 -0
  110. package/dist/docs/product/patterns/empty-states.md +612 -0
  111. package/dist/docs/product/patterns/errors-and-validation.md +808 -0
  112. package/dist/docs/product/patterns/feedback-and-status.md +518 -0
  113. package/dist/docs/product/patterns/forms.md +679 -0
  114. package/dist/docs/product/patterns/loading.md +630 -0
  115. package/dist/docs/product/patterns/navigation.md +397 -0
  116. package/dist/docs/product/patterns/payments.md +765 -0
  117. package/dist/docs/product/patterns/responsive-layout.md +997 -0
  118. package/dist/docs/product/patterns/tables-and-lists.md +964 -0
  119. package/dist/docs/product/products/tempo-console.md +671 -0
  120. package/dist/docs/product/products/tempo-performance.md +437 -0
  121. package/dist/docs/product/products/tempo-wallet.md +438 -0
  122. package/dist/marketing/index.d.ts +4 -0
  123. package/dist/marketing/index.d.ts.map +1 -0
  124. package/dist/marketing/index.js +4 -0
  125. package/dist/marketing/index.js.map +1 -0
  126. package/dist/marketing/index.js.zyzz.json +1 -0
  127. package/dist/marketing/vars.d.ts +907 -0
  128. package/dist/marketing/vars.d.ts.map +1 -0
  129. package/dist/marketing/vars.js +15 -0
  130. package/dist/marketing/vars.js.map +1 -0
  131. package/dist/marketing/vars.js.zyzz.json +1 -0
  132. package/dist/marketing/zyzz.config.d.ts +4512 -0
  133. package/dist/marketing/zyzz.config.d.ts.map +1 -0
  134. package/dist/marketing/zyzz.config.js +63 -0
  135. package/dist/marketing/zyzz.config.js.map +1 -0
  136. package/dist/marketing/zyzz.config.js.zyzz.json +1 -0
  137. package/dist/oxlint/index.d.ts +12 -1
  138. package/dist/oxlint/index.d.ts.map +1 -1
  139. package/dist/oxlint/index.js +39 -1
  140. package/dist/oxlint/index.js.map +1 -1
  141. package/dist/oxlint/internal/Docs.d.ts +9 -0
  142. package/dist/oxlint/internal/Docs.d.ts.map +1 -0
  143. package/dist/oxlint/internal/Docs.js +15 -0
  144. package/dist/oxlint/internal/Docs.js.map +1 -0
  145. package/dist/oxlint/internal/Imports.d.ts +1 -1
  146. package/dist/oxlint/internal/Imports.d.ts.map +1 -1
  147. package/dist/oxlint/internal/Imports.js +2 -0
  148. package/dist/oxlint/internal/Imports.js.map +1 -1
  149. package/dist/oxlint/internal/Styles.d.ts +40 -0
  150. package/dist/oxlint/internal/Styles.d.ts.map +1 -0
  151. package/dist/oxlint/internal/Styles.js +194 -0
  152. package/dist/oxlint/internal/Styles.js.map +1 -0
  153. package/dist/oxlint/internal/Tokens.d.ts +16 -0
  154. package/dist/oxlint/internal/Tokens.d.ts.map +1 -0
  155. package/dist/oxlint/internal/Tokens.js +39 -0
  156. package/dist/oxlint/internal/Tokens.js.map +1 -0
  157. package/dist/oxlint/rules/NoCrossSurface.d.ts +2 -2
  158. package/dist/oxlint/rules/NoCrossSurface.js +3 -3
  159. package/dist/oxlint/rules/NoCrossSurface.js.map +1 -1
  160. package/dist/oxlint/rules/NoCustomValues.d.ts +11 -0
  161. package/dist/oxlint/rules/NoCustomValues.d.ts.map +1 -0
  162. package/dist/oxlint/rules/NoCustomValues.js +150 -0
  163. package/dist/oxlint/rules/NoCustomValues.js.map +1 -0
  164. package/dist/oxlint/rules/NoSingleKeyShortcuts.d.ts +11 -0
  165. package/dist/oxlint/rules/NoSingleKeyShortcuts.d.ts.map +1 -0
  166. package/dist/oxlint/rules/NoSingleKeyShortcuts.js +158 -0
  167. package/dist/oxlint/rules/NoSingleKeyShortcuts.js.map +1 -0
  168. package/dist/oxlint/rules/PreferComponent.d.ts +3 -2
  169. package/dist/oxlint/rules/PreferComponent.d.ts.map +1 -1
  170. package/dist/oxlint/rules/PreferComponent.js +11 -4
  171. package/dist/oxlint/rules/PreferComponent.js.map +1 -1
  172. package/dist/oxlint/rules/PreferLayout.d.ts +9 -0
  173. package/dist/oxlint/rules/PreferLayout.d.ts.map +1 -0
  174. package/dist/oxlint/rules/PreferLayout.js +90 -0
  175. package/dist/oxlint/rules/PreferLayout.js.map +1 -0
  176. package/dist/oxlint/rules/PreferProgress.d.ts +9 -0
  177. package/dist/oxlint/rules/PreferProgress.d.ts.map +1 -0
  178. package/dist/oxlint/rules/PreferProgress.js +43 -0
  179. package/dist/oxlint/rules/PreferProgress.js.map +1 -0
  180. package/dist/oxlint/rules/PreferSurface.d.ts +9 -0
  181. package/dist/oxlint/rules/PreferSurface.d.ts.map +1 -0
  182. package/dist/oxlint/rules/PreferSurface.js +55 -0
  183. package/dist/oxlint/rules/PreferSurface.js.map +1 -0
  184. package/dist/oxlint/rules/RequireAccessibleName.d.ts.map +1 -1
  185. package/dist/oxlint/rules/RequireAccessibleName.js +3 -1
  186. package/dist/oxlint/rules/RequireAccessibleName.js.map +1 -1
  187. package/dist/oxlint/rules/RequireScrollRegion.d.ts +11 -0
  188. package/dist/oxlint/rules/RequireScrollRegion.d.ts.map +1 -0
  189. package/dist/oxlint/rules/RequireScrollRegion.js +71 -0
  190. package/dist/oxlint/rules/RequireScrollRegion.js.map +1 -0
  191. package/dist/web/icons/index.js.zyzz.json +1 -0
  192. package/package.json +6 -1
  193. package/src/marketing/README.md +21 -0
  194. package/src/marketing/index.ts +3 -0
  195. package/src/marketing/vars.ts +751 -0
  196. package/src/marketing/zyzz.config.ts +376 -0
  197. package/src/oxlint/index.ts +40 -1
  198. package/src/oxlint/internal/Docs.ts +18 -0
  199. package/src/oxlint/internal/Imports.ts +2 -1
  200. package/src/oxlint/internal/Styles.ts +240 -0
  201. package/src/oxlint/internal/Tokens.ts +43 -0
  202. package/src/oxlint/rules/NoCrossSurface.ts +3 -3
  203. package/src/oxlint/rules/NoCustomValues.ts +170 -0
  204. package/src/oxlint/rules/NoSingleKeyShortcuts.ts +190 -0
  205. package/src/oxlint/rules/PreferComponent.ts +11 -4
  206. package/src/oxlint/rules/PreferLayout.ts +96 -0
  207. package/src/oxlint/rules/PreferProgress.ts +49 -0
  208. package/src/oxlint/rules/PreferSurface.ts +70 -0
  209. package/src/oxlint/rules/RequireAccessibleName.ts +3 -1
  210. package/src/oxlint/rules/RequireScrollRegion.ts +92 -0
@@ -0,0 +1,496 @@
1
+ # Linter
2
+
3
+ Oxlint rules that check how your app uses Tempo Design System.
4
+
5
+ ## Overview
6
+
7
+ The library ships an Oxlint plugin that reports the mistakes types cannot catch, such as an unnamed icon-only [Button](../components/button.md), an import from an internal path, or a native control or hand-built card that a component replaces.
8
+
9
+ ## Set Up the Linter
10
+
11
+ Spread the configuration for your surface, web, mobile, or marketing, into the lint options of Vite Plus, then run the linter.
12
+
13
+ ```ts title="vite.config.ts"
14
+ import { web } from '@tempoxyz/ds/oxlint'
15
+ import { defineConfig } from 'vite-plus'
16
+
17
+ export default defineConfig({ lint: web })
18
+ ```
19
+
20
+ ```sh
21
+ vp lint
22
+ ```
23
+
24
+ To change a severity, spread the rules and override one.
25
+
26
+ ```ts title="vite.config.ts"
27
+ import { web } from '@tempoxyz/ds/oxlint'
28
+ import { defineConfig } from 'vite-plus'
29
+
30
+ export default defineConfig({
31
+ lint: { ...web, rules: { ...web.rules, 'tds/no-raw-values': 'error' } },
32
+ })
33
+ ```
34
+
35
+ Without Vite Plus, register both plugins and enable the rules in an Oxlint configuration file.
36
+
37
+ ```json title=".oxlintrc.json"
38
+ {
39
+ "jsPlugins": [
40
+ { "name": "tds", "specifier": "@tempoxyz/ds/oxlint" },
41
+ { "name": "zyzz", "specifier": "zyzz/oxlint" }
42
+ ],
43
+ "rules": {
44
+ "tds/no-cross-surface": ["error", { "surface": "web" }],
45
+ "tds/no-custom-values": "warn",
46
+ "tds/no-internal-imports": "error",
47
+ "tds/no-raw-values": "warn",
48
+ "tds/no-single-key-shortcuts": "warn",
49
+ "tds/prefer-component": "warn",
50
+ "tds/prefer-layout": "warn",
51
+ "tds/prefer-progress": "warn",
52
+ "tds/prefer-surface": "warn",
53
+ "tds/require-accessible-name": "error",
54
+ "tds/require-scroll-region": "warn"
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Rules
60
+
61
+ ### Import From Public Entrypoints
62
+
63
+ Reports a library import from a path that the package does not publish. The root, icons, mobile, and oxlint entrypoints pass, and so do icon SVG and font files.
64
+
65
+ ```json
66
+ { "rules": { "tds/no-internal-imports": "error" } }
67
+ ```
68
+
69
+ **Do:** Import components from `@tempoxyz/ds` and icons from `@tempoxyz/ds/icons`.
70
+
71
+ ````tsx
72
+ ```tsx
73
+ import { Spinner } from '@tempoxyz/ds'
74
+ import { Copy } from '@tempoxyz/ds/icons'
75
+ ```
76
+ ````
77
+
78
+ **Don't:** Import from `src` or `dist`. Internal paths change without a release note.
79
+
80
+ ````tsx
81
+ ```tsx
82
+ import { Spinner } from '@tempoxyz/ds/src/web'
83
+ // @lint: tds/no-internal-imports: '@tempoxyz/ds/src/web' is not a published entrypoint. Import from '@tempoxyz/ds', '@tempoxyz/ds/mobile', or their `icons` subpaths.
84
+ import { Copy } from '@tempoxyz/ds/dist/icons'
85
+ // @lint: tds/no-internal-imports: '@tempoxyz/ds/dist/icons' is not a published entrypoint. Import from '@tempoxyz/ds', '@tempoxyz/ds/mobile', or their `icons` subpaths.
86
+ ```
87
+ ````
88
+
89
+ ### Stay on One Surface
90
+
91
+ Reports an import from the surface that your app does not render. Without a surface option, it reports a file that imports from both surfaces.
92
+
93
+ ```json
94
+ { "rules": { "tds/no-cross-surface": ["error", { "surface": "web" }] } }
95
+ ```
96
+
97
+ **Do:** Import every component and icon of a React Native app from the Mobile entrypoints.
98
+
99
+ ````tsx
100
+ ```tsx
101
+ import { Toggle } from '@tempoxyz/ds/mobile'
102
+ import { Copy } from '@tempoxyz/ds/mobile/icons'
103
+ ```
104
+ ````
105
+
106
+ **Don't:** Mix surfaces in one app. Each surface owns its variables, and a Web component does not render in React Native.
107
+
108
+ ````tsx
109
+ ```tsx
110
+ import { Toggle } from '@tempoxyz/ds/mobile'
111
+ import { Switch } from '@tempoxyz/ds'
112
+ // @lint: tds/no-cross-surface: '@tempoxyz/ds' belongs to the web surface, but this app uses the mobile surface.
113
+ ```
114
+ ````
115
+
116
+ ### Name Controls Without Text
117
+
118
+ Reports a library component that renders no text and has no accessible name, such as an icon-only [Button](../components/button.md) or a [Gauge](../components/gauge.md).
119
+
120
+ ```json
121
+ { "rules": { "tds/require-accessible-name": "error" } }
122
+ ```
123
+
124
+ **Do:** Name the action in `aria-label`, on the button or on the trigger that renders it.
125
+
126
+ ````tsx
127
+ ```tsx
128
+ <Button aria-label="Copy address" shape="icon">
129
+ <Copy />
130
+ </Button>
131
+ ```
132
+ ````
133
+
134
+ **Don't:** Leave an icon-only button or a [Gauge](../components/gauge.md) unnamed. Screen readers announce only its role.
135
+
136
+ ````tsx
137
+ ```tsx
138
+ <Button shape="icon">
139
+ <Copy />
140
+ </Button>
141
+ // @lint: tds/require-accessible-name: 'Button' renders no text. Name it with `aria-label` or `aria-labelledby`.
142
+ ```
143
+ ````
144
+
145
+ ### Name Scroll Regions
146
+
147
+ Reports a native element that scrolls without a role, a tab stop, and a name. A container whose content holds a link, a button, or a field passes, since its content takes focus.
148
+
149
+ ```json
150
+ { "rules": { "tds/require-scroll-region": "warn" } }
151
+ ```
152
+
153
+ **Do:** Name the region and give it a tab stop, so keyboards can scroll it.
154
+
155
+ ````tsx
156
+ ```tsx
157
+ <div aria-label="Fee schedule" role="region" {...styles.scroll()} tabIndex={0}>
158
+ <Table.Root>{rows}</Table.Root>
159
+ </div>
160
+ ```
161
+ ````
162
+
163
+ **Don't:** Leave a scroll container without a tab stop. Keyboard users cannot reach the hidden content.
164
+
165
+ ````tsx
166
+ ```tsx
167
+ <div style={{ overflowX: 'auto' }}>
168
+ // @lint: tds/require-scroll-region: This scrolling <div> needs role="region", tabIndex={0}, and an aria-label, so keyboard and screen reader users can reach and identify it.
169
+ <Table.Root>{rows}</Table.Root>
170
+ </div>
171
+ ```
172
+ ````
173
+
174
+ ### Require a Modifier on Shortcuts
175
+
176
+ Warns on a key listener on the window or the document that matches one printable character, such as G or /, and reads no modifier. A character key on the whole page fires while people type or dictate ([SC 2.1.4](https://www.w3.org/WAI/WCAG22/Understanding/character-key-shortcuts.html)).
177
+
178
+ ```json
179
+ { "rules": { "tds/no-single-key-shortcuts": "warn" } }
180
+ ```
181
+
182
+ A single-key shortcut is valid when people can turn it off or remap it, or when it works only while a component has focus. The rule cannot detect either case. If a shortcut meets one of them, disable the rule on that line and state the reason.
183
+
184
+ ```tsx
185
+ window.addEventListener('keydown', (event) => {
186
+ // oxlint-disable-next-line tds/no-single-key-shortcuts -- Settings turn this shortcut off.
187
+ if (shortcuts.enabled && event.key === '/') open()
188
+ })
189
+ ```
190
+
191
+ **Do:** Require Command or Control, and show the shortcut with [Kbd](../components/kbd.md).
192
+
193
+ ````tsx
194
+ ```tsx
195
+ window.addEventListener('keydown', (event) => {
196
+ if (event.key === 'k' && (event.metaKey || event.ctrlKey)) open()
197
+ })
198
+ ```
199
+ ````
200
+
201
+ **Don't:** Bind a lone character key on the whole page, unless people can turn it off or remap it.
202
+
203
+ ````tsx
204
+ ```tsx
205
+ window.addEventListener('keydown', (event) => {
206
+ if (event.key === '/') open()
207
+ // @lint: tds/no-single-key-shortcuts: '/' is a single-key shortcut on the whole page. Require a modifier, such as metaKey or ctrlKey, or let people turn it off or remap it (WCAG 2.1.4).
208
+ })
209
+ ```
210
+ ````
211
+
212
+ ### Prefer Library Components
213
+
214
+ Warns on a native element that a library component replaces, such as a button, a checkbox, a dialog, a table, or a description list. The message links the docs page of the component. Elements in a render prop pass, and an allow list skips the elements you name.
215
+
216
+ ```json
217
+ { "rules": { "tds/prefer-component": ["warn", { "allow": ["table"] }] } }
218
+ ```
219
+
220
+ **Do:** Use [Checkbox](../components/checkbox.md) and [Button](../components/button.md), which bring Tempo states, focus rings, and motion.
221
+
222
+ ````tsx
223
+ ```tsx
224
+ <Checkbox defaultChecked />
225
+ <Button variant="secondary">Cancel</Button>
226
+ ```
227
+ ````
228
+
229
+ **Don't:** Restyle a native control that the library already ships.
230
+
231
+ ````tsx
232
+ ```tsx
233
+ <input defaultChecked type="checkbox" />
234
+ // @lint: tds/prefer-component: Use 'Checkbox' from '@tempoxyz/ds' instead of <input>. See https://ds.tehq.dev/docs/components/checkbox
235
+ <button type="button">Cancel</button>
236
+ // @lint: tds/prefer-component: Use 'Button' from '@tempoxyz/ds' instead of <button>. See https://ds.tehq.dev/docs/components/button
237
+ ```
238
+ ````
239
+
240
+ ### Use Surface for Cards
241
+
242
+ Warns on an article, aside, div, list item, or section whose styles draw a card: a corner radius and padding with a border or a background. Pills and positioned layers pass.
243
+
244
+ ```json
245
+ { "rules": { "tds/prefer-surface": "warn" } }
246
+ ```
247
+
248
+ **Do:** Use [Surface](../components/surface.md), which sets the radius and padding together.
249
+
250
+ ````tsx
251
+ ```tsx
252
+ <Surface>
253
+ <Surface.Title>Balance</Surface.Title>
254
+ </Surface>
255
+ ```
256
+ ````
257
+
258
+ **Don't:** Rebuild a card from tokens. The radius and padding drift apart.
259
+
260
+ ````tsx
261
+ ```tsx
262
+ namespace styles {
263
+ export const card = style({
264
+ backgroundColor: 'background.elevated',
265
+ borderRadius: 'sm',
266
+ padding: '16',
267
+ })
268
+ }
269
+
270
+ ;<div {...styles.card()} />
271
+ // @lint: tds/prefer-surface: Use 'Surface' from '@tempoxyz/ds' instead of styling <div> as a card. It sets the radius and padding together.
272
+ ```
273
+ ````
274
+
275
+ ### Use Layout Components
276
+
277
+ Warns on a block element whose only styles lay out its children with flex or grid and a gap. A column maps to [Stack](../components/stack.md), a row to [Inline](../components/inline.md), and equal tracks to [Grid](../components/grid.md). An element with any other declaration or a class name passes.
278
+
279
+ ```json
280
+ { "rules": { "tds/prefer-layout": "warn" } }
281
+ ```
282
+
283
+ **Do:** Space siblings with a layout component and its gap token.
284
+
285
+ ````tsx
286
+ ```tsx
287
+ <Stack gap="16">
288
+ <Field.Root />
289
+ <Field.Root />
290
+ </Stack>
291
+ ```
292
+ ````
293
+
294
+ **Don't:** Hand-build a flex or grid wrapper that a layout component replaces.
295
+
296
+ ````tsx
297
+ ```tsx
298
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
299
+ // @lint: tds/prefer-layout: Use 'Stack' from '@tempoxyz/ds' instead of laying out <div> with flex and a gap.
300
+ <Field.Root />
301
+ <Field.Root />
302
+ </div>
303
+ ```
304
+ ````
305
+
306
+ ### Use Progress for Bars
307
+
308
+ Warns on an empty div or span whose inline width is a computed percentage, which draws the fill of a hand-built bar. An element with children passes, since a resizable pane sets its width the same way.
309
+
310
+ ```json
311
+ { "rules": { "tds/prefer-progress": "warn" } }
312
+ ```
313
+
314
+ **Do:** Use [Progress](../components/progress.md), or `ListGroup.Meter` in a [ListGroup](../components/list-group.md) row, which announce the value.
315
+
316
+ ````tsx
317
+ ```tsx
318
+ <Progress.Root value={66}>
319
+ <Progress.Track>
320
+ <Progress.Indicator />
321
+ </Progress.Track>
322
+ </Progress.Root>
323
+ ```
324
+ ````
325
+
326
+ **Don't:** Draw a bar from a sized element. Screen readers announce nothing.
327
+
328
+ ````tsx
329
+ ```tsx
330
+ <div {...styles.track()}>
331
+ <div style={{ width: `${used}%` }} />
332
+ // @lint: tds/prefer-progress: Use 'Progress' from '@tempoxyz/ds', or 'ListGroup.Meter' in a ListGroup row, instead of drawing a bar with <div> and a percentage width.
333
+ </div>
334
+ ```
335
+ ````
336
+
337
+ ### Style With Tokens
338
+
339
+ Warns on a raw color, length, or font family in an inline style. A value that reads a custom property passes.
340
+
341
+ ```json
342
+ { "rules": { "tds/no-raw-values": "warn" } }
343
+ ```
344
+
345
+ **Do:** Set colors and spacing with tokens in a `style` definition.
346
+
347
+ ````tsx
348
+ ```tsx
349
+ namespace styles {
350
+ export const card = style({ padding: '16' })
351
+ }
352
+
353
+ ;<div {...styles.card()} />
354
+ ```
355
+ ````
356
+
357
+ **Don't:** Hardcode colors and lengths in inline styles. They do not follow the scheme or the token scale.
358
+
359
+ ````tsx
360
+ ```tsx
361
+ <div style={{ color: '#000', padding: 16 }} />
362
+ // @lint: tds/no-raw-values: 'color' sets the raw value '#000'. Use a token through a Zyzz style instead.
363
+ // @lint: tds/no-raw-values: 'padding' sets the raw value 16. Use a token through a Zyzz style instead.
364
+ ```
365
+ ````
366
+
367
+ ### Use Tokens for Custom Values
368
+
369
+ Warns on a `!custom` value in a `style` or `variants` definition that equals a token: a px or rem length that equals a spacing, radius, size, or container step, or a `zIndex` number that equals a layer. The message names the token. A value that no token fits, such as `'13px !custom'`, passes.
370
+
371
+ ```json
372
+ { "rules": { "tds/no-custom-values": "warn" } }
373
+ ```
374
+
375
+ * **Spacing ('16'):** Padding, margins, and gaps, with keys in px.
376
+ * **Radius ('xs'):** Corners, from `3xs` at 4px to `2xl` at 64px.
377
+ * **Layers ('popover'):** `overlay` 40, `popover` 50, `modal` 60, and `toast` 1000.
378
+
379
+ **Do:** Set lengths and layers with tokens.
380
+
381
+ ````tsx
382
+ ```tsx
383
+ style({ borderRadius: 'xs', padding: '16', zIndex: 'popover' })
384
+ ```
385
+ ````
386
+
387
+ **Don't:** Mark a value with `!custom` when a token equals it.
388
+
389
+ ````tsx
390
+ ```tsx
391
+ style({ borderRadius: '12px !custom', zIndex: '50 !custom' })
392
+ // @lint: tds/no-custom-values: 'borderRadius' sets the custom value '12px !custom', which equals a token. Use the radius token 'xs'.
393
+ // @lint: tds/no-custom-values: 'zIndex' sets the custom value '50 !custom', which equals a token. Use the zIndex token 'popover'.
394
+ ```
395
+ ````
396
+
397
+ ## Zyzz Rules
398
+
399
+ Both configurations also turn on the rules of the Zyzz plugin, which check style definitions and their use.
400
+
401
+ ### Merge Styling Props Into the Style
402
+
403
+ Reports a className or style prop that would overwrite a style spread.
404
+
405
+ ```json
406
+ { "rules": { "zyzz/no-conflicting-props": "error" } }
407
+ ```
408
+
409
+ **Do:** Pass the caller's class into the style call, which merges it.
410
+
411
+ ````tsx
412
+ ```tsx
413
+ <div {...styles.card({ className: props.className })} />
414
+ ```
415
+ ````
416
+
417
+ **Don't:** Set a class beside a style spread. One overwrites the other.
418
+
419
+ ````tsx
420
+ ```tsx
421
+ <div className="card" {...styles.card()} />
422
+ // @lint: zyzz/no-conflicting-props: Pass 'className' into the Zyzz style call to merge styling props.
423
+ ```
424
+ ````
425
+
426
+ ### Remove Unused Styles
427
+
428
+ Warns on a style in a styles namespace that nothing applies.
429
+
430
+ ```json
431
+ { "rules": { "zyzz/no-unused": "warn" } }
432
+ ```
433
+
434
+ **Do:** Keep only the styles that the module applies.
435
+
436
+ ````tsx
437
+ ```tsx
438
+ namespace styles {
439
+ export const card = style({ padding: '16' })
440
+ }
441
+
442
+ ;<div {...styles.card()} />
443
+ ```
444
+ ````
445
+
446
+ **Don't:** Leave a style that nothing applies. It still ships in the stylesheet.
447
+
448
+ ````tsx
449
+ ```tsx
450
+ namespace styles {
451
+ export const card = style({ padding: '16' })
452
+
453
+ export const legacy = style({ padding: '8' })
454
+ // @lint: zyzz/no-unused: Style 'styles.legacy' is never used.
455
+ }
456
+ ```
457
+ ````
458
+
459
+ ### Write Scalar Declarations
460
+
461
+ Reports a declaration whose value is not a string, a number, or a fallback array.
462
+
463
+ ```json
464
+ { "rules": { "zyzz/valid-styles": "error" } }
465
+ ```
466
+
467
+ **Do:** Set each side with its own property.
468
+
469
+ ````tsx
470
+ ```tsx
471
+ style({ paddingTop: '8' })
472
+ ```
473
+ ````
474
+
475
+ **Don't:** Nest an object inside a declaration.
476
+
477
+ ````tsx
478
+ ```tsx
479
+ style({ padding: { top: '8' } })
480
+ // @lint: zyzz/valid-styles: CSS declarations require scalar values or fallback arrays.
481
+ ```
482
+ ````
483
+
484
+ ## Related
485
+
486
+ [Agents](agents.md)
487
+
488
+ Covers the rules that coding agents follow and the checks they run.
489
+
490
+ [Accessibility](accessibility.md)
491
+
492
+ Covers the accessible names that the linter checks.
493
+
494
+ [Getting Started](../introduction/getting-started.md)
495
+
496
+ Covers the install and the Zyzz setup that the linter builds on.
@@ -0,0 +1,151 @@
1
+ # Motion
2
+
3
+ Easing curves, durations, and reduced-motion rules for state changes.
4
+
5
+ ## Overview
6
+
7
+ Motion in Tempo shows where something came from, confirms a press, and marks what is new. It is short, eases out, and never delays input, both in components and in anything you style yourself.
8
+
9
+ ## Easing
10
+
11
+ Three ease-out curves cover interface motion, so each response starts at full speed. Use them by name instead of the browser default or a custom curve.
12
+
13
+ * `ease.snappy`: `cubic-bezier(0.23, 1, 0.32, 1)`
14
+ * `ease.spring`: `cubic-bezier(0.32, 0.72, 0, 1)`
15
+ * `ease.overshoot`: `cubic-bezier(0.34, 1.36, 0.64, 1)`
16
+
17
+ * **Snappy (ease.snappy):** Short changes, such as hover, focus, presses, popovers, and menus.
18
+ * **Overshoot (ease.overshoot):** Only physical feedback, such as switch thumbs and check marks. A dialog or menu would wobble.
19
+ * **Spring (ease.spring):** Larger travel, such as dialogs, drawers, toasts, and tabs.
20
+ * **Constant Rate (linear):** Continuous indicators only, such as [Spinner](../components/spinner.md), which would pulse with easing.
21
+
22
+ ## Durations
23
+
24
+ Small state changes finish fastest, overlays that travel farther run longer, and every overlay leaves faster than it enters.
25
+
26
+ ### Micro Interactions
27
+
28
+ * **Press (100ms in, 160ms out):** Buttons scale down slightly and return on release.
29
+ * **Item Highlight (120ms):** Menu, select, and combobox items highlight.
30
+ * **Hover, Focus, and Color (160ms):** Buttons, tabs, and checkboxes change color.
31
+ * **Check Indicators (200ms):** Checkbox and radio indicators overshoot slightly.
32
+ * **Switch Thumb (240ms):** The switch thumb slides with a slight overshoot.
33
+ * **Selection Indicator (240ms):** The tabs indicator slides to the selected tab.
34
+
35
+ ### Overlays and Content
36
+
37
+ * **Tooltip (160ms in, 100ms out):** Tooltips fade in, and leave faster than they enter.
38
+ * **Popover and Menu (200ms in, 140ms out):** Popovers and menus grow from their trigger.
39
+ * **Select and Combobox List (240ms in, 150ms out):** Lists open on the spring curve and close on snappy.
40
+ * **Expanding Content (240ms in, 180ms out):** Accordion panels expand and collapse.
41
+ * **Dialog (280ms in, 180ms out):** Dialogs enter over a fading backdrop.
42
+ * **Drawer and Toast (320ms in, 200 to 240ms out):** Drawers and toasts travel farthest and run longest.
43
+
44
+ ## Guidelines
45
+
46
+ ### Animate Every State Change
47
+
48
+ Give every state change a transition, from hover and focus to checked, disabled, and invalid states. Animate overlays, toasts, expanding content, and changing values in and out.
49
+
50
+ * **Press:** Scale the control to `scale.press` (0.98).
51
+ * **Popover and Menu:** Fade and grow from `scale.enter` (0.96).
52
+ * **Dialog and Drawer:** Fade the backdrop and slide or scale the panel.
53
+ * **Value Change:** Fade or slide the new value in.
54
+
55
+ ### Use Transitions for State, Keyframes for Feedback
56
+
57
+ A transition reverses from its current point when the state flips back, so a quick hover in and out never jumps. Keep keyframes for feedback that plays once.
58
+
59
+ * **State Changes (transition):** Hover, press, checked, and open states, such as the switch thumb.
60
+ * **One-Shot Feedback (keyframes):** The 360ms nudge of an invalid [TextInput](../components/text-input.md) or [OtpField](../components/otp-field.md), and the 240ms pop of a [CopyButton](../components/copy-button.md) check.
61
+
62
+ ### Animate Transform and Opacity
63
+
64
+ Animate transforms, opacity, colors, shadows, and outlines, which change without a new layout. Animate size only when no transform produces the effect, as the components below do.
65
+
66
+ **Do:** Scale a pressed control to `scale: 'press'` with `ease.snappy`.
67
+
68
+ **Don't:** Animate `width`, `height`, or `margin` when a transform produces the same effect.
69
+
70
+ * **Accordion (height):** Panels grow to a measured height, not a transform.
71
+ * **SidebarMenu (height):** Nested items grow as a group expands.
72
+ * **Progress (width):** The indicator grows and keeps its rounded ends.
73
+ * **Tabs (size):** The indicator stretches to match each tab.
74
+ * **Switch (width):** The pressed thumb stretches to keep its round ends.
75
+ * **Toast (height):** Toasts shrink as their stack collapses.
76
+
77
+ ### Never Delay Input
78
+
79
+ Controls respond on the first frame of a press. Don't hold an action until an animation finishes, and don't block the next interaction during an exit animation.
80
+
81
+ **Do:** Use short transitions that respond on the first frame, such as the library's press feedback.
82
+
83
+ ```tsx
84
+ <MotionDemo kind="press" />
85
+ ```
86
+
87
+ **Don't:** Make the reader wait for an animation to finish before they can act.
88
+
89
+ ```tsx
90
+ <MotionDemo kind="slow" />
91
+ ```
92
+
93
+ ## Authoring Motion
94
+
95
+ Set each curve and duration by its token, and pair each transition with a reduced-motion override that removes movement and keeps color changes.
96
+
97
+ ```tsx
98
+ import { style } from '@tempoxyz/ds'
99
+
100
+ namespace styles {
101
+ export const row = style({
102
+ transitionDuration: '160',
103
+ transitionProperty: 'background-color, scale',
104
+ transitionTimingFunction: 'snappy',
105
+ selectors: {
106
+ '&:active': { scale: 'press' },
107
+ '&:hover': { backgroundColor: 'container.regular' },
108
+ },
109
+ '@media (prefers-reduced-motion: reduce)': {
110
+ transitionProperty: 'background-color',
111
+ selectors: { '&:active': { scale: 'none !custom' } },
112
+ },
113
+ })
114
+ }
115
+ ```
116
+
117
+ * **Durations (transitionDuration):** Name a duration in ms, such as `'160'`.
118
+ * **Curves (transitionTimingFunction):** Name a curve, such as `'snappy'`.
119
+ * **Keyframes (vars.ease):** A keyframe shorthand reads its curve from `vars`.
120
+
121
+ ## Reduced Motion
122
+
123
+ When the system asks for reduced motion, components remove movement and scaling, and keep instant changes or brief opacity fades. Apply the same rule in your own styles, and honor Reduce Motion on iOS and Android.
124
+
125
+ ### What Components Change
126
+
127
+ * **Pressable Controls:** Buttons, surfaces, and other pressable controls stop scaling and keep their color transitions.
128
+ * **Overlays:** Popovers, menus, dialogs, and toasts fade in place, including the narrow-screen dialog sheet.
129
+ * **Moving Indicators:** The tabs indicator and the switch thumb jump straight to their final position.
130
+ * **Loading Indicators:** Spinners stop rotating, skeletons stop their sweep, and loading dots stop pulsing.
131
+ * **Feedback:** [CopyButton](../components/copy-button.md) skips its pop, and invalid fields skip their nudge.
132
+
133
+ ## Accessibility
134
+
135
+ [Accessibility](accessibility.md#motion) sets the limits for flashing and for content that moves on its own.
136
+
137
+ * **Never Motion Alone (Field.Error):** Never convey information through motion alone. Pair a nudge on an invalid field with an error message.
138
+
139
+ ## Related
140
+
141
+ [Loading](../patterns/loading.md)
142
+
143
+ Covers spinners, skeletons, and progress.
144
+
145
+ [Feedback and Status](../patterns/feedback-and-status.md)
146
+
147
+ Covers toasts and confirmations.
148
+
149
+ [Accessibility](accessibility.md)
150
+
151
+ Covers reduced motion and other preferences.