@tempoxyz/ds 0.3.11 → 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 (212) hide show
  1. package/README.md +7 -6
  2. package/dist/docs/AGENTS.md +8 -5
  3. package/dist/docs/index.md +143 -135
  4. package/dist/docs/marketing/introduction/getting-started.md +241 -0
  5. package/dist/docs/{components → product/components}/accordion.md +8 -8
  6. package/dist/docs/{components → product/components}/address-card.md +5 -5
  7. package/dist/docs/{components → product/components}/agent-logo.md +5 -5
  8. package/dist/docs/{components → product/components}/alert-dialog.md +11 -11
  9. package/dist/docs/{components → product/components}/alert.md +11 -11
  10. package/dist/docs/{components → product/components}/amount-input.md +13 -13
  11. package/dist/docs/{components → product/components}/amount.md +8 -8
  12. package/dist/docs/{components → product/components}/app-shell.md +13 -13
  13. package/dist/docs/{components → product/components}/asset-chip.md +11 -11
  14. package/dist/docs/{components → product/components}/autocomplete.md +10 -9
  15. package/dist/docs/{components → product/components}/avatar.md +8 -8
  16. package/dist/docs/{components → product/components}/badge.md +9 -9
  17. package/dist/docs/{components → product/components}/badged-logo.md +6 -6
  18. package/dist/docs/{components → product/components}/banner.md +8 -8
  19. package/dist/docs/{components → product/components}/bar-chart.md +7 -7
  20. package/dist/docs/{components → product/components}/breadcrumbs.md +8 -8
  21. package/dist/docs/{components → product/components}/button-group.md +8 -8
  22. package/dist/docs/{components → product/components}/button.md +17 -17
  23. package/dist/docs/{components → product/components}/calendar.md +8 -8
  24. package/dist/docs/{components → product/components}/callout.md +5 -5
  25. package/dist/docs/{components → product/components}/checkbox-group.md +13 -13
  26. package/dist/docs/{components → product/components}/checkbox.md +12 -12
  27. package/dist/docs/{components → product/components}/choice-group.md +14 -14
  28. package/dist/docs/{components → product/components}/choicebox.md +9 -9
  29. package/dist/docs/{components → product/components}/code.md +5 -5
  30. package/dist/docs/{components → product/components}/combobox.md +9 -9
  31. package/dist/docs/{components → product/components}/container.md +7 -7
  32. package/dist/docs/{components → product/components}/copy-button.md +13 -13
  33. package/dist/docs/{components → product/components}/date-picker.md +11 -11
  34. package/dist/docs/{components → product/components}/dialog.md +17 -17
  35. package/dist/docs/{components → product/components}/drawer.md +11 -11
  36. package/dist/docs/{components → product/components}/empty.md +9 -9
  37. package/dist/docs/{components → product/components}/field.md +14 -14
  38. package/dist/docs/{components → product/components}/fieldset.md +6 -6
  39. package/dist/docs/{components → product/components}/frame.md +9 -9
  40. package/dist/docs/{components → product/components}/gauge.md +7 -7
  41. package/dist/docs/{components → product/components}/get-started-visual.md +4 -4
  42. package/dist/docs/{components → product/components}/grid.md +7 -7
  43. package/dist/docs/{components → product/components}/hero-visual.md +4 -4
  44. package/dist/docs/{components → product/components}/hex.md +9 -9
  45. package/dist/docs/{components → product/components}/icon-tile.md +8 -8
  46. package/dist/docs/{components → product/components}/identicon.md +7 -7
  47. package/dist/docs/{components → product/components}/inline.md +7 -7
  48. package/dist/docs/{components → product/components}/inset.md +8 -8
  49. package/dist/docs/{components → product/components}/kbd.md +8 -8
  50. package/dist/docs/{components → product/components}/list-group.md +24 -24
  51. package/dist/docs/{components → product/components}/loading-dots.md +8 -8
  52. package/dist/docs/{components → product/components}/menu.md +14 -14
  53. package/dist/docs/{components → product/components}/native-select.md +7 -7
  54. package/dist/docs/{components → product/components}/network-logo.md +7 -7
  55. package/dist/docs/{components → product/components}/number-field.md +8 -8
  56. package/dist/docs/{components → product/components}/otp-field.md +9 -9
  57. package/dist/docs/{components → product/components}/pagination.md +8 -8
  58. package/dist/docs/{components → product/components}/popover.md +11 -11
  59. package/dist/docs/{components → product/components}/progress.md +10 -10
  60. package/dist/docs/{components → product/components}/qr.md +8 -8
  61. package/dist/docs/{components → product/components}/radio-group.md +10 -10
  62. package/dist/docs/{components → product/components}/secret-value.md +8 -8
  63. package/dist/docs/{components → product/components}/select.md +13 -13
  64. package/dist/docs/{components → product/components}/separator.md +7 -7
  65. package/dist/docs/{components → product/components}/sidebar-menu.md +11 -11
  66. package/dist/docs/{components → product/components}/skeleton.md +9 -9
  67. package/dist/docs/{components → product/components}/slider.md +8 -8
  68. package/dist/docs/{components → product/components}/snippet.md +10 -10
  69. package/dist/docs/{components → product/components}/spinner.md +12 -12
  70. package/dist/docs/{components → product/components}/split-bar.md +5 -5
  71. package/dist/docs/{components → product/components}/split-button.md +11 -11
  72. package/dist/docs/{components → product/components}/stack.md +8 -8
  73. package/dist/docs/{components → product/components}/status-chip.md +7 -7
  74. package/dist/docs/{components → product/components}/status-indicator.md +6 -6
  75. package/dist/docs/{components → product/components}/status-row.md +7 -7
  76. package/dist/docs/{components → product/components}/stepper.md +9 -9
  77. package/dist/docs/{components → product/components}/success-hero.md +5 -5
  78. package/dist/docs/{components → product/components}/surface.md +11 -11
  79. package/dist/docs/{components → product/components}/switch.md +11 -11
  80. package/dist/docs/{components → product/components}/table.md +16 -16
  81. package/dist/docs/{components → product/components}/tabs.md +11 -11
  82. package/dist/docs/{components → product/components}/tempo-logo.md +4 -4
  83. package/dist/docs/{components → product/components}/text-input.md +14 -14
  84. package/dist/docs/{components → product/components}/textarea.md +7 -7
  85. package/dist/docs/{components → product/components}/toast.md +11 -11
  86. package/dist/docs/{components → product/components}/token-logo.md +16 -16
  87. package/dist/docs/{components → product/components}/token.md +10 -10
  88. package/dist/docs/{components → product/components}/tooltip.md +11 -11
  89. package/dist/docs/{components → product/components}/verification-code.md +4 -4
  90. package/dist/docs/{components → product/components}/visual.md +5 -5
  91. package/dist/docs/product/components.md +203 -0
  92. package/dist/docs/{foundations → product/foundations}/accessibility.md +16 -16
  93. package/dist/docs/{foundations → product/foundations}/agents.md +17 -17
  94. package/dist/docs/{foundations → product/foundations}/color.md +7 -7
  95. package/dist/docs/{foundations → product/foundations}/content/tone.md +7 -7
  96. package/dist/docs/{foundations → product/foundations}/content/voice.md +2 -2
  97. package/dist/docs/{foundations → product/foundations}/content/writing-style.md +8 -8
  98. package/dist/docs/{foundations → product/foundations}/iconography.md +11 -11
  99. package/dist/docs/{foundations → product/foundations}/intent.md +28 -28
  100. package/dist/docs/{foundations → product/foundations}/layout.md +22 -22
  101. package/dist/docs/product/foundations/linter.md +496 -0
  102. package/dist/docs/{foundations → product/foundations}/motion.md +7 -7
  103. package/dist/docs/{foundations → product/foundations}/radius-and-padding.md +3 -3
  104. package/dist/docs/{foundations → product/foundations}/typography.md +4 -4
  105. package/dist/docs/{introduction → product/introduction}/faq.md +22 -21
  106. package/dist/docs/{introduction → product/introduction}/getting-started.md +4 -4
  107. package/dist/docs/{patterns → product/patterns}/blockchain-values.md +20 -20
  108. package/dist/docs/{patterns → product/patterns}/dashboards.md +28 -28
  109. package/dist/docs/{patterns → product/patterns}/destructive-actions.md +16 -16
  110. package/dist/docs/{patterns → product/patterns}/empty-states.md +14 -14
  111. package/dist/docs/{patterns → product/patterns}/errors-and-validation.md +23 -23
  112. package/dist/docs/{patterns → product/patterns}/feedback-and-status.md +31 -31
  113. package/dist/docs/{patterns → product/patterns}/forms.md +24 -24
  114. package/dist/docs/{patterns → product/patterns}/loading.md +19 -19
  115. package/dist/docs/{patterns → product/patterns}/navigation.md +23 -23
  116. package/dist/docs/{patterns → product/patterns}/payments.md +20 -20
  117. package/dist/docs/{patterns → product/patterns}/responsive-layout.md +23 -23
  118. package/dist/docs/{patterns → product/patterns}/tables-and-lists.md +19 -19
  119. package/dist/docs/{products → product/products}/tempo-console.md +12 -12
  120. package/dist/docs/{products → product/products}/tempo-performance.md +11 -11
  121. package/dist/docs/{products → product/products}/tempo-wallet.md +12 -12
  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
  211. package/dist/docs/components.md +0 -203
  212. package/dist/docs/foundations/linter.md +0 -296
@@ -1,11 +1,13 @@
1
1
  /** Points native HTML controls and structures at the Web components that replace them. @module */
2
2
  import type { ESTree, Rule } from '@oxlint/plugins'
3
+ import * as Docs from '../internal/Docs.ts'
3
4
 
4
5
  // Each element maps to the component that renders it with Tempo styles, states, and semantics.
5
6
  const elements: Record<string, string> = {
6
7
  button: 'Button',
7
8
  details: 'Accordion',
8
9
  dialog: 'Dialog',
10
+ dl: 'ListGroup',
9
11
  fieldset: 'Fieldset',
10
12
  hr: 'Separator',
11
13
  kbd: 'Kbd',
@@ -35,8 +37,9 @@ const inputs: Record<string, string> = {
35
37
  }
36
38
 
37
39
  /**
38
- * Reports a native element that a `@tempoxyz/ds` component replaces, such as `<button>` or
39
- * `<input type="checkbox">`. Elements inside a `render` prop are skipped, since the component that
40
+ * Reports a native element that a `@tempoxyz/ds` component replaces, such as `<button>`,
41
+ * `<input type="checkbox">`, or a `<dl>` of labels and values. The message links the docs page of
42
+ * the component. Elements inside a `render` prop are skipped, since the component that
40
43
  * receives them supplies the styles.
41
44
  */
42
45
  export const rule: Rule = {
@@ -45,7 +48,7 @@ export const rule: Rule = {
45
48
  description: 'Prefer `@tempoxyz/ds` components over the native elements they replace.',
46
49
  },
47
50
  messages: {
48
- component: "Use '{{component}}' from '@tempoxyz/ds' instead of <{{element}}>.",
51
+ component: "Use '{{component}}' from '@tempoxyz/ds' instead of <{{element}}>. See {{url}}",
49
52
  },
50
53
  schema: [
51
54
  {
@@ -95,7 +98,11 @@ export const rule: Rule = {
95
98
 
96
99
  if (replacement)
97
100
  context.report({
98
- data: { component: replacement, element: node.name.name },
101
+ data: {
102
+ component: replacement,
103
+ element: node.name.name,
104
+ url: Docs.component(replacement),
105
+ },
99
106
  messageId: 'component',
100
107
  node,
101
108
  })
@@ -0,0 +1,96 @@
1
+ /** Points plain flex and grid wrappers at `Stack`, `Inline`, and `Grid`. @module */
2
+ import type { Rule } from '@oxlint/plugins'
3
+ import * as Docs from '../internal/Docs.ts'
4
+ import * as Styles from '../internal/Styles.ts'
5
+
6
+ // Block elements that a layout component renders through `render`.
7
+ const tags = new Set([
8
+ 'article',
9
+ 'aside',
10
+ 'div',
11
+ 'footer',
12
+ 'form',
13
+ 'header',
14
+ 'main',
15
+ 'nav',
16
+ 'ol',
17
+ 'section',
18
+ 'ul',
19
+ ])
20
+
21
+ // Declarations that the layout components set. Any other declaration makes the element more than a
22
+ // layout wrapper, so the rule skips it.
23
+ const layout = new Set([
24
+ 'alignItems',
25
+ 'columnGap',
26
+ 'display',
27
+ 'flexDirection',
28
+ 'flexWrap',
29
+ 'gap',
30
+ 'gridTemplateColumns',
31
+ 'justifyContent',
32
+ 'rowGap',
33
+ ])
34
+
35
+ // Equal tracks that `columns` or `fit` on `Grid` produce.
36
+ const tracks = /^repeat\(\s*(?:[1-46]|12|auto-fill|auto-fit)\s*,/
37
+
38
+ /**
39
+ * Reports a block element whose only styles lay out its children with flex or grid and a gap.
40
+ * A column maps to `Stack`, a row to `Inline`, and equal grid tracks to `Grid`. An element
41
+ * with a `className`, a spread that the rule cannot read, or any other declaration passes.
42
+ */
43
+ export const rule: Rule = {
44
+ meta: {
45
+ docs: {
46
+ description: 'Use `Stack`, `Inline`, or `Grid` instead of a plain flex or grid wrapper.',
47
+ },
48
+ messages: {
49
+ layout:
50
+ "Use '{{component}}' from '@tempoxyz/ds' instead of laying out <{{element}}> with {{display}} and a gap. See {{url}}",
51
+ },
52
+ schema: [],
53
+ type: 'suggestion',
54
+ },
55
+ create(context) {
56
+ return Styles.visit((element) => {
57
+ if (!tags.has(element.tag) || element.open) return
58
+
59
+ const declarations = element.declarations
60
+
61
+ if (![...declarations.keys()].every((property) => layout.has(property))) return
62
+ if (!['columnGap', 'gap', 'rowGap'].some((property) => declarations.has(property))) return
63
+
64
+ const display = Styles.text(declarations.get('display'))
65
+ const component = (() => {
66
+ if (display === 'flex' || display === 'inline-flex') {
67
+ const direction = declarations.has('flexDirection')
68
+ ? Styles.text(declarations.get('flexDirection'))
69
+ : 'row'
70
+
71
+ if (direction === 'column') return 'Stack'
72
+ if (direction === 'row') return 'Inline'
73
+ return undefined
74
+ }
75
+ if (display !== 'grid' && display !== 'inline-grid') return undefined
76
+ if (!declarations.has('gridTemplateColumns')) return 'Stack'
77
+
78
+ const columns = Styles.text(declarations.get('gridTemplateColumns'))
79
+
80
+ return columns !== undefined && tracks.test(columns) ? 'Grid' : undefined
81
+ })()
82
+
83
+ if (component)
84
+ context.report({
85
+ data: {
86
+ component,
87
+ display: display?.replace('inline-', '') ?? '',
88
+ element: element.tag,
89
+ url: Docs.component(component),
90
+ },
91
+ messageId: 'layout',
92
+ node: element.node,
93
+ })
94
+ })
95
+ },
96
+ }
@@ -0,0 +1,49 @@
1
+ /** Points hand-built bars at `Progress` and `ListGroup.Meter`. @module */
2
+ import type { ESTree, Rule } from '@oxlint/plugins'
3
+ import * as Docs from '../internal/Docs.ts'
4
+ import * as Styles from '../internal/Styles.ts'
5
+
6
+ /**
7
+ * Reports an empty `div` or `span` whose inline `width` or `inlineSize` is a computed percentage,
8
+ * such as a template literal that ends in `%`, which draws the fill of a hand-built bar. An element
9
+ * with children passes, since a resizable pane sets its width the same way.
10
+ */
11
+ export const rule: Rule = {
12
+ meta: {
13
+ docs: { description: 'Use `Progress` or `ListGroup.Meter` instead of a hand-built bar.' },
14
+ messages: {
15
+ bar: "Use 'Progress' from '@tempoxyz/ds', or 'ListGroup.Meter' in a ListGroup row, instead of drawing a bar with <{{element}}> and a percentage width. See {{url}}",
16
+ },
17
+ schema: [],
18
+ type: 'suggestion',
19
+ },
20
+ create(context) {
21
+ function percentage(node: ESTree.Node | undefined) {
22
+ if (node?.type === 'TemplateLiteral')
23
+ return node.expressions.length > 0 && node.quasis.at(-1)?.value.cooked?.trim() === '%'
24
+ if (node?.type === 'BinaryExpression')
25
+ return node.operator === '+' && node.right.type === 'Literal' && node.right.value === '%'
26
+ return false
27
+ }
28
+
29
+ return Styles.visit((element) => {
30
+ if (element.tag !== 'div' && element.tag !== 'span') return
31
+
32
+ const parent = element.node.parent
33
+ const empty =
34
+ parent.type === 'JSXElement' &&
35
+ parent.children.every((child) => child.type === 'JSXText' && child.value.trim() === '')
36
+
37
+ if (!empty) return
38
+ if (
39
+ percentage(element.declarations.get('width')) ||
40
+ percentage(element.declarations.get('inlineSize'))
41
+ )
42
+ context.report({
43
+ data: { element: element.tag, url: Docs.component('Progress') },
44
+ messageId: 'bar',
45
+ node: element.node,
46
+ })
47
+ })
48
+ },
49
+ }
@@ -0,0 +1,70 @@
1
+ /** Points native elements styled as cards at `Surface`. @module */
2
+ import type { Rule } from '@oxlint/plugins'
3
+ import * as Docs from '../internal/Docs.ts'
4
+ import * as Styles from '../internal/Styles.ts'
5
+
6
+ // Elements that render a card's container. Inline elements and controls draw other components.
7
+ const tags = new Set(['article', 'aside', 'div', 'li', 'section'])
8
+
9
+ // Values that draw nothing, so the declaration does not make the element a card.
10
+ const empty = /^(?:0(?:\.0+)?(?:px|rem)?|initial|none|transparent|unset)$/
11
+
12
+ /**
13
+ * Reports an `article`, `aside`, `div`, `li`, or `section` whose styles draw a card: a corner
14
+ * radius and padding with a border or a background. A pill radius, such as `full`, and a
15
+ * positioned element, such as a hand-built popover, pass.
16
+ */
17
+ export const rule: Rule = {
18
+ meta: {
19
+ docs: { description: 'Use `Surface` instead of a native element styled as a card.' },
20
+ messages: {
21
+ surface:
22
+ "Use 'Surface' from '@tempoxyz/ds' instead of styling <{{element}}> as a card. It sets the radius and padding together. See {{url}}",
23
+ },
24
+ schema: [],
25
+ type: 'suggestion',
26
+ },
27
+ create(context) {
28
+ return Styles.visit((element) => {
29
+ if (!tags.has(element.tag)) return
30
+
31
+ const declarations = element.declarations
32
+
33
+ function set(property: string) {
34
+ const node = declarations.get(property)
35
+
36
+ if (!node) return false
37
+
38
+ const value = Styles.text(node)
39
+
40
+ return value === undefined || !empty.test(value)
41
+ }
42
+
43
+ const properties = [...declarations.keys()]
44
+ const position = Styles.text(declarations.get('position'))
45
+ const radius = properties.some((property) => {
46
+ if (!property.endsWith('Radius') || !set(property)) return false
47
+
48
+ const value = Styles.text(declarations.get(property))
49
+
50
+ return value === undefined || !/^(?:50%|9{3,}(?:px)?|full)$/.test(value)
51
+ })
52
+ const padding = properties.some((property) => property.startsWith('padding') && set(property))
53
+ const border = properties.some(
54
+ (property) =>
55
+ /^border(?:Block|Bottom|Inline|Left|Right|Top)?(?:End|Start)?(?:Style|Width)?$/.test(
56
+ property,
57
+ ) && set(property),
58
+ )
59
+ const background = set('background') || set('backgroundColor')
60
+
61
+ if (position === 'absolute' || position === 'fixed' || position === 'sticky') return
62
+ if (radius && padding && (border || background))
63
+ context.report({
64
+ data: { element: element.tag, url: Docs.component('Surface') },
65
+ messageId: 'surface',
66
+ node: element.node,
67
+ })
68
+ })
69
+ },
70
+ }
@@ -13,8 +13,10 @@ const list = new Intl.ListFormat('en', { type: 'disjunction' })
13
13
  const web = ['aria-label', 'aria-labelledby']
14
14
  const mobile = ['accessibilityLabel', 'accessibilityLabelledBy', 'aria-label', 'aria-labelledby']
15
15
 
16
- // Components from each surface whose rendered output has no text to name it.
16
+ // Components from each surface whose rendered output has no text to name it. Marketing ships no
17
+ // components yet.
17
18
  const targets: Record<Imports.Surface, Record<string, Target>> = {
19
+ marketing: {},
18
20
  mobile: { IconButton: { names: mobile } },
19
21
  web: {
20
22
  Button: { names: web, when: { attribute: 'shape', value: 'icon' } },
@@ -0,0 +1,92 @@
1
+ /** Requires a name, a role, and a tab stop on native scroll containers. @module */
2
+ import type { ESTree, Rule } from '@oxlint/plugins'
3
+ import * as Docs from '../internal/Docs.ts'
4
+ import * as Styles from '../internal/Styles.ts'
5
+
6
+ const list = new Intl.ListFormat('en', { type: 'conjunction' })
7
+
8
+ // Native elements and component names that take focus, so the region's content holds a tab stop.
9
+ const focusable =
10
+ /^(?:a|button|input|select|summary|textarea|.*(?:Button|Input|Link|Select|Trigger))$/
11
+
12
+ const overflows = ['overflow', 'overflowBlock', 'overflowInline', 'overflowX', 'overflowY']
13
+
14
+ /**
15
+ * Reports a native element that scrolls, through `overflow` set to `auto` or `scroll`, without
16
+ * `role`, `tabIndex`, and `aria-label` or `aria-labelledby`. Keyboards scroll only a focusable
17
+ * region, and screen readers announce the region by its name. An element with a spread that the
18
+ * rule cannot read passes, since the spread can supply the attributes, and so does a container whose
19
+ * content holds a link, a button, a field, or an element with `tabIndex`.
20
+ */
21
+ export const rule: Rule = {
22
+ meta: {
23
+ docs: { description: 'Name scroll containers and make them focusable.' },
24
+ messages: {
25
+ region:
26
+ 'This scrolling <{{element}}> needs {{missing}}, so keyboard and screen reader users can reach and identify it. See {{url}}',
27
+ },
28
+ schema: [],
29
+ type: 'problem',
30
+ },
31
+ create(context) {
32
+ function attribute(node: ESTree.JSXOpeningElement, names: readonly string[]) {
33
+ return node.attributes.some(
34
+ (attribute) =>
35
+ attribute.type === 'JSXAttribute' &&
36
+ attribute.name.type === 'JSXIdentifier' &&
37
+ names.includes(attribute.name.name),
38
+ )
39
+ }
40
+
41
+ // Whether the JSX inside `node` renders an element that takes focus.
42
+ function interactive(node: ESTree.Node): boolean {
43
+ if (node.type === 'JSXOpeningElement') {
44
+ const name = context.sourceCode.getText(node.name).split('.').at(-1) ?? ''
45
+
46
+ if (focusable.test(name) || attribute(node, ['href', 'tabIndex'])) return true
47
+ }
48
+
49
+ return (context.sourceCode.visitorKeys[node.type] ?? []).some((field) => {
50
+ const value: unknown = Reflect.get(node, field)
51
+
52
+ return (Array.isArray(value) ? value : [value]).some(
53
+ (child) => child && typeof child === 'object' && 'type' in child && interactive(child),
54
+ )
55
+ })
56
+ }
57
+
58
+ return Styles.visit((element) => {
59
+ if (element.spread) return
60
+
61
+ const scrolls = overflows.some((property) => {
62
+ const value = Styles.text(element.declarations.get(property))
63
+
64
+ return value !== undefined && /\b(?:auto|scroll)\b/.test(value)
65
+ })
66
+
67
+ if (!scrolls) return
68
+ if (
69
+ element.node.parent.type === 'JSXElement' &&
70
+ element.node.parent.children.some(interactive)
71
+ )
72
+ return
73
+
74
+ const missing = [
75
+ ...(attribute(element.node, ['role']) ? [] : ['role="region"']),
76
+ ...(attribute(element.node, ['tabIndex']) ? [] : ['tabIndex={0}']),
77
+ ...(attribute(element.node, ['aria-label', 'aria-labelledby']) ? [] : ['an aria-label']),
78
+ ]
79
+
80
+ if (missing.length > 0)
81
+ context.report({
82
+ data: {
83
+ element: element.tag,
84
+ missing: list.format(missing),
85
+ url: Docs.page('patterns/responsive-layout#scroll-wide-content-inside-its-own-region'),
86
+ },
87
+ messageId: 'region',
88
+ node: element.node,
89
+ })
90
+ })
91
+ },
92
+ }
@@ -1,203 +0,0 @@
1
- # Overview
2
-
3
- Components for building Tempo products, grouped by the job each one does.
4
-
5
- ## Principles
6
-
7
- * **Native First:** Components render native elements such as `<button>`, `<input>`, and `<table>`, and accept their attributes, refs, classes, and styles.
8
- * **Accessible Behavior:** Components that coordinate focus, keyboard, or popups build on Base UI primitives, which supply keyboard support and ARIA wiring.
9
- * **Composable Parts:** Larger components split into named parts, such as `Dialog.Root` and `Dialog.Title`, so you control the markup between them.
10
- * **Shared Tokens:** Every component reads Tempo variables for color, spacing, type, and motion. A variable scope on a wrapper restyles everything inside it.
11
-
12
- ## Actions
13
-
14
- Controls that start an action or copy a value.
15
-
16
- - [Button](/docs/components/button): Triggers an action, such as submitting a form or sending a payment.
17
- - [ButtonGroup](/docs/components/button-group): Lays out the actions at the foot of a dialog or focused flow.
18
- - [CopyButton](/docs/components/copy-button): Copies a value to the clipboard and confirms the copy.
19
- - [SplitButton](/docs/components/split-button): Pairs a primary action with a menu of alternatives.
20
-
21
- ## Forms and Inputs
22
-
23
- Controls that collect values, and the parts that label and group them. Wrap each control in a [Field](/docs/components/field) to link its label, description, and error.
24
-
25
- - [AmountInput](/docs/components/amount-input): Accepts a decimal amount, such as a payment or transfer value.
26
- - [Autocomplete](/docs/components/autocomplete): A text input that suggests values while people type.
27
- - [Calendar](/docs/components/calendar): A month grid for picking one date or a date range.
28
- - [Checkbox](/docs/components/checkbox): Turns a single independent option on or off.
29
- - [CheckboxGroup](/docs/components/checkbox-group): Shares one value array across a group of checkboxes.
30
- - [Choicebox](/docs/components/choicebox): A selectable tile with an optional icon, title, and description.
31
- - [ChoiceGroup](/docs/components/choice-group): A segmented control for a few mutually exclusive options.
32
- - [Combobox](/docs/components/combobox): A text input that filters a list of fixed options while people type.
33
- - [DatePicker](/docs/components/date-picker): Shows the selected date and opens a calendar in a popup.
34
- - [Field](/docs/components/field): Links a label, description, and error message to a control.
35
- - [Fieldset](/docs/components/fieldset): Groups related fields under a shared legend.
36
- - [NativeSelect](/docs/components/native-select): A native select styled as a field, which opens the browser's own picker.
37
- - [NumberField](/docs/components/number-field): A numeric input with increment and decrement buttons.
38
- - [OtpField](/docs/components/otp-field): Accepts a one-time passcode, one character per slot.
39
- - [RadioGroup](/docs/components/radio-group): Lets you pick a single option from a short list.
40
- - [Select](/docs/components/select): Opens a list of options for choosing one value.
41
- - [Slider](/docs/components/slider): Picks a single value or a range by dragging.
42
- - [Switch](/docs/components/switch): Toggles a setting that applies immediately.
43
- - [Textarea](/docs/components/textarea): A multi-line text field.
44
- - [TextInput](/docs/components/text-input): A text field with optional prefix, suffix, and clear button.
45
-
46
- ## Feedback and Status
47
-
48
- Components that report progress, results, and system state.
49
-
50
- - [Alert](/docs/components/alert): Shows an inline message tied to nearby content.
51
- - [Banner](/docs/components/banner): Shows a full-width message about the system or page.
52
- - [Callout](/docs/components/callout): Sets a short passage of body text apart in a subtle box.
53
- - [Empty](/docs/components/empty): Fills a region that has nothing to show yet.
54
- - [Gauge](/docs/components/gauge): Shows a measurement within a range as a ring.
55
- - [LoadingDots](/docs/components/loading-dots): Shows three pulsing dots for inline loading.
56
- - [Progress](/docs/components/progress): Shows how far a task has advanced.
57
- - [Skeleton](/docs/components/skeleton): Holds the place of content while it loads.
58
- - [Spinner](/docs/components/spinner): Shows that something is loading.
59
- - [StatusChip](/docs/components/status-chip): Names the status of a payment, an authorization, or a network.
60
- - [StatusIndicator](/docs/components/status-indicator): Names the status of a system or a service with a dot and a label.
61
- - [StatusRow](/docs/components/status-row): Follows the status of one payment or task while it runs.
62
- - [Toast](/docs/components/toast): Shows a brief notification that dismisses itself.
63
-
64
- ## Overlays
65
-
66
- Popups that open above the page from a trigger.
67
-
68
- - [AlertDialog](/docs/components/alert-dialog): Asks you to confirm a critical action before it happens.
69
- - [Dialog](/docs/components/dialog): Opens a modal popup over the page.
70
- - [Drawer](/docs/components/drawer): Slides a modal panel in from an edge of the viewport.
71
- - [Menu](/docs/components/menu): Opens a list of actions from a button.
72
- - [Popover](/docs/components/popover): Opens a non-modal popup anchored to a trigger.
73
- - [Tooltip](/docs/components/tooltip): Shows a short label for a control on hover or focus.
74
-
75
- ## Navigation
76
-
77
- Components that move between pages, views, and steps.
78
-
79
- - [Breadcrumbs](/docs/components/breadcrumbs): Shows where the current page sits in the site hierarchy.
80
- - [Pagination](/docs/components/pagination): Moves between pages of a long list.
81
- - [SidebarMenu](/docs/components/sidebar-menu): Vertical navigation for sidebars and drawers.
82
- - [Stepper](/docs/components/stepper): Shows progress through a fixed sequence of steps.
83
- - [Tabs](/docs/components/tabs): Switches between related views.
84
-
85
- ## Layout
86
-
87
- Primitives that size, space, and arrange content. They carry no meaning, so pair them with semantic elements where structure matters.
88
-
89
- - [AppShell](/docs/components/app-shell): Places a sticky sidebar beside the main content column.
90
- - [Container](/docs/components/container): Centers content at a maximum width.
91
- - [Frame](/docs/components/frame): Lays out a header, body, and footer in a column.
92
- - [Grid](/docs/components/grid): Lays out children on a responsive grid.
93
- - [Inline](/docs/components/inline): Lays out children in a horizontal row.
94
- - [Inset](/docs/components/inset): Pads content with spacing tokens.
95
- - [Separator](/docs/components/separator): Draws a rule between groups of content.
96
- - [Stack](/docs/components/stack): Lays out children in a vertical column.
97
- - [Surface](/docs/components/surface): A card container for grouping related content.
98
-
99
- ## Data Display
100
-
101
- Components that present content and records.
102
-
103
- - [Accordion](/docs/components/accordion): Stacks collapsible sections that each reveal a labelled panel.
104
- - [AgentLogo](/docs/components/agent-logo): Shows the logo of an AI agent that works with a wallet.
105
- - [Avatar](/docs/components/avatar): Represents a person, contact, or wallet with an image, initials, or an icon.
106
- - [Badge](/docs/components/badge): Labels a status or category in a compact chip.
107
- - [BarChart](/docs/components/bar-chart): Shows values over time as stacked bars, such as the daily spend of each agent.
108
- - [Code](/docs/components/code): Sets a command, identifier, or value apart inside a sentence.
109
- - [GetStartedVisual](/docs/components/get-started-visual): Illustrates one step of a get started flow at the top of its card.
110
- - [HeroVisual](/docs/components/hero-visual): Shows animated streaks and a title beside the steps of a setup flow.
111
- - [IconTile](/docs/components/icon-tile): Anchors a centered modal with one icon in a rounded square.
112
- - [Kbd](/docs/components/kbd): Shows a keyboard key or shortcut.
113
- - [ListGroup](/docs/components/list-group): Stacks rows for details, pickers, checklists, and settings.
114
- - [Snippet](/docs/components/snippet): Shows a command or value with a copy button.
115
- - [SplitBar](/docs/components/split-bar): Splits one total between two parts, with a legend under the bar.
116
- - [SuccessHero](/docs/components/success-hero): Confirms that a setup flow finished, on a dark card of animated streaks.
117
- - [Table](/docs/components/table): Displays tabular data in rows and columns.
118
- - [TempoLogo](/docs/components/tempo-logo): Shows the Tempo brand logo.
119
- - [VerificationCode](/docs/components/verification-code): Shows a code that a person compares with the one another app shows.
120
- - [Visual](/docs/components/visual): Illustrates the top of an onboarding or feature modal.
121
-
122
- ## Blockchain
123
-
124
- Components for onchain values such as addresses, amounts, and tokens.
125
-
126
- - [AddressCard](/docs/components/address-card): Shows a wallet address to receive funds, with its QR code and networks.
127
- - [Amount](/docs/components/amount): Formats a number as money or a token amount.
128
- - [AssetChip](/docs/components/asset-chip): Labels a supported asset or network in a small chip, which can also pick one.
129
- - [BadgedLogo](/docs/components/badged-logo): Marks a token or an app with a small badge on the corner of its logo.
130
- - [Hex](/docs/components/hex): Shows a hex string, such as an address, truncated to fit.
131
- - [Identicon](/docs/components/identicon): Shows a unique gradient avatar derived from an address.
132
- - [NetworkLogo](/docs/components/network-logo): Shows the logo of the network a token lives on.
133
- - [Qr](/docs/components/qr): Renders a value as a QR code.
134
- - [SecretValue](/docs/components/secret-value): Masks a secret and adds reveal and copy buttons.
135
- - [Token](/docs/components/token): Shows which token an amount is in, and opens a picker to change it.
136
- - [TokenLogo](/docs/components/token-logo): Shows a token logo by contract address or symbol.
137
-
138
- ## Choosing a Component
139
-
140
- ### Choosing One Option
141
-
142
- * **[RadioGroup](/docs/components/radio-group):** Two to five options, all visible at once.
143
- * **[ChoiceGroup](/docs/components/choice-group):** Two to four short options that switch a view or mode.
144
- * **[Choicebox](/docs/components/choicebox):** A few options that each need an icon or description.
145
- * **[Select](/docs/components/select):** Many options, or limited space.
146
- * **[Combobox](/docs/components/combobox):** A long list to filter by typing.
147
- * **[Autocomplete](/docs/components/autocomplete):** Free text with suggested values.
148
-
149
- ### On and Off
150
-
151
- * **[Switch](/docs/components/switch):** A setting that applies the moment you flip it.
152
- * **[Checkbox](/docs/components/checkbox):** A choice that applies on form submit, or agreement to terms.
153
-
154
- ### Messages
155
-
156
- * **[Banner](/docs/components/banner):** A message about the whole product or page, such as an outage.
157
- * **[Alert](/docs/components/alert):** A message about a specific section or field.
158
- * **[Toast](/docs/components/toast):** Confirms that an action finished.
159
- * **[AlertDialog](/docs/components/alert-dialog):** An action that needs a decision before it continues.
160
-
161
- ### Loading
162
-
163
- * **[Skeleton](/docs/components/skeleton):** Content with a known shape.
164
- * **[Progress](/docs/components/progress):** A task with a measurable amount of work.
165
- * **[Spinner](/docs/components/spinner):** A region or control that is busy for an unknown time.
166
- * **[LoadingDots](/docs/components/loading-dots):** A short wait inside a line of text.
167
-
168
- ### Popups
169
-
170
- * **[Dialog](/docs/components/dialog):** A focused task that blocks the page.
171
- * **[Drawer](/docs/components/drawer):** A long form or details panel that keeps page context visible.
172
- * **[Menu](/docs/components/menu):** A list of actions.
173
- * **[Popover](/docs/components/popover):** Interactive content anchored to a trigger, without a block on the page.
174
- * **[Tooltip](/docs/components/tooltip):** A label for an icon button.
175
-
176
- ### Showing a Status
177
-
178
- * **[Table](/docs/components/table) cell `status`:** A status in a table row, as a colored word such as Failed.
179
- * **[StatusChip](/docs/components/status-chip):** The status of a payment, an authorization, or a network, at the end of a row or beside a title.
180
- * **[StatusIndicator](/docs/components/status-indicator):** The live state of a system, a service, or a connection, such as Degraded.
181
- * **[StatusRow](/docs/components/status-row):** One running task that a person follows until it finishes.
182
- * **[Badge](/docs/components/badge):** A category or an attribute, such as Testnet or Nightly.
183
-
184
- ### Lists of Records
185
-
186
- * **[Table](/docs/components/table):** Many records with the same fields, such as payouts or team members, to compare by column.
187
- * **[ListGroup](/docs/components/list-group):** The fields of one record, a short list to pick from or open, or table rows below 640px.
188
- * **[Surface](/docs/components/surface) in a [Grid](/docs/components/grid):** A few similar items that each stand alone, such as metric tiles. Don't make a card per record.
189
-
190
- ### Identifiers
191
-
192
- * **[Hex](/docs/components/hex):** An address or a hash in a row, a card, or a details view. It truncates in the middle to fit its container, down to the prefix and one character on each side, and overflows a narrower one.
193
- * **[Table](/docs/components/table) cell `mono`:** An address or a hash in a table column, shortened with `formatHex` to `0x4f2a…91c3`. Link the row to a details view with a Hex.
194
- * **[Snippet](/docs/components/snippet):** A URL, a command, or another value that isn't hex.
195
- * **[SecretValue](/docs/components/secret-value):** A private key or another secret, hidden until a reveal.
196
-
197
- Set `copy` on a Hex wherever people need the full value. [Building From Intent](/docs/foundations/intent) shows how to choose by the job of the content.
198
-
199
- ### Copying Values
200
-
201
- * **[Snippet](/docs/components/snippet):** A command or value with its own copy button.
202
- * **[CopyButton](/docs/components/copy-button):** Copy beside content that you lay out yourself.
203
- * **[SecretValue](/docs/components/secret-value):** A value that stays hidden until a reveal.