@astryxdesign/core 0.5.2-canary.c9c8564 → 0.5.2-canary.e03511b

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 (216) hide show
  1. package/dist/BottomSheet/BottomSheet.d.ts +3 -1
  2. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  3. package/dist/BottomSheet/BottomSheet.js +4 -3
  4. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
  5. package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
  6. package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
  7. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
  9. package/dist/Button/Button.d.ts +2 -1
  10. package/dist/Button/Button.d.ts.map +1 -1
  11. package/dist/Button/Button.js +11 -4
  12. package/dist/Carousel/Carousel.d.ts.map +1 -1
  13. package/dist/Carousel/Carousel.js +2 -2
  14. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  15. package/dist/Chat/ChatToolCalls.js +34 -17
  16. package/dist/ContextMenu/ContextMenu.d.ts +14 -3
  17. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  18. package/dist/ContextMenu/ContextMenu.js +149 -26
  19. package/dist/ContextMenu/index.d.ts +1 -0
  20. package/dist/ContextMenu/index.d.ts.map +1 -1
  21. package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
  22. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  23. package/dist/DropdownMenu/DropdownMenu.js +340 -26
  24. package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
  25. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  26. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  27. package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
  28. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
  29. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  30. package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
  31. package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
  32. package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
  33. package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
  34. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
  35. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
  36. package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
  37. package/dist/DropdownMenu/index.d.ts +2 -1
  38. package/dist/DropdownMenu/index.d.ts.map +1 -1
  39. package/dist/DropdownMenu/menuWidth.d.ts +14 -0
  40. package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
  41. package/dist/DropdownMenu/menuWidth.js +35 -0
  42. package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
  43. package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
  44. package/dist/DropdownMenu/useMenuOverflow.js +60 -0
  45. package/dist/MoreMenu/MoreMenu.d.ts +8 -2
  46. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  47. package/dist/MoreMenu/MoreMenu.js +2 -0
  48. package/dist/MultiSelector/MultiSelector.d.ts +11 -1
  49. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  50. package/dist/MultiSelector/MultiSelector.js +97 -68
  51. package/dist/MultiSelector/index.d.ts +1 -1
  52. package/dist/MultiSelector/index.d.ts.map +1 -1
  53. package/dist/Popover/Popover.d.ts +10 -6
  54. package/dist/Popover/Popover.d.ts.map +1 -1
  55. package/dist/Popover/Popover.js +141 -16
  56. package/dist/Popover/usePopover.d.ts +4 -0
  57. package/dist/Popover/usePopover.d.ts.map +1 -1
  58. package/dist/Popover/usePopover.js +52 -5
  59. package/dist/Selector/Selector.d.ts +10 -0
  60. package/dist/Selector/Selector.d.ts.map +1 -1
  61. package/dist/Selector/Selector.js +106 -80
  62. package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
  63. package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
  64. package/dist/Selector/SelectorBottomSheet.js +83 -0
  65. package/dist/Selector/index.d.ts +1 -1
  66. package/dist/Selector/index.d.ts.map +1 -1
  67. package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
  68. package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
  69. package/dist/Selector/selectorPresentation.stylex.js +21 -0
  70. package/dist/Selector/useSelectorPresentation.d.ts +32 -0
  71. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
  72. package/dist/Selector/useSelectorPresentation.js +90 -0
  73. package/dist/Toast/useToastGesture.d.ts.map +1 -1
  74. package/dist/Toast/useToastGesture.js +46 -9
  75. package/dist/astryx.css +19 -1
  76. package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
  77. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
  78. package/dist/hooks/useAdaptivePresentation.js +19 -0
  79. package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
  80. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
  81. package/dist/hooks/useFocusReturnVisibility.js +35 -0
  82. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  83. package/dist/hooks/useFocusTrap.js +12 -0
  84. package/locales/en.json +20 -0
  85. package/locales/pseudo.json +15 -0
  86. package/package.json +2 -2
  87. package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
  88. package/src/BottomSheet/BottomSheet.spec.md +177 -0
  89. package/src/BottomSheet/BottomSheet.test.tsx +29 -0
  90. package/src/BottomSheet/BottomSheet.tsx +7 -4
  91. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
  92. package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
  93. package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
  94. package/src/Button/Button.doc.mjs +56 -0
  95. package/src/Button/Button.test.tsx +10 -0
  96. package/src/Button/Button.tsx +13 -5
  97. package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
  98. package/src/Carousel/Carousel.test.tsx +83 -0
  99. package/src/Carousel/Carousel.tsx +8 -2
  100. package/src/Chat/ChatToolCalls.test.tsx +44 -0
  101. package/src/Chat/ChatToolCalls.tsx +36 -15
  102. package/src/CheckboxList/CheckboxList.doc.mjs +172 -31
  103. package/src/CheckboxList/CheckboxList.spec.md +214 -0
  104. package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
  105. package/src/CommandPalette/CommandPalette.spec.md +223 -0
  106. package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
  107. package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
  108. package/src/ContextMenu/ContextMenu.doc.mjs +61 -1
  109. package/src/ContextMenu/ContextMenu.spec.md +206 -0
  110. package/src/ContextMenu/ContextMenu.test.tsx +131 -1
  111. package/src/ContextMenu/ContextMenu.tsx +199 -32
  112. package/src/ContextMenu/index.ts +1 -0
  113. package/src/Divider/Divider.doc.mjs +24 -0
  114. package/src/Divider/Divider.spec.md +150 -0
  115. package/src/DropdownMenu/DropdownMenu.doc.mjs +305 -40
  116. package/src/DropdownMenu/DropdownMenu.spec.md +245 -0
  117. package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
  118. package/src/DropdownMenu/DropdownMenu.tsx +459 -28
  119. package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
  120. package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
  121. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
  122. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
  123. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
  124. package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
  125. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
  126. package/src/DropdownMenu/index.ts +2 -0
  127. package/src/DropdownMenu/menuWidth.ts +51 -0
  128. package/src/DropdownMenu/useMenuOverflow.ts +77 -0
  129. package/src/FieldStatus/FieldStatus.doc.mjs +21 -0
  130. package/src/FieldStatus/FieldStatus.spec.md +212 -0
  131. package/src/Grid/Grid.doc.mjs +19 -0
  132. package/src/Grid/Grid.spec.md +161 -0
  133. package/src/Icon/Icon.doc.mjs +196 -48
  134. package/src/Icon/Icon.spec.md +191 -0
  135. package/src/IconButton/IconButton.doc.mjs +38 -0
  136. package/src/Kbd/Kbd.doc.mjs +18 -0
  137. package/src/Kbd/Kbd.spec.md +141 -0
  138. package/src/Layout/Layout.doc.mjs +32 -0
  139. package/src/Layout/Layout.spec.md +199 -0
  140. package/src/Lightbox/Lightbox.doc.mjs +149 -33
  141. package/src/Lightbox/Lightbox.spec.md +197 -0
  142. package/src/Markdown/Markdown.doc.mjs +60 -0
  143. package/src/Markdown/Markdown.spec.md +200 -0
  144. package/src/MobileNav/MobileNav.doc.mjs +140 -32
  145. package/src/MobileNav/MobileNav.spec.md +208 -0
  146. package/src/MoreMenu/MoreMenu.doc.mjs +59 -0
  147. package/src/MoreMenu/MoreMenu.spec.md +221 -0
  148. package/src/MoreMenu/MoreMenu.test.tsx +47 -0
  149. package/src/MoreMenu/MoreMenu.tsx +13 -1
  150. package/src/MultiSelector/MultiSelector.doc.mjs +128 -0
  151. package/src/MultiSelector/MultiSelector.spec.md +266 -0
  152. package/src/MultiSelector/MultiSelector.test.tsx +127 -0
  153. package/src/MultiSelector/MultiSelector.tsx +121 -73
  154. package/src/MultiSelector/index.ts +1 -0
  155. package/src/NavIcon/NavIcon.doc.mjs +17 -0
  156. package/src/NavIcon/NavIcon.spec.md +133 -0
  157. package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
  158. package/src/NavMenu/NavMenu.doc.mjs +37 -0
  159. package/src/Outline/Outline.doc.mjs +35 -0
  160. package/src/Outline/Outline.spec.md +161 -0
  161. package/src/OverflowList/OverflowList.doc.mjs +24 -0
  162. package/src/OverflowList/OverflowList.spec.md +156 -0
  163. package/src/Pagination/Pagination.doc.mjs +74 -0
  164. package/src/Pagination/Pagination.spec.md +225 -0
  165. package/src/Popover/Popover.doc.mjs +11 -11
  166. package/src/Popover/Popover.test.tsx +397 -2
  167. package/src/Popover/Popover.tsx +228 -21
  168. package/src/Popover/usePopover.tsx +92 -15
  169. package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
  170. package/src/ProgressBar/ProgressBar.spec.md +260 -0
  171. package/src/Resizable/Resizable.doc.mjs +59 -16
  172. package/src/Resizable/Resizable.spec.md +194 -0
  173. package/src/Section/Section.doc.mjs +17 -0
  174. package/src/Section/Section.spec.md +138 -0
  175. package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
  176. package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
  177. package/src/Selector/Selector.doc.mjs +12 -0
  178. package/src/Selector/Selector.spec.md +202 -0
  179. package/src/Selector/Selector.test.tsx +135 -0
  180. package/src/Selector/Selector.tsx +141 -82
  181. package/src/Selector/SelectorBottomSheet.tsx +97 -0
  182. package/src/Selector/index.ts +1 -0
  183. package/src/Selector/selectorPresentation.stylex.ts +20 -0
  184. package/src/Selector/useSelectorPresentation.ts +147 -0
  185. package/src/Skeleton/Skeleton.doc.mjs +12 -0
  186. package/src/Skeleton/Skeleton.spec.md +126 -0
  187. package/src/Slider/Slider.doc.mjs +184 -30
  188. package/src/Slider/Slider.spec.md +223 -0
  189. package/src/Stack/Stack.doc.mjs +22 -0
  190. package/src/Stack/Stack.spec.md +154 -0
  191. package/src/StatusDot/StatusDot.doc.mjs +17 -0
  192. package/src/StatusDot/StatusDot.spec.md +137 -0
  193. package/src/Switch/Switch.doc.mjs +40 -0
  194. package/src/Switch/Switch.spec.md +174 -0
  195. package/src/Table/Table.doc.mjs +102 -24
  196. package/src/Table/Table.spec.md +283 -0
  197. package/src/Text/Text.doc.mjs +190 -43
  198. package/src/Text/Text.spec.md +205 -0
  199. package/src/TextArea/TextArea.doc.mjs +255 -45
  200. package/src/TextArea/TextArea.spec.md +204 -0
  201. package/src/Toast/ToastViewport.test.tsx +118 -4
  202. package/src/Toast/useToastGesture.ts +61 -8
  203. package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
  204. package/src/Toolbar/Toolbar.doc.mjs +19 -0
  205. package/src/Toolbar/Toolbar.spec.md +173 -0
  206. package/src/Tooltip/Tooltip.doc.mjs +17 -0
  207. package/src/Tooltip/Tooltip.spec.md +141 -0
  208. package/src/TreeList/TreeList.doc.mjs +58 -0
  209. package/src/TreeList/TreeList.spec.md +220 -0
  210. package/src/Typeahead/Typeahead.doc.mjs +83 -1
  211. package/src/Typeahead/Typeahead.spec.md +250 -0
  212. package/src/hooks/useAdaptivePresentation.ts +35 -0
  213. package/src/hooks/useFocusReturnVisibility.ts +45 -0
  214. package/src/hooks/useFocusTrap.test.tsx +40 -0
  215. package/src/hooks/useFocusTrap.ts +13 -0
  216. package/src/theme/themingTargets.test.ts +91 -25
@@ -1,18 +1,95 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
+ /** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
4
+ const anatomy = [
5
+ {
6
+ name: 'Label',
7
+ required: true,
8
+ description: 'Text identifying the multi-line field.',
9
+ },
10
+ {
11
+ name: 'Description',
12
+ required: false,
13
+ description: 'Helper text between the label and the input.',
14
+ },
15
+ {
16
+ name: 'Input container',
17
+ required: true,
18
+ description: 'Painted boundary containing the text area and its overlays.',
19
+ },
20
+ {
21
+ name: 'Text area',
22
+ required: true,
23
+ description: 'Multi-line control that displays and edits the value.',
24
+ },
25
+ {
26
+ name: 'Placeholder',
27
+ required: false,
28
+ description: 'Hint text shown inside the empty text area.',
29
+ },
30
+ {
31
+ name: 'Start icon',
32
+ required: false,
33
+ description:
34
+ 'Astryx Icon rendered at the start when startIcon is a semantic name or icon component.',
35
+ },
36
+ {
37
+ name: 'Custom start content',
38
+ required: false,
39
+ description:
40
+ 'Caller-provided ReactNode rendered at the start instead of an Astryx Icon.',
41
+ },
42
+ {
43
+ name: 'Spinner',
44
+ required: false,
45
+ description: 'Loading indicator shown at the end of the input container.',
46
+ },
47
+ {
48
+ name: 'Status icon',
49
+ required: false,
50
+ description: 'Error, warning, or success icon shown inside the input.',
51
+ },
52
+ {
53
+ name: 'Character counter',
54
+ required: false,
55
+ description:
56
+ 'Current and maximum character counts shown inside the input container.',
57
+ },
58
+ {
59
+ name: 'Field status message',
60
+ required: false,
61
+ description:
62
+ 'Attached or detached error, warning, or success message associated with the field.',
63
+ },
64
+ {
65
+ name: 'Tooltip status message',
66
+ required: false,
67
+ description:
68
+ 'Tooltip surface presenting the status message for the tooltip variant.',
69
+ },
70
+ ];
71
+
3
72
  /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
4
73
 
5
74
  export const docs = {
6
75
  name: 'TextArea',
7
76
  displayName: 'Text Area',
8
77
  category: 'Form Controls',
9
- keywords: ["textarea","textfield","multiline","comment","message","autoresize","autosize","charlimit"],
78
+ keywords: [
79
+ 'textarea',
80
+ 'textfield',
81
+ 'multiline',
82
+ 'comment',
83
+ 'message',
84
+ 'autoresize',
85
+ 'autosize',
86
+ 'charlimit',
87
+ ],
10
88
  props: [
11
89
  {
12
90
  name: 'ref',
13
91
  type: 'React.Ref<HTMLTextAreaElement>',
14
- description:
15
- 'Ref forwarded to the underlying <textarea> element.',
92
+ description: 'Ref forwarded to the underlying <textarea> element.',
16
93
  },
17
94
  {
18
95
  name: 'label',
@@ -147,7 +224,8 @@ export const docs = {
147
224
  {
148
225
  name: 'size',
149
226
  type: "'sm' | 'md' | 'lg'",
150
- description: 'Size of the textarea, affecting internal padding. Height is controlled by rows, not size.',
227
+ description:
228
+ 'Size of the textarea, affecting internal padding. Height is controlled by rows, not size.',
151
229
  default: "'md'",
152
230
  },
153
231
  {
@@ -186,12 +264,21 @@ export const docs = {
186
264
  ],
187
265
  theming: {
188
266
  targets: [
189
- {className: 'astryx-text-area', visualProps: ['size', 'status'], states: ['disabled', 'readonly']},
267
+ {
268
+ className: 'astryx-text-area',
269
+ visualProps: ['size', 'status'],
270
+ states: ['disabled', 'readonly'],
271
+ },
190
272
  {className: 'astryx-text-area-control'},
191
273
  {className: 'astryx-text-area-counter'},
192
274
  // Still emitted beside the names above, so themes written against
193
275
  // them keep working. Drop in the next major.
194
- {className: 'astryx-textarea', visualProps: ['size', 'status'], states: ['disabled', 'readonly'], deprecatedFor: 'text-area'},
276
+ {
277
+ className: 'astryx-textarea',
278
+ visualProps: ['size', 'status'],
279
+ states: ['disabled', 'readonly'],
280
+ deprecatedFor: 'text-area',
281
+ },
195
282
  ],
196
283
  vars: [
197
284
  {
@@ -203,21 +290,58 @@ export const docs = {
203
290
  },
204
291
  ],
205
292
  derived: [
206
- {property: 'paddingInline', vars: ['--_textarea-inline-padding'], replaces: true},
293
+ {
294
+ property: 'paddingInline',
295
+ vars: ['--_textarea-inline-padding'],
296
+ replaces: true,
297
+ },
207
298
  ],
208
299
  },
209
300
  usage: {
301
+ anatomy,
210
302
  description:
211
303
  'TextArea is a multi-line text input for collecting longer-form content like comments, descriptions, or messages. Use it when the expected input spans multiple lines. For shorter, single-line values, use TextInput.',
212
304
  bestPractices: [
213
- { guidance: true, description: 'Provide a visible label so users know what to enter. If the label must be hidden, set isLabelHidden with a descriptive label for screen readers.' },
214
- { guidance: true, description: 'Set maxLength with a character counter when there is a defined limit; it helps users stay within bounds before they submit.' },
215
- { guidance: true, description: 'Use the status prop to surface validation feedback inline: show success when input is valid, warning for soft limits, and error for hard failures.' },
216
- { guidance: true, description: 'Add a description or placeholder to clarify expected content, like "Describe the issue in detail," but never rely on placeholder alone as the only label.' },
217
- { guidance: false, description: 'Avoid using TextArea for short, single-line values like names or emails; use TextInput instead.' },
218
- { guidance: false, description: 'Don\'t rely solely on placeholder text to communicate the purpose of the field; placeholders disappear on focus and are not accessible labels.' },
219
- { guidance: false, description: 'Don\'t show a status message without also setting the status type; the colored border and icon are what draw the user\'s attention to the message.' },
220
- { guidance: false, description: 'Don\'t wrap a disabled TextArea in Tooltip to explain why it\'s disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.' },
305
+ {
306
+ guidance: true,
307
+ description:
308
+ 'Provide a visible label so users know what to enter. If the label must be hidden, set isLabelHidden with a descriptive label for screen readers.',
309
+ },
310
+ {
311
+ guidance: true,
312
+ description:
313
+ 'Set maxLength with a character counter when there is a defined limit; it helps users stay within bounds before they submit.',
314
+ },
315
+ {
316
+ guidance: true,
317
+ description:
318
+ 'Use the status prop to surface validation feedback inline: show success when input is valid, warning for soft limits, and error for hard failures.',
319
+ },
320
+ {
321
+ guidance: true,
322
+ description:
323
+ 'Add a description or placeholder to clarify expected content, like "Describe the issue in detail," but never rely on placeholder alone as the only label.',
324
+ },
325
+ {
326
+ guidance: false,
327
+ description:
328
+ 'Avoid using TextArea for short, single-line values like names or emails; use TextInput instead.',
329
+ },
330
+ {
331
+ guidance: false,
332
+ description:
333
+ "Don't rely solely on placeholder text to communicate the purpose of the field; placeholders disappear on focus and are not accessible labels.",
334
+ },
335
+ {
336
+ guidance: false,
337
+ description:
338
+ "Don't show a status message without also setting the status type; the colored border and icon are what draw the user's attention to the message.",
339
+ },
340
+ {
341
+ guidance: false,
342
+ description:
343
+ "Don't wrap a disabled TextArea in Tooltip to explain why it's disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.",
344
+ },
221
345
  ],
222
346
  },
223
347
  };
@@ -252,7 +376,8 @@ export const docsZh = {
252
376
  {
253
377
  name: 'changeAction',
254
378
  type: '(value: string, e: ChangeEvent<HTMLTextAreaElement>) => void | Promise<void>',
255
- description: '在 React transition 内于 onChange 之后触发的异步操作。通过 useOptimistic 启用乐观更新。',
379
+ description:
380
+ '在 React transition 内于 onChange 之后触发的异步操作。通过 useOptimistic 启用乐观更新。',
256
381
  },
257
382
  {
258
383
  name: 'isLabelHidden',
@@ -274,7 +399,8 @@ export const docsZh = {
274
399
  {
275
400
  name: 'isRequired',
276
401
  type: 'boolean',
277
- description: '在标签旁显示"必填"指示器并设置 aria-required。与 isOptional 互斥。',
402
+ description:
403
+ '在标签旁显示"必填"指示器并设置 aria-required。与 isOptional 互斥。',
278
404
  default: 'false',
279
405
  },
280
406
  {
@@ -316,12 +442,14 @@ export const docsZh = {
316
442
  {
317
443
  name: 'maxLength',
318
444
  type: 'number',
319
- description: '允许的最大字符数。设置后,在文本域下方显示字符计数器(当前/最大)。不原生强制限制:超出时计数器显示错误样式。',
445
+ description:
446
+ '允许的最大字符数。设置后,在文本域下方显示字符计数器(当前/最大)。不原生强制限制:超出时计数器显示错误样式。',
320
447
  },
321
448
  {
322
449
  name: 'status',
323
450
  type: "{ type: 'warning' | 'error' | 'success'; message?: string }",
324
- description: '应用彩色边框和图标的状态指示器。可选消息显示在文本域下方的浮动框中。',
451
+ description:
452
+ '应用彩色边框和图标的状态指示器。可选消息显示在文本域下方的浮动框中。',
325
453
  },
326
454
  {
327
455
  name: 'statusVariant',
@@ -381,17 +509,27 @@ export const docsZh = {
381
509
  {
382
510
  name: 'xstyle',
383
511
  type: 'StyleXStyles',
384
- description: 'StyleX 样式,用于布局自定义(边距、定位、尺寸)。必须是 stylex.create() 的值,而非内联样式对象。',
512
+ description:
513
+ 'StyleX 样式,用于布局自定义(边距、定位、尺寸)。必须是 stylex.create() 的值,而非内联样式对象。',
385
514
  },
386
515
  ],
387
516
  theming: {
388
517
  targets: [
389
- {className: 'astryx-text-area', visualProps: ['size', 'status'], states: ['disabled', 'readonly']},
518
+ {
519
+ className: 'astryx-text-area',
520
+ visualProps: ['size', 'status'],
521
+ states: ['disabled', 'readonly'],
522
+ },
390
523
  {className: 'astryx-text-area-control'},
391
524
  {className: 'astryx-text-area-counter'},
392
525
  // Still emitted beside the names above, so themes written against
393
526
  // them keep working. Drop in the next major.
394
- {className: 'astryx-textarea', visualProps: ['size', 'status'], states: ['disabled', 'readonly'], deprecatedFor: 'text-area'},
527
+ {
528
+ className: 'astryx-textarea',
529
+ visualProps: ['size', 'status'],
530
+ states: ['disabled', 'readonly'],
531
+ deprecatedFor: 'text-area',
532
+ },
395
533
  ],
396
534
  vars: [
397
535
  {
@@ -403,21 +541,57 @@ export const docsZh = {
403
541
  },
404
542
  ],
405
543
  derived: [
406
- {property: 'paddingInline', vars: ['--_textarea-inline-padding'], replaces: true},
544
+ {
545
+ property: 'paddingInline',
546
+ vars: ['--_textarea-inline-padding'],
547
+ replaces: true,
548
+ },
407
549
  ],
408
550
  },
409
551
  usage: {
410
552
  description:
411
553
  'TextArea is a multi-line text input for collecting longer-form content like comments, descriptions, or messages. Use it when the expected input spans multiple lines. For shorter, single-line values, use TextInput.',
412
554
  bestPractices: [
413
- { guidance: true, description: 'Provide a visible label so users know what to enter. If the label must be hidden, set isLabelHidden with a descriptive label for screen readers.' },
414
- { guidance: true, description: 'Set maxLength with a character counter when there is a defined limit; it helps users stay within bounds before they submit.' },
415
- { guidance: true, description: 'Use the status prop to surface validation feedback inline: show success when input is valid, warning for soft limits, and error for hard failures.' },
416
- { guidance: true, description: 'Add a description or placeholder to clarify expected content, like "Describe the issue in detail," but never rely on placeholder alone as the only label.' },
417
- { guidance: false, description: 'Avoid using TextArea for short, single-line values like names or emails; use TextInput instead.' },
418
- { guidance: false, description: 'Don\'t rely solely on placeholder text to communicate the purpose of the field; placeholders disappear on focus and are not accessible labels.' },
419
- { guidance: false, description: 'Don\'t show a status message without also setting the status type; the colored border and icon are what draw the user\'s attention to the message.' },
420
- { guidance: false, description: 'Don\'t wrap a disabled TextArea in Tooltip to explain why it\'s disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.' },
555
+ {
556
+ guidance: true,
557
+ description:
558
+ 'Provide a visible label so users know what to enter. If the label must be hidden, set isLabelHidden with a descriptive label for screen readers.',
559
+ },
560
+ {
561
+ guidance: true,
562
+ description:
563
+ 'Set maxLength with a character counter when there is a defined limit; it helps users stay within bounds before they submit.',
564
+ },
565
+ {
566
+ guidance: true,
567
+ description:
568
+ 'Use the status prop to surface validation feedback inline: show success when input is valid, warning for soft limits, and error for hard failures.',
569
+ },
570
+ {
571
+ guidance: true,
572
+ description:
573
+ 'Add a description or placeholder to clarify expected content, like "Describe the issue in detail," but never rely on placeholder alone as the only label.',
574
+ },
575
+ {
576
+ guidance: false,
577
+ description:
578
+ 'Avoid using TextArea for short, single-line values like names or emails; use TextInput instead.',
579
+ },
580
+ {
581
+ guidance: false,
582
+ description:
583
+ "Don't rely solely on placeholder text to communicate the purpose of the field; placeholders disappear on focus and are not accessible labels.",
584
+ },
585
+ {
586
+ guidance: false,
587
+ description:
588
+ "Don't show a status message without also setting the status type; the colored border and icon are what draw the user's attention to the message.",
589
+ },
590
+ {
591
+ guidance: false,
592
+ description:
593
+ "Don't wrap a disabled TextArea in Tooltip to explain why it's disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.",
594
+ },
421
595
  ],
422
596
  },
423
597
  };
@@ -429,14 +603,44 @@ export const docsDense = {
429
603
  description:
430
604
  'Multi-line input for comments, descriptions, messages. Use when input spans multiple lines; use TextInput for single-line.',
431
605
  bestPractices: [
432
- { guidance: true, description: 'Visible label or isLabelHidden with descriptive label for screen readers.' },
433
- { guidance: true, description: 'Set maxLength for character counter when a limit exists.' },
434
- { guidance: true, description: 'Use status prop for inline validation: success, warning, error.' },
435
- { guidance: true, description: 'Add description or placeholder for context; never placeholder alone as label.' },
436
- { guidance: false, description: 'Avoid TextArea for single-line values; use TextInput.' },
437
- { guidance: false, description: 'Don\'t use placeholder as only label; disappears on focus, not accessible.' },
438
- { guidance: false, description: 'Don\'t show status message without status type; border and icon draw attention.' },
439
- { guidance: false, description: 'Don\'t wrap a disabled TextArea in Tooltip to explain the disabled state; use the disabledMessage prop instead.' },
606
+ {
607
+ guidance: true,
608
+ description:
609
+ 'Visible label or isLabelHidden with descriptive label for screen readers.',
610
+ },
611
+ {
612
+ guidance: true,
613
+ description: 'Set maxLength for character counter when a limit exists.',
614
+ },
615
+ {
616
+ guidance: true,
617
+ description:
618
+ 'Use status prop for inline validation: success, warning, error.',
619
+ },
620
+ {
621
+ guidance: true,
622
+ description:
623
+ 'Add description or placeholder for context; never placeholder alone as label.',
624
+ },
625
+ {
626
+ guidance: false,
627
+ description: 'Avoid TextArea for single-line values; use TextInput.',
628
+ },
629
+ {
630
+ guidance: false,
631
+ description:
632
+ "Don't use placeholder as only label; disappears on focus, not accessible.",
633
+ },
634
+ {
635
+ guidance: false,
636
+ description:
637
+ "Don't show status message without status type; border and icon draw attention.",
638
+ },
639
+ {
640
+ guidance: false,
641
+ description:
642
+ "Don't wrap a disabled TextArea in Tooltip to explain the disabled state; use the disabledMessage prop instead.",
643
+ },
440
644
  ],
441
645
  },
442
646
  propDescriptions: {
@@ -444,11 +648,13 @@ export const docsDense = {
444
648
  label: 'Label text for textarea; always rendered for a11y.',
445
649
  value: 'Current textarea value.',
446
650
  onChange: 'Fired on textarea value change.',
447
- changeAction: 'Async action after onChange in React transition. Enables useOptimistic.',
651
+ changeAction:
652
+ 'Async action after onChange in React transition. Enables useOptimistic.',
448
653
  isLabelHidden: 'Visually hides label; keeps screen reader access.',
449
654
  description: 'Helper text between label+textarea.',
450
655
  isOptional: 'Shows "Optional" indicator. Mutually exclusive w/ isRequired.',
451
- isRequired: 'Shows "Required" indicator+sets aria-required. Mutually exclusive w/ isOptional.',
656
+ isRequired:
657
+ 'Shows "Required" indicator+sets aria-required. Mutually exclusive w/ isOptional.',
452
658
  isDisabled: 'Disables textarea, prevents interaction.',
453
659
  isReadOnly:
454
660
  'Read-only: value visible + still submits, but not editable. Unlike isDisabled: not dimmed, stays in tab order.',
@@ -457,9 +663,12 @@ export const docsDense = {
457
663
  isLoading: 'Loading state w/ spinner inside input.',
458
664
  placeholder: 'Placeholder when textarea empty.',
459
665
  rows: 'Visible text rows.',
460
- maxLength: 'Max chars allowed. Shows counter (current/max) inside the container, bottom-right beneath the text. No native enforcement; over-limit shows red + a warning icon and is announced to screen readers.',
461
- status: 'Colored border+icon status. Optional floating message below textarea.',
462
- statusVariant: 'How status message is placed: attached overlaps below input; detached floats below w/ spacing; tooltip hides the box and shows it on the status icon.',
666
+ maxLength:
667
+ 'Max chars allowed. Shows counter (current/max) inside the container, bottom-right beneath the text. No native enforcement; over-limit shows red + a warning icon and is announced to screen readers.',
668
+ status:
669
+ 'Colored border+icon status. Optional floating message below textarea.',
670
+ statusVariant:
671
+ 'How status message is placed: attached overlaps below input; detached floats below w/ spacing; tooltip hides the box and shows it on the status icon.',
463
672
  labelTooltip: 'Tooltip in info icon at label end.',
464
673
  startIcon: 'Icon inside leading edge of textarea wrapper.',
465
674
  hasSpellCheck: 'Enables/disables browser spell checking.',
@@ -469,6 +678,7 @@ export const docsDense = {
469
678
  htmlName: 'HTML name attr for form submissions.',
470
679
  onFocus: 'Callback on focus.',
471
680
  onBlur: 'Callback on blur.',
472
- xstyle: 'StyleX styles for layout customization. Must be stylex.create() value, not inline style.',
681
+ xstyle:
682
+ 'StyleX styles for layout customization. Must be stylex.create() value, not inline style.',
473
683
  },
474
684
  };
@@ -0,0 +1,204 @@
1
+ ---
2
+ schema_version: 1
3
+ template_version: 3
4
+ kind: component
5
+ id: component:TextArea
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [theming]
13
+ verified_by:
14
+ [
15
+ packages/core/src/TextArea/TextArea.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ scripts/check-knowledge.mjs,
18
+ ]
19
+ families: []
20
+ design_specs: []
21
+ architecture: [architecture:component-theming-surface]
22
+ contributing: []
23
+ system_specs: []
24
+ ---
25
+
26
+ # TextArea component contract
27
+
28
+ ## Intent
29
+
30
+ TextArea presents a labeled multi-line text field. This draft records its current
31
+ consumer anatomy and the theming ownership of parts rendered by TextArea, Field,
32
+ FieldStatus, and Spinner. It changes no runtime behavior or public API.
33
+
34
+ ## Compatibility and migration
35
+
36
+ - Released default preserved: `yes`
37
+ - Compatibility class: additive documentation only; runtime, DOM, styling,
38
+ targets, aliases, and public API remain unchanged
39
+ - Controlled/uncontrolled behavior: unchanged; TextArea remains controlled
40
+ - Migration decision: none
41
+
42
+ Consumer migration instructions belong in consumer docs and release notes.
43
+
44
+ ## Ownership boundary
45
+
46
+ **Owns**
47
+
48
+ - The painted input container, native text area, and character counter represented
49
+ by the existing `text-area`, `text-area-control`, and `text-area-counter`
50
+ targets.
51
+
52
+ **Does not own / non-goals**
53
+
54
+ - Label and attached/detached validation-message presentation — owned by
55
+ `component:Field` and `component:FieldStatus`.
56
+ - The tooltip-variant status surface — owned by `component:Tooltip`.
57
+ - Standard start-icon presentation and the shared on-field status icon — owned
58
+ by `component:Icon` and `component:Field`, respectively.
59
+ - Loading-indicator presentation — owned by `component:Spinner`.
60
+ - Caller-provided custom start content outside Icon's supported source forms.
61
+ - The deprecated `textarea` alias as separate anatomy.
62
+
63
+ ## Public concepts
64
+
65
+ No new public concept is introduced. Consumer props, states, and usage remain
66
+ documented in `TextArea.doc.mjs`.
67
+
68
+ ## Behavioral and layout contract
69
+
70
+ | ID | Candidate invariant | Basis | Draft review state |
71
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------------- |
72
+ | FR1 | The current render places the native text area inside its painted input container. | Current source, docs, and focused tests | Verified current behavior; no new behavior decided |
73
+ | FR2 | The input container, native control, and conditional character counter carry the three current local targets. | Current source and public docs | Verified current behavior; no target change |
74
+ | FR3 | Label, standard start icon, loading indicator, status icon, and status surfaces continue to use their shared Field, Icon, Spinner, FieldStatus, and Tooltip owners. | Current source | Verified conditional composition boundary |
75
+ | FR4 | Placeholder text remains part of the native control; the over-limit glyph remains part of the character counter. | Current source, docs, and focused tests | Verified current grouping |
76
+
77
+ ### Allowed variation
78
+
79
+ - Value, placeholder, row count, size, status, disabled/read-only state, and
80
+ optional slots remain current capabilities rather than separate target names.
81
+ - Start-icon content may be an Icon-supported value or caller-supplied ReactNode.
82
+
83
+ ### Representative states
84
+
85
+ | State | Required invariant | Allowed variation |
86
+ | -------------------- | ---------------------------------------------------------------------- | ----------------------------------- |
87
+ | Default editable | Label, input container, and native text area render. | Value, placeholder, rows, and size |
88
+ | With character limit | Character counter renders inside the input container. | Count and over-limit state |
89
+ | Busy or status | Shared Spinner or status presentation renders in its current location. | Status variant and message presence |
90
+
91
+ ### Transformation and precedence order
92
+
93
+ - No new value, layout, status, or styling precedence rule is introduced.
94
+
95
+ ### Performance and resources
96
+
97
+ - No new performance or resource rule is introduced.
98
+
99
+ ## Accessibility contract
100
+
101
+ This draft does not change or extend TextArea's existing label, description,
102
+ status, counter, busy state, disabled/read-only state, or announcement behavior.
103
+
104
+ ## Design relationships
105
+
106
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
107
+ | --------------------- | -------------------------------------------------------------------- | ------------------------------- | -------------- | ------------------ |
108
+ | Input container | Presents the current painted field boundary. | Current source and public docs | Supporting | FR1, FR2 |
109
+ | Text area | Presents and edits the current multi-line value. | Current source and public docs | Prominent | FR1, FR2 |
110
+ | Character counter | Presents current and maximum character counts. | Current source and public docs | Supporting | FR2, FR4 |
111
+ | Shared field feedback | Presents current label, standard icons, status, and loading content. | Current shared-component source | Supporting | FR3 |
112
+
113
+ ### Theming anatomy
114
+
115
+ <!-- anatomy-theming:v1 -->
116
+
117
+ ```json
118
+ {
119
+ "Label": {
120
+ "delegatesTo": {"owner": "component:Field", "target": "field-label"}
121
+ },
122
+ "Description": {
123
+ "none": {
124
+ "reason": "unsettled: No current public target reaches the stable Description; future exposure still needs an owner decision"
125
+ }
126
+ },
127
+ "Input container": {"target": "text-area"},
128
+ "Text area": {"target": "text-area-control"},
129
+ "Placeholder": {"inherits": "text-area-control"},
130
+ "Start icon": {
131
+ "delegatesTo": {"owner": "component:Icon", "target": "icon"}
132
+ },
133
+ "Custom start content": {
134
+ "none": {
135
+ "reason": "intentional: Custom start content is caller-provided ReactNode content outside TextArea's public theming ownership"
136
+ }
137
+ },
138
+ "Spinner": {
139
+ "delegatesTo": {"owner": "component:Spinner", "target": "spinner"}
140
+ },
141
+ "Status icon": {
142
+ "delegatesTo": {
143
+ "owner": "component:Field",
144
+ "target": "input-status-icon"
145
+ }
146
+ },
147
+ "Character counter": {"target": "text-area-counter"},
148
+ "Field status message": {
149
+ "delegatesTo": {
150
+ "owner": "component:FieldStatus",
151
+ "target": "field-status"
152
+ }
153
+ },
154
+ "Tooltip status message": {
155
+ "delegatesTo": {"owner": "component:Tooltip", "target": "tooltip"}
156
+ }
157
+ }
158
+ ```
159
+
160
+ `Description` remains stable consumer anatomy, but no current public target
161
+ reaches it; future exposure remains unsettled. Semantic names and icon component
162
+ values render through Icon and delegate to its `icon` target; arbitrary ReactNode
163
+ start content is caller-provided and intentionally stays outside TextArea's
164
+ public theming ownership. Attached and detached status messages delegate to
165
+ FieldStatus, while the tooltip variant delegates its rendered surface to Tooltip.
166
+ The deprecated `textarea` alias is compatibility evidence only and does not
167
+ appear in the map.
168
+
169
+ ## Family and system relationships
170
+
171
+ - `architecture:component-theming-surface` owns anatomy qualification, factual
172
+ `none` dispositions, composition-preserving ownership, and alias exclusion.
173
+ - Field, FieldStatus, Icon, Spinner, and Tooltip retain their existing public
174
+ target contracts when composed by TextArea.
175
+
176
+ ## Verification map
177
+
178
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
179
+ | ------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
180
+ | FR1, FR4 | `TextArea.test.tsx` structure and counter suites | Default, placeholder, counter, over limit | Removing or regrouping stable parts breaks existing role, content, or DOM assertions. | `audit:TextArea/anatomy` |
181
+ | FR2 | `TextArea.test.tsx` root-target suite and `themingTargets.test.ts` | Current and deprecated root names; current targets | Removing root compatibility classes fails focused coverage; source/docs drift fails the target guard. | `audit:TextArea/theming` |
182
+ | FR3 | `TextArea.test.tsx`, `useInputStatusIcon.test.tsx`, and source inspection of `renderIconSlot` | Standard/custom start content; Spinner; attached/detached/tooltip status | Removing or rerouting composed content breaks existing icon, spinner, tooltip, message, or association assertions. | `audit:TextArea/anatomy` |
183
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current local targets | Canonical-key drift, invalid dispositions or target spelling, alias-backed local claims, or an unclaimed current local target fails validation. | `audit:TextArea/theming` |
184
+
185
+ The focused TextArea suite pins the current and deprecated root classes, but does
186
+ not separately assert the exact `text-area-control` or `text-area-counter` class
187
+ placement. The source/metadata target guard covers those local declarations. No
188
+ current repository check resolves `delegatesTo` owner/target pairs; the pairs in
189
+ this draft were verified manually, so semantic delegation drift remains a
190
+ validation gap.
191
+
192
+ ## Decision log
193
+
194
+ None. This draft records current facts and introduces no component-local design
195
+ or API decision.
196
+
197
+ ## Open questions
198
+
199
+ None.
200
+
201
+ ## Content boundary
202
+
203
+ This file does not duplicate consumer prop tables, examples, implementation
204
+ steps, or shared-component contracts. It links to their owners.