@commercetools/nimbus-mcp 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/README.md +9 -1
  2. package/data/docs/route-manifest.json +1608 -143
  3. package/data/docs/routes/components-buttons-button.json +25 -2
  4. package/data/docs/routes/components-chat-chat-message-list.json +448 -0
  5. package/data/docs/routes/components-chat-chat-message.json +580 -0
  6. package/data/docs/routes/components-content-markdown.json +548 -0
  7. package/data/docs/routes/components-data-display-item-group.json +413 -0
  8. package/data/docs/routes/components-data-display-item.json +611 -0
  9. package/data/docs/routes/components-feedback-activity-indicator.json +396 -0
  10. package/data/docs/routes/components-feedback-skeleton.json +481 -0
  11. package/data/docs/routes/components-feedback-skeletoncircle.json +429 -0
  12. package/data/docs/routes/components-feedback-skeletontext.json +417 -0
  13. package/data/docs/routes/components-feedback-tooltip.json +23 -1
  14. package/data/docs/routes/components-inputs-drop-zone.json +562 -0
  15. package/data/docs/routes/components-inputs-file-trigger.json +421 -0
  16. package/data/docs/routes/components-inputs-range-slider.json +633 -0
  17. package/data/docs/routes/components-inputs-slider.json +611 -0
  18. package/data/docs/routes/components-layout-splitter.json +3 -3
  19. package/data/docs/routes/components-media-avatar.json +16 -5
  20. package/data/docs/routes/components-navigation-breadcrumbs.json +565 -0
  21. package/data/docs/routes/components-navigation-menu.json +1 -1
  22. package/data/docs/routes/components-navigation-tabnav.json +80 -14
  23. package/data/docs/routes/components-navigation-tabs.json +44 -11
  24. package/data/docs/routes/components-navigation-tree.json +445 -0
  25. package/data/docs/routes/home-getting-started-mcp-server-overview.json +2 -2
  26. package/data/docs/routes/home-getting-started-mcp-server-setup.json +2 -2
  27. package/data/docs/routes/icons.json +3 -3
  28. package/data/docs/routes/patterns-buttons-floating-action-button.json +392 -0
  29. package/data/docs/routes/patterns-buttons.json +58 -0
  30. package/data/docs/search-index.json +1 -1
  31. package/data/docs/types/AccordionContent.json +24 -24
  32. package/data/docs/types/AccordionHeader.json +88 -88
  33. package/data/docs/types/AccordionItem.json +20 -20
  34. package/data/docs/types/AccordionRoot.json +14 -14
  35. package/data/docs/types/ActivityIndicator.json +202 -0
  36. package/data/docs/types/AlertDescription.json +8 -8
  37. package/data/docs/types/AlertDismissButton.json +98 -81
  38. package/data/docs/types/AlertTitle.json +8 -8
  39. package/data/docs/types/Avatar.json +19 -2
  40. package/data/docs/types/Badge.json +2 -2
  41. package/data/docs/types/Body.json +6 -6
  42. package/data/docs/types/Box.json +6 -6
  43. package/data/docs/types/Breadcrumbs.json +12 -0
  44. package/data/docs/types/BreadcrumbsItem.json +296 -0
  45. package/data/docs/types/BreadcrumbsItemProps.json +9 -0
  46. package/data/docs/types/BreadcrumbsProps.json +9 -0
  47. package/data/docs/types/BreadcrumbsRoot.json +110 -0
  48. package/data/docs/types/Button.json +106 -89
  49. package/data/docs/types/Calendar.json +56 -56
  50. package/data/docs/types/Caption.json +6 -6
  51. package/data/docs/types/CardRoot.json +2 -2
  52. package/data/docs/types/Cell.json +6 -6
  53. package/data/docs/types/ChatMessage.json +12 -0
  54. package/data/docs/types/ChatMessageActions.json +42 -0
  55. package/data/docs/types/ChatMessageAvatar.json +140 -0
  56. package/data/docs/types/ChatMessageBody.json +42 -0
  57. package/data/docs/types/ChatMessageList.json +12 -0
  58. package/data/docs/types/ChatMessageListItem.json +42 -0
  59. package/data/docs/types/ChatMessageListRoot.json +78 -0
  60. package/data/docs/types/ChatMessageMeta.json +42 -0
  61. package/data/docs/types/ChatMessageRoot.json +119 -0
  62. package/data/docs/types/ChatMessageTyping.json +42 -0
  63. package/data/docs/types/Checkbox.json +86 -86
  64. package/data/docs/types/Code.json +11 -11
  65. package/data/docs/types/Column.json +6 -6
  66. package/data/docs/types/ColumnGroup.json +6 -6
  67. package/data/docs/types/ColumnHeader.json +6 -6
  68. package/data/docs/types/ComboBoxListBox.json +70 -70
  69. package/data/docs/types/ComboBoxOption.json +64 -64
  70. package/data/docs/types/ComboBoxPopover.json +91 -70
  71. package/data/docs/types/ComboBoxRoot.json +8 -8
  72. package/data/docs/types/ComboBoxSection.json +24 -24
  73. package/data/docs/types/ComboBoxTrigger.json +6 -6
  74. package/data/docs/types/DataTable.json +2 -2
  75. package/data/docs/types/DataTableBody.json +20 -20
  76. package/data/docs/types/DataTableHeader.json +25 -25
  77. package/data/docs/types/DataTableRoot.json +2 -2
  78. package/data/docs/types/DataTableTable.json +6 -6
  79. package/data/docs/types/DateInput.json +73 -73
  80. package/data/docs/types/DatePicker.json +58 -58
  81. package/data/docs/types/DateRangePicker.json +86 -86
  82. package/data/docs/types/DateRangePickerField.json +86 -86
  83. package/data/docs/types/DefaultPageRoot.json +2 -2
  84. package/data/docs/types/DialogCloseTrigger.json +96 -79
  85. package/data/docs/types/DraggableListField.json +88 -60
  86. package/data/docs/types/DraggableListItem.json +52 -52
  87. package/data/docs/types/DraggableListRoot.json +88 -60
  88. package/data/docs/types/DrawerCloseTrigger.json +96 -79
  89. package/data/docs/types/DropZone.json +161 -0
  90. package/data/docs/types/FieldErrors.json +2 -2
  91. package/data/docs/types/FileTrigger.json +130 -0
  92. package/data/docs/types/Flex.json +22 -22
  93. package/data/docs/types/FloatingActionButton.json +1036 -0
  94. package/data/docs/types/Footer.json +6 -6
  95. package/data/docs/types/FormActionBar.json +1 -1
  96. package/data/docs/types/FormFieldRoot.json +2 -2
  97. package/data/docs/types/Grid.json +24 -24
  98. package/data/docs/types/Group.json +12 -12
  99. package/data/docs/types/Header.json +6 -6
  100. package/data/docs/types/Heading.json +8 -8
  101. package/data/docs/types/Icon.json +4 -4
  102. package/data/docs/types/IconButton.json +106 -89
  103. package/data/docs/types/IconToggleButton.json +71 -71
  104. package/data/docs/types/Image.json +43 -43
  105. package/data/docs/types/Indicator.json +6 -6
  106. package/data/docs/types/InlineSvg.json +2 -2
  107. package/data/docs/types/Item.json +6 -6
  108. package/data/docs/types/ItemActions.json +27 -0
  109. package/data/docs/types/ItemContent.json +27 -0
  110. package/data/docs/types/ItemDescription.json +27 -0
  111. package/data/docs/types/ItemFooter.json +27 -0
  112. package/data/docs/types/ItemGroup.json +12 -0
  113. package/data/docs/types/ItemGroupRoot.json +27 -0
  114. package/data/docs/types/ItemGroupSeparator.json +27 -0
  115. package/data/docs/types/ItemHeader.json +27 -0
  116. package/data/docs/types/ItemMedia.json +56 -0
  117. package/data/docs/types/ItemRoot.json +239 -0
  118. package/data/docs/types/ItemTitle.json +27 -0
  119. package/data/docs/types/Kbd.json +8 -8
  120. package/data/docs/types/Link.json +14 -14
  121. package/data/docs/types/ListIndicator.json +6 -6
  122. package/data/docs/types/ListItem.json +6 -6
  123. package/data/docs/types/ListRoot.json +11 -11
  124. package/data/docs/types/LoadingSpinner.json +2 -2
  125. package/data/docs/types/MakeElementFocusable.json +18 -18
  126. package/data/docs/types/Markdown.json +183 -0
  127. package/data/docs/types/MenuItem.json +62 -62
  128. package/data/docs/types/MenuRoot.json +55 -55
  129. package/data/docs/types/MenuSection.json +30 -30
  130. package/data/docs/types/MenuSubmenuTrigger.json +4 -4
  131. package/data/docs/types/MenuTrigger.json +88 -88
  132. package/data/docs/types/MultilineTextInput.json +104 -104
  133. package/data/docs/types/MultilineTextInputField.json +102 -102
  134. package/data/docs/types/NumberInput.json +86 -86
  135. package/data/docs/types/NumberInputField.json +82 -82
  136. package/data/docs/types/PageContentColumn.json +6 -6
  137. package/data/docs/types/PageContentRoot.json +6 -6
  138. package/data/docs/types/PasswordInput.json +100 -100
  139. package/data/docs/types/PasswordInputField.json +100 -100
  140. package/data/docs/types/ProgressBar.json +13 -13
  141. package/data/docs/types/RadioInputOption.json +56 -56
  142. package/data/docs/types/RadioInputRoot.json +48 -48
  143. package/data/docs/types/RangeCalendar.json +58 -58
  144. package/data/docs/types/RangeSlider.json +337 -0
  145. package/data/docs/types/RangeSliderProps.json +9 -0
  146. package/data/docs/types/Region.json +6 -6
  147. package/data/docs/types/RegionTarget.json +6 -6
  148. package/data/docs/types/RichTextInput.json +2 -2
  149. package/data/docs/types/Root.json +11 -11
  150. package/data/docs/types/Row.json +6 -6
  151. package/data/docs/types/ScrollArea.json +2 -2
  152. package/data/docs/types/SearchInput.json +106 -106
  153. package/data/docs/types/SearchInputField.json +102 -102
  154. package/data/docs/types/SelectOption.json +54 -54
  155. package/data/docs/types/SelectOptionGroup.json +18 -18
  156. package/data/docs/types/SelectOptions.json +64 -64
  157. package/data/docs/types/SelectRoot.json +92 -92
  158. package/data/docs/types/Separator.json +4 -4
  159. package/data/docs/types/SimpleGrid.json +28 -28
  160. package/data/docs/types/Skeleton.json +129 -0
  161. package/data/docs/types/SkeletonCircle.json +103 -0
  162. package/data/docs/types/SkeletonCircleProps.json +9 -0
  163. package/data/docs/types/SkeletonProps.json +9 -0
  164. package/data/docs/types/SkeletonText.json +62 -0
  165. package/data/docs/types/SkeletonTextProps.json +9 -0
  166. package/data/docs/types/Slider.json +337 -0
  167. package/data/docs/types/SlidingIndicatorGeometry.json +9 -0
  168. package/data/docs/types/SlidingIndicatorRects.json +9 -0
  169. package/data/docs/types/SplitButton.json +16 -20
  170. package/data/docs/types/Stack.json +2 -2
  171. package/data/docs/types/StepsRoot.json +2 -2
  172. package/data/docs/types/Switch.json +34 -34
  173. package/data/docs/types/TabNavItem.json +5 -5
  174. package/data/docs/types/TabNavRoot.json +5 -5
  175. package/data/docs/types/TableBody.json +6 -6
  176. package/data/docs/types/TableCaption.json +6 -6
  177. package/data/docs/types/TableCell.json +6 -6
  178. package/data/docs/types/TableColumn.json +6 -6
  179. package/data/docs/types/TableColumnGroup.json +6 -6
  180. package/data/docs/types/TableColumnHeader.json +6 -6
  181. package/data/docs/types/TableFooter.json +6 -6
  182. package/data/docs/types/TableHeader.json +6 -6
  183. package/data/docs/types/TableRoot.json +19 -19
  184. package/data/docs/types/TableRow.json +6 -6
  185. package/data/docs/types/TableScrollArea.json +6 -6
  186. package/data/docs/types/TabsList.json +2 -2
  187. package/data/docs/types/TabsPanel.json +2 -2
  188. package/data/docs/types/TabsPanels.json +2 -2
  189. package/data/docs/types/TabsRoot.json +3 -3
  190. package/data/docs/types/TabsTab.json +2 -2
  191. package/data/docs/types/TagGroupRoot.json +24 -24
  192. package/data/docs/types/TagGroupTag.json +56 -56
  193. package/data/docs/types/TagGroupTagList.json +14 -14
  194. package/data/docs/types/Text.json +8 -8
  195. package/data/docs/types/TextInput.json +102 -102
  196. package/data/docs/types/TextInputField.json +100 -100
  197. package/data/docs/types/TimeInput.json +68 -68
  198. package/data/docs/types/ToggleButton.json +73 -73
  199. package/data/docs/types/ToggleButtonGroupButton.json +30 -30
  200. package/data/docs/types/ToggleButtonGroupRoot.json +20 -20
  201. package/data/docs/types/Toolbar.json +9 -9
  202. package/data/docs/types/TooltipContent.json +28 -28
  203. package/data/docs/types/TooltipRoot.json +16 -16
  204. package/data/docs/types/Tree.json +12 -0
  205. package/data/docs/types/TreeIndicator.json +828 -0
  206. package/data/docs/types/TreeItem.json +559 -0
  207. package/data/docs/types/TreeItemContent.json +27 -0
  208. package/data/docs/types/TreeRoot.json +636 -0
  209. package/data/docs/types/TreeSubTree.json +40 -0
  210. package/data/docs/types/VisuallyHidden.json +6 -6
  211. package/data/docs/types/createItemsFromCsvDrop.json +4 -4
  212. package/data/docs/types/createItemsFromDirectoryDrop.json +4 -4
  213. package/data/docs/types/createItemsFromFileDrop.json +4 -4
  214. package/data/docs/types/createItemsFromImageDrop.json +4 -4
  215. package/data/docs/types/createItemsFromJsonDrop.json +4 -4
  216. package/data/docs/types/manifest.json +50 -0
  217. package/data/docs/types/useRegion.json +2 -2
  218. package/data/docs/types/useSlidingIndicator.json +145 -0
  219. package/data/docs/types/useTree.json +267 -0
  220. package/data/tokens.json +21 -0
  221. package/data/uikit-tokens.json +894 -0
  222. package/dist/index.js +3298 -109
  223. package/package.json +8 -6
@@ -0,0 +1,417 @@
1
+ {
2
+ "meta": {
3
+ "id": "Components-SkeletonText",
4
+ "title": "SkeletonText",
5
+ "exportName": "SkeletonText",
6
+ "description": "A stack of placeholder lines that approximates a paragraph of text while content loads.",
7
+ "lifecycleState": "Stable",
8
+ "order": 999,
9
+ "repoPath": "packages/nimbus/src/components/skeleton/derivatives/skeleton-text/skeleton-text.mdx",
10
+ "menu": [
11
+ "Components",
12
+ "Feedback",
13
+ "SkeletonText"
14
+ ],
15
+ "route": "components/feedback/skeletontext",
16
+ "tags": [
17
+ "component",
18
+ "skeleton",
19
+ "loading",
20
+ "placeholder",
21
+ "feedback",
22
+ "text"
23
+ ],
24
+ "toc": [
25
+ {
26
+ "value": "Overview",
27
+ "href": "#overview",
28
+ "depth": 2,
29
+ "numbering": [
30
+ 1,
31
+ 1
32
+ ],
33
+ "parent": "root"
34
+ },
35
+ {
36
+ "value": "Resources",
37
+ "href": "#resources",
38
+ "depth": 3,
39
+ "numbering": [
40
+ 1,
41
+ 1,
42
+ 1
43
+ ],
44
+ "parent": "root"
45
+ },
46
+ {
47
+ "value": "Variables",
48
+ "href": "#variables",
49
+ "depth": 2,
50
+ "numbering": [
51
+ 1,
52
+ 2
53
+ ],
54
+ "parent": "root"
55
+ },
56
+ {
57
+ "value": "Line count",
58
+ "href": "#line-count",
59
+ "depth": 3,
60
+ "numbering": [
61
+ 1,
62
+ 2,
63
+ 1
64
+ ],
65
+ "parent": "root"
66
+ },
67
+ {
68
+ "value": "Matching text size",
69
+ "href": "#matching-text-size",
70
+ "depth": 3,
71
+ "numbering": [
72
+ 1,
73
+ 2,
74
+ 2
75
+ ],
76
+ "parent": "root"
77
+ },
78
+ {
79
+ "value": "Last line width",
80
+ "href": "#last-line-width",
81
+ "depth": 3,
82
+ "numbering": [
83
+ 1,
84
+ 2,
85
+ 3
86
+ ],
87
+ "parent": "root"
88
+ },
89
+ {
90
+ "value": "Guidelines",
91
+ "href": "#guidelines",
92
+ "depth": 2,
93
+ "numbering": [
94
+ 1,
95
+ 3
96
+ ],
97
+ "parent": "root"
98
+ },
99
+ {
100
+ "value": "Best practices",
101
+ "href": "#best-practices",
102
+ "depth": 3,
103
+ "numbering": [
104
+ 1,
105
+ 3,
106
+ 1
107
+ ],
108
+ "parent": "root"
109
+ },
110
+ {
111
+ "value": "When to use",
112
+ "href": "#when-to-use",
113
+ "depth": 3,
114
+ "numbering": [
115
+ 1,
116
+ 3,
117
+ 2
118
+ ],
119
+ "parent": "root"
120
+ }
121
+ ],
122
+ "layout": "app-frame",
123
+ "tabs": [
124
+ {
125
+ "key": "overview",
126
+ "title": "Overview",
127
+ "order": 0
128
+ },
129
+ {
130
+ "key": "dev",
131
+ "title": "Implementation",
132
+ "order": 3
133
+ }
134
+ ]
135
+ },
136
+ "mdx": "\n## Overview\n\n`SkeletonText` renders a stack of placeholder lines that stand in for a\nparagraph of text while it loads. The last line is narrower by default to mimic\na natural paragraph ending, and the lines are sized to the same vertical rhythm\nas the real text they replace — so the transition to loaded content stays\nsmooth.\n\n`SkeletonText` is part of the Skeleton family, built from\n[Skeleton](/components/feedback/skeleton) lines. For single blocks, images, or\ncustom shapes use `Skeleton` directly; for avatars and icons use\n[SkeletonCircle](/components/feedback/skeletoncircle).\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n[Figma library](https://www.figma.com/design/AvtPX6g7OGGCRvNlatGOIY/NIMBUS-design-system)\n\n## Variables\n\nGet familiar with the features.\n\n### Line count\n\nUse `lines` to set how many placeholder lines to render. Set the block's\n`width` to match the column the text will occupy.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"row\" gap=\"800\" width=\"600px\">\n <SkeletonText lines={2} width=\"100%\" />\n <SkeletonText lines={4} width=\"100%\" />\n </Stack>\n)\n```\n\n### Matching text size\n\nSet `textStyle` (default `body`) so the placeholder lines match the size and\nline spacing of the text they stand in for — from small captions to large\nheadings. Each bar's height and the gaps between them resize together, keeping\nthe same vertical rhythm as real text of that style.\n\n```jsx live\nconst App = () => (\n <Stack gap=\"600\" width=\"300px\">\n <SkeletonText textStyle=\"caption\" />\n <SkeletonText textStyle=\"body\" />\n <SkeletonText textStyle=\"3xl\" lines={2} />\n </Stack>\n)\n```\n\n### Last line width\n\nThe last line is narrower by default (`lastLineWidth=\"60%\"`) to mimic a\nparagraph ending. Adjust it to match the content, or set it to `\"100%\"` for a\njustified block.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"row\" gap=\"800\" width=\"600px\">\n <SkeletonText lines={3} width=\"100%\" lastLineWidth=\"40%\" />\n <SkeletonText lines={3} width=\"100%\" lastLineWidth=\"100%\" />\n </Stack>\n)\n```\n\n## Guidelines\n\n### Best practices\n\n- Match `textStyle` to the text you are standing in for so the placeholder\n occupies the same space as the loaded content and avoids layout shift.\n- Use `SkeletonText` for multi-line paragraphs; for a single heading or line,\n the base `Skeleton` gives you more direct control over height and width.\n- Keep the line count realistic — approximating the real paragraph length keeps\n the transition to loaded content smooth.\n\n### When to use\n\n> [!TIP]\\\n> Use SkeletonText when:\n- You are loading a paragraph, description, or block of running text\n- You want the placeholder to match a specific text style's size and rhythm\n- A detail panel or card contains several lines of text\n\n> [!CAUTION]\\\n> When NOT to use SkeletonText:\n- For a single line, heading, or non-text block — use\n [Skeleton](/components/feedback/skeleton) instead\n- For circular content such as avatars — use\n [SkeletonCircle](/components/feedback/skeletoncircle)\n",
137
+ "views": {
138
+ "overview": {
139
+ "mdx": "\n## Overview\n\n`SkeletonText` renders a stack of placeholder lines that stand in for a\nparagraph of text while it loads. The last line is narrower by default to mimic\na natural paragraph ending, and the lines are sized to the same vertical rhythm\nas the real text they replace — so the transition to loaded content stays\nsmooth.\n\n`SkeletonText` is part of the Skeleton family, built from\n[Skeleton](/components/feedback/skeleton) lines. For single blocks, images, or\ncustom shapes use `Skeleton` directly; for avatars and icons use\n[SkeletonCircle](/components/feedback/skeletoncircle).\n\n### Resources\n\nDeep dive into implementation details and access the Nimbus design library.\n\n[Figma library](https://www.figma.com/design/AvtPX6g7OGGCRvNlatGOIY/NIMBUS-design-system)\n\n## Variables\n\nGet familiar with the features.\n\n### Line count\n\nUse `lines` to set how many placeholder lines to render. Set the block's\n`width` to match the column the text will occupy.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"row\" gap=\"800\" width=\"600px\">\n <SkeletonText lines={2} width=\"100%\" />\n <SkeletonText lines={4} width=\"100%\" />\n </Stack>\n)\n```\n\n### Matching text size\n\nSet `textStyle` (default `body`) so the placeholder lines match the size and\nline spacing of the text they stand in for — from small captions to large\nheadings. Each bar's height and the gaps between them resize together, keeping\nthe same vertical rhythm as real text of that style.\n\n```jsx live\nconst App = () => (\n <Stack gap=\"600\" width=\"300px\">\n <SkeletonText textStyle=\"caption\" />\n <SkeletonText textStyle=\"body\" />\n <SkeletonText textStyle=\"3xl\" lines={2} />\n </Stack>\n)\n```\n\n### Last line width\n\nThe last line is narrower by default (`lastLineWidth=\"60%\"`) to mimic a\nparagraph ending. Adjust it to match the content, or set it to `\"100%\"` for a\njustified block.\n\n```jsx live\nconst App = () => (\n <Stack direction=\"row\" gap=\"800\" width=\"600px\">\n <SkeletonText lines={3} width=\"100%\" lastLineWidth=\"40%\" />\n <SkeletonText lines={3} width=\"100%\" lastLineWidth=\"100%\" />\n </Stack>\n)\n```\n\n## Guidelines\n\n### Best practices\n\n- Match `textStyle` to the text you are standing in for so the placeholder\n occupies the same space as the loaded content and avoids layout shift.\n- Use `SkeletonText` for multi-line paragraphs; for a single heading or line,\n the base `Skeleton` gives you more direct control over height and width.\n- Keep the line count realistic — approximating the real paragraph length keeps\n the transition to loaded content smooth.\n\n### When to use\n\n> [!TIP]\\\n> Use SkeletonText when:\n- You are loading a paragraph, description, or block of running text\n- You want the placeholder to match a specific text style's size and rhythm\n- A detail panel or card contains several lines of text\n\n> [!CAUTION]\\\n> When NOT to use SkeletonText:\n- For a single line, heading, or non-text block — use\n [Skeleton](/components/feedback/skeleton) instead\n- For circular content such as avatars — use\n [SkeletonCircle](/components/feedback/skeletoncircle)\n",
140
+ "toc": [
141
+ {
142
+ "value": "Overview",
143
+ "href": "#overview",
144
+ "depth": 2,
145
+ "numbering": [
146
+ 1,
147
+ 1
148
+ ],
149
+ "parent": "root"
150
+ },
151
+ {
152
+ "value": "Resources",
153
+ "href": "#resources",
154
+ "depth": 3,
155
+ "numbering": [
156
+ 1,
157
+ 1,
158
+ 1
159
+ ],
160
+ "parent": "root"
161
+ },
162
+ {
163
+ "value": "Variables",
164
+ "href": "#variables",
165
+ "depth": 2,
166
+ "numbering": [
167
+ 1,
168
+ 2
169
+ ],
170
+ "parent": "root"
171
+ },
172
+ {
173
+ "value": "Line count",
174
+ "href": "#line-count",
175
+ "depth": 3,
176
+ "numbering": [
177
+ 1,
178
+ 2,
179
+ 1
180
+ ],
181
+ "parent": "root"
182
+ },
183
+ {
184
+ "value": "Matching text size",
185
+ "href": "#matching-text-size",
186
+ "depth": 3,
187
+ "numbering": [
188
+ 1,
189
+ 2,
190
+ 2
191
+ ],
192
+ "parent": "root"
193
+ },
194
+ {
195
+ "value": "Last line width",
196
+ "href": "#last-line-width",
197
+ "depth": 3,
198
+ "numbering": [
199
+ 1,
200
+ 2,
201
+ 3
202
+ ],
203
+ "parent": "root"
204
+ },
205
+ {
206
+ "value": "Guidelines",
207
+ "href": "#guidelines",
208
+ "depth": 2,
209
+ "numbering": [
210
+ 1,
211
+ 3
212
+ ],
213
+ "parent": "root"
214
+ },
215
+ {
216
+ "value": "Best practices",
217
+ "href": "#best-practices",
218
+ "depth": 3,
219
+ "numbering": [
220
+ 1,
221
+ 3,
222
+ 1
223
+ ],
224
+ "parent": "root"
225
+ },
226
+ {
227
+ "value": "When to use",
228
+ "href": "#when-to-use",
229
+ "depth": 3,
230
+ "numbering": [
231
+ 1,
232
+ 3,
233
+ 2
234
+ ],
235
+ "parent": "root"
236
+ }
237
+ ]
238
+ },
239
+ "dev": {
240
+ "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { SkeletonText, type SkeletonTextProps } from '@commercetools/nimbus';\n```\n\n`SkeletonText` is built on [Skeleton](/components/feedback/skeleton). For single\nblocks use `Skeleton`; for circular placeholders see\n[SkeletonCircle](/components/feedback/skeletoncircle).\n\n### Basic usage\n\n`SkeletonText` renders a configurable number of placeholder lines to approximate\na paragraph. The last line is narrower by default (`lastLineWidth=\"60%\"`) to\nmimic a natural paragraph ending.\n\n```jsx live-dev\nconst App = () => (\n <SkeletonText lines={4} width=\"300px\" />\n)\n```\n\n## Usage examples\n\n### Matching text size with `textStyle`\n\n`SkeletonText` accepts a `textStyle` prop (default `\"body\"`) that makes the\nplaceholder lines occupy the same vertical rhythm as real text of that style.\nEach line becomes a `1lh` line-box sized by the style, with a `0.75em`\n(≈ cap-height) bar centered in it — so the bar sits where real glyphs sit and the\npitch scales automatically: a `body` skeleton uses a 26 px line pitch, a `3xl`\nheading skeleton a 36 px pitch. Pass any Nimbus text style to match the content\nyou are standing in for.\n\n```jsx live-dev\nconst App = () => (\n <Stack gap=\"600\" width=\"300px\">\n <SkeletonText textStyle=\"caption\" />\n <SkeletonText textStyle=\"body\" />\n <SkeletonText textStyle=\"3xl\" lines={2} />\n </Stack>\n)\n```\n\n### Conditional rendering (standalone pattern)\n\nLike the rest of the family, `SkeletonText` has no `isLoaded` prop. Render it\nwhile loading and swap it for the real text with conditional rendering.\n\n```jsx live-dev\nconst App = () => {\n const [isLoading, setIsLoading] = React.useState(true);\n\n return (\n <Stack gap=\"400\" width=\"320px\">\n <Button onPress={() => setIsLoading(v => !v)}>Toggle loading</Button>\n <div aria-busy={isLoading}>\n {isLoading ? (\n <SkeletonText lines={3} width=\"100%\" />\n ) : (\n <Text>\n A short product description that replaces the placeholder once the\n data has finished loading.\n </Text>\n )}\n </div>\n </Stack>\n )\n}\n```\n\n## Component requirements\n\n### Accessibility\n\n`SkeletonText` is **decorative by default** — its container and lines render\nwith `aria-hidden=\"true\"` so screen readers skip over them.\n\nTo communicate loading state to assistive technology, set `aria-busy=\"true\"` on\nthe **container** that wraps the skeleton region, then clear it and render the\nreal content once loading completes.\n\n```tsx\n<section aria-busy={isLoading} aria-label=\"Product description\">\n {isLoading ? (\n <SkeletonText lines={3} width=\"100%\" />\n ) : (\n <ProductDescription product={product} />\n )}\n</section>\n```\n\nAnnounce the loading region on the wrapping container rather than labelling the\nskeleton itself — see [Skeleton](/components/feedback/skeleton) for the full\nrationale.\n\n#### Reduced motion\n\nWhen the user has enabled `prefers-reduced-motion: reduce`, the line animations\nare automatically disabled regardless of the `animation` prop value. No\nadditional configuration is required.\n\n#### Keyboard navigation\n\n`SkeletonText` is non-interactive and decorative. It does not receive focus or\nsupport keyboard interaction.\n\n## API reference\n\n<PropsTable id=\"SkeletonText\" />\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using SkeletonText within your application. As the component's internal functionality is already tested by Nimbus, these patterns help you verify your integration and application-specific logic.\n\n### Basic Rendering\n\nRender a `SkeletonText` block and confirm it renders one placeholder line per `lines`; the container is decorative (`aria-hidden`) by default, so assistive technology skips it.\n\n```tsx\nimport { useState } from \"react\";\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport {\n SkeletonText,\n Stack,\n Text,\n Button,\n NimbusProvider,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"SkeletonText - Basic rendering\", () => {\n it(\"renders one placeholder line per `lines`\", () => {\n render(\n <NimbusProvider>\n <SkeletonText data-testid=\"skeleton-text\" lines={3} width=\"300px\" />\n </NimbusProvider>\n );\n\n const container = screen.getByTestId(\"skeleton-text\");\n expect(container).toBeInTheDocument();\n expect(container).toHaveAttribute(\"aria-hidden\", \"true\");\n // One Skeleton line is rendered for each of the three requested lines.\n expect(container.children).toHaveLength(3);\n });\n});\n```\n\n### Standalone Loading Pattern\n\nThe recommended way to use SkeletonText: render placeholder lines while the text is loading, then swap in the real paragraph with conditional rendering — SkeletonText has no `isLoaded` prop, so the consumer owns the loading state.\n\n```tsx\nimport { useState } from \"react\";\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport {\n SkeletonText,\n Stack,\n Text,\n Button,\n NimbusProvider,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"SkeletonText - Standalone loading pattern\", () => {\n /**\n * A representative consumer component: show placeholder lines while\n * `isLoading`, then render the real description once loading completes.\n */\n function ProductDescription({ isLoading }: { isLoading: boolean }) {\n return isLoading ? (\n <SkeletonText lines={3} width=\"100%\" />\n ) : (\n <Text>\n Wireless keyboard with a compact layout and long battery life.\n </Text>\n );\n }\n\n it(\"shows placeholder lines while loading, then the real text\", async () => {\n const description =\n \"Wireless keyboard with a compact layout and long battery life.\";\n\n const Example = () => {\n const [isLoading, setIsLoading] = useState(true);\n return (\n <Stack gap=\"400\" width=\"320px\">\n <ProductDescription isLoading={isLoading} />\n <Button onPress={() => setIsLoading(false)}>Finish loading</Button>\n </Stack>\n );\n };\n\n render(\n <NimbusProvider>\n <Example />\n </NimbusProvider>\n );\n\n // While loading: real text is absent.\n expect(screen.queryByText(description)).toBeNull();\n\n await userEvent.click(\n screen.getByRole(\"button\", { name: \"Finish loading\" })\n );\n\n // After loading: real text is present.\n expect(screen.getByText(description)).toBeInTheDocument();\n });\n});\n```\n\n### Announcing Loading with aria-busy\n\nSkeletonText is decorative (`aria-hidden`), so it is not announced on its own; communicate the loading state to assistive technology by setting `aria-busy` on the surrounding container instead.\n\n```tsx\nimport { useState } from \"react\";\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport {\n SkeletonText,\n Stack,\n Text,\n Button,\n NimbusProvider,\n} from \"@commercetools/nimbus\";\n\ndescribe(\"SkeletonText - Container aria-busy pattern\", () => {\n it(\"announces loading state via aria-busy on the container\", () => {\n render(\n <NimbusProvider>\n <section\n data-testid=\"container\"\n aria-busy={true}\n aria-label=\"Product description\"\n >\n <SkeletonText lines={3} width=\"100%\" />\n </section>\n </NimbusProvider>\n );\n\n const container = screen.getByTestId(\"container\");\n expect(container).toHaveAttribute(\"aria-busy\", \"true\");\n expect(container).toHaveAttribute(\"aria-label\", \"Product description\");\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-skeleton--docs)\n- [Skeleton](/components/feedback/skeleton)\n- [SkeletonCircle](/components/feedback/skeletoncircle)\n",
241
+ "toc": [
242
+ {
243
+ "value": "Getting started",
244
+ "href": "#getting-started",
245
+ "depth": 2,
246
+ "numbering": [
247
+ 1,
248
+ 1
249
+ ],
250
+ "parent": "root"
251
+ },
252
+ {
253
+ "value": "Import",
254
+ "href": "#import",
255
+ "depth": 3,
256
+ "numbering": [
257
+ 1,
258
+ 1,
259
+ 1
260
+ ],
261
+ "parent": "root"
262
+ },
263
+ {
264
+ "value": "Basic usage",
265
+ "href": "#basic-usage",
266
+ "depth": 3,
267
+ "numbering": [
268
+ 1,
269
+ 1,
270
+ 2
271
+ ],
272
+ "parent": "root"
273
+ },
274
+ {
275
+ "value": "Usage examples",
276
+ "href": "#usage-examples",
277
+ "depth": 2,
278
+ "numbering": [
279
+ 1,
280
+ 2
281
+ ],
282
+ "parent": "root"
283
+ },
284
+ {
285
+ "value": "Matching text size with textStyle",
286
+ "href": "#matching-text-size-with-textstyle",
287
+ "depth": 3,
288
+ "numbering": [
289
+ 1,
290
+ 2,
291
+ 1
292
+ ],
293
+ "parent": "root"
294
+ },
295
+ {
296
+ "value": "Conditional rendering (standalone pattern)",
297
+ "href": "#conditional-rendering-standalone-pattern",
298
+ "depth": 3,
299
+ "numbering": [
300
+ 1,
301
+ 2,
302
+ 2
303
+ ],
304
+ "parent": "root"
305
+ },
306
+ {
307
+ "value": "Component requirements",
308
+ "href": "#component-requirements",
309
+ "depth": 2,
310
+ "numbering": [
311
+ 1,
312
+ 3
313
+ ],
314
+ "parent": "root"
315
+ },
316
+ {
317
+ "value": "Accessibility",
318
+ "href": "#accessibility",
319
+ "depth": 3,
320
+ "numbering": [
321
+ 1,
322
+ 3,
323
+ 1
324
+ ],
325
+ "parent": "root"
326
+ },
327
+ {
328
+ "value": "Reduced motion",
329
+ "href": "#reduced-motion",
330
+ "depth": 4,
331
+ "numbering": [
332
+ 1,
333
+ 3,
334
+ 1,
335
+ 1
336
+ ],
337
+ "parent": "root"
338
+ },
339
+ {
340
+ "value": "Keyboard navigation",
341
+ "href": "#keyboard-navigation",
342
+ "depth": 4,
343
+ "numbering": [
344
+ 1,
345
+ 3,
346
+ 1,
347
+ 2
348
+ ],
349
+ "parent": "root"
350
+ },
351
+ {
352
+ "value": "API reference",
353
+ "href": "#api-reference",
354
+ "depth": 2,
355
+ "numbering": [
356
+ 1,
357
+ 4
358
+ ],
359
+ "parent": "root"
360
+ },
361
+ {
362
+ "value": "Testing your implementation",
363
+ "href": "#testing-your-implementation",
364
+ "depth": 2,
365
+ "numbering": [
366
+ 1,
367
+ 5
368
+ ],
369
+ "parent": "root"
370
+ },
371
+ {
372
+ "value": "Basic Rendering",
373
+ "href": "#basic-rendering",
374
+ "depth": 3,
375
+ "numbering": [
376
+ 1,
377
+ 5,
378
+ 1
379
+ ],
380
+ "parent": "root"
381
+ },
382
+ {
383
+ "value": "Standalone Loading Pattern",
384
+ "href": "#standalone-loading-pattern",
385
+ "depth": 3,
386
+ "numbering": [
387
+ 1,
388
+ 5,
389
+ 2
390
+ ],
391
+ "parent": "root"
392
+ },
393
+ {
394
+ "value": "Announcing Loading with aria-busy",
395
+ "href": "#announcing-loading-with-aria-busy",
396
+ "depth": 3,
397
+ "numbering": [
398
+ 1,
399
+ 5,
400
+ 3
401
+ ],
402
+ "parent": "root"
403
+ },
404
+ {
405
+ "value": "Resources",
406
+ "href": "#resources",
407
+ "depth": 2,
408
+ "numbering": [
409
+ 1,
410
+ 6
411
+ ],
412
+ "parent": "root"
413
+ }
414
+ ]
415
+ }
416
+ }
417
+ }
@@ -165,7 +165,7 @@
165
165
  ]
166
166
  },
167
167
  "dev": {
168
- "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Tooltip, MakeElementFocusable } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe simplest implementation wraps a trigger element and tooltip content:\n\n```jsx live-dev\nconst App = () => (\n <Tooltip.Root>\n <Button>Hover me</Button>\n <Tooltip.Content>\n Helpful information appears here\n </Tooltip.Content>\n </Tooltip.Root>\n)\n```\n\n## Usage examples\n\n### Placement options\n\nThe `placement` prop controls where the tooltip appears relative to the trigger element. The default placement is `\"bottom\"`:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Tooltip.Root>\n <Button>Top</Button>\n <Tooltip.Content placement=\"top\">\n Top placement\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <Button>Bottom</Button>\n <Tooltip.Content placement=\"bottom\">\n Bottom placement\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <Button>Left</Button>\n <Tooltip.Content placement=\"left\">\n Left placement\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <Button>Right</Button>\n <Tooltip.Content placement=\"right\">\n Right placement\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n)\n```\n\n**Available placements:**\n\nThe component supports 26 placement options:\n- **Basic**: `top`, `bottom`, `left`, `right`, `start`, `end`\n- **With alignment**: `top left`, `top right`, `top start`, `top end`, `bottom left`, `bottom right`, `bottom start`, `bottom end`, `left top`, `left bottom`, `right top`, `right bottom`, `start top`, `start bottom`, `end top`, `end bottom`\n\n### Delay configuration\n\nControl when tooltips appear and disappear using `delay` and `closeDelay` props on `Tooltip.Root`:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Tooltip.Root delay={0} closeDelay={0}>\n <Button>Instant</Button>\n <Tooltip.Content>\n Shows immediately\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root delay={300} closeDelay={200}>\n <Button>Default timing</Button>\n <Tooltip.Content>\n 300ms delay to show, 200ms to hide\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root delay={1000}>\n <Button>Slow</Button>\n <Tooltip.Content>\n 1000ms delay to show\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n)\n```\n\n**Timing considerations:**\n- `delay`: Time in milliseconds before the tooltip appears (default: `300`)\n- `closeDelay`: Time in milliseconds before the tooltip disappears (default: `200`)\n- Shorter delays improve responsiveness but may feel too aggressive\n- Longer delays reduce accidental triggers but may feel sluggish\n\n### Complex content\n\nTooltip content can include multiple elements using layout components:\n\n```jsx live-dev\nconst App = () => (\n <Tooltip.Root>\n <Button>Feature info</Button>\n <Tooltip.Content>\n <Stack direction=\"column\" gap=\"100\">\n <Text fontWeight=\"600\" fontSize=\"sm\">Premium Feature</Text>\n <Text fontSize=\"xs\">\n Available on Pro and Enterprise plans\n </Text>\n </Stack>\n </Tooltip.Content>\n </Tooltip.Root>\n)\n```\n\n### With icon buttons\n\nA common pattern is providing context for icon-only buttons:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"300\">\n <Tooltip.Root>\n <IconButton aria-label=\"Edit\">\n <Icons.Edit />\n </IconButton>\n <Tooltip.Content placement=\"top\">\n Edit item\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <IconButton aria-label=\"Delete\">\n <Icons.Delete />\n </IconButton>\n <Tooltip.Content placement=\"top\">\n Delete item\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <IconButton aria-label=\"Share\">\n <Icons.Share />\n </IconButton>\n <Tooltip.Content placement=\"top\">\n Share with team\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n)\n```\n\n### With focusable elements\n\nTooltips work with any focusable element. For non-interactive elements like Text, use `MakeElementFocusable`:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Tooltip.Root>\n <MakeElementFocusable>\n <Text\n as=\"span\"\n textDecoration=\"underline\"\n cursor=\"help\"\n >\n Hover or focus me\n </Text>\n </MakeElementFocusable>\n <Tooltip.Content>\n This text element is focusable and shows a tooltip\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <Button variant=\"ghost\">\n <Icons.Info />\n Learn more\n </Button>\n <Tooltip.Content placement=\"right\">\n Opens detailed documentation\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n)\n```\n\n## Component requirements\n\n## Accessibility\n\nThe Tooltip component is built with React Aria Components and provides accessible tooltip behavior by default. The component:\n\n- Automatically associates tooltip content with the trigger using `aria-describedby`\n- Assigns the `tooltip` ARIA role to the content\n- Shows on both hover and keyboard focus\n- Hides when focus leaves or `Escape` is pressed\n\n**Important:** Ensure the trigger element has an accessible label. For icon-only buttons, always provide an `aria-label`:\n\n```tsx\n<Tooltip.Root>\n <IconButton aria-label=\"Delete item\">\n <Icons.Delete />\n </IconButton>\n <Tooltip.Content>Delete this item</Tooltip.Content>\n</Tooltip.Root>\n```\n\nIf your use case requires tracking and analytics for this component, it is good practice to add a **persistent**, **unique** id to the trigger element:\n\n```tsx\nconst PERSISTENT_ID = \"delete-button-tooltip\";\n\nexport const Example = () => (\n <Tooltip.Root>\n <IconButton id={PERSISTENT_ID} aria-label=\"Delete\">\n <Icons.Delete />\n </IconButton>\n <Tooltip.Content>Delete item</Tooltip.Content>\n </Tooltip.Root>\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n\n- `Tab` / `Shift+Tab`: Move focus to/from the trigger element\n- Tooltip appears automatically when trigger receives focus\n- `Escape`: Dismisses the tooltip (if shown)\n- Tooltip disappears automatically when focus leaves the trigger\n\n### Trigger element requirements\n\nThe first child of `Tooltip.Root` must be a focusable element:\n\n- Use buttons, links, or other interactive elements (recommended)\n- For non-interactive elements like Text or div, wrap with `MakeElementFocusable`\n- Avoid wrapping non-focusable elements without proper handling\n\n```tsx\n// ✅ Good - button is natively focusable\n<Tooltip.Root>\n <Button>Trigger</Button>\n <Tooltip.Content>Content</Tooltip.Content>\n</Tooltip.Root>\n\n// ✅ Good - non-interactive element with MakeElementFocusable\n<Tooltip.Root>\n <MakeElementFocusable>\n <Text as=\"span\">Trigger</Text>\n </MakeElementFocusable>\n <Tooltip.Content>Content</Tooltip.Content>\n</Tooltip.Root>\n\n// ❌ Bad - Text with tabIndex won't work properly\n<Tooltip.Root>\n <Text as=\"span\" tabIndex={0}>Trigger</Text>\n <Tooltip.Content>Content</Tooltip.Content>\n</Tooltip.Root>\n```\n\n## API reference\n\n<PropsTable id=\"Tooltip\" />\n\n## Common patterns\n\n### Info tooltips for form fields\n\nProvide additional context for form fields without cluttering the interface:\n\n```jsx live-dev\nconst App = () => {\n const [value, setValue] = useState('');\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"center\">\n <Text as=\"label\" htmlFor=\"username-input\" fontWeight=\"500\">\n Username\n </Text>\n <Tooltip.Root>\n <IconButton\n aria-label=\"Username requirements\"\n size=\"sm\"\n variant=\"ghost\"\n >\n <Icons.Info />\n </IconButton>\n <Tooltip.Content placement=\"right\">\n <Stack direction=\"column\" gap=\"100\">\n <Text fontSize=\"xs\" fontWeight=\"600\">Requirements:</Text>\n <Text fontSize=\"xs\">• 3-20 characters</Text>\n <Text fontSize=\"xs\">• Letters and numbers only</Text>\n <Text fontSize=\"xs\">• No spaces</Text>\n </Stack>\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n <TextInput\n id=\"username-input\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder=\"Enter username\"\n />\n </Stack>\n );\n}\n```\n\n### Action descriptions for icon buttons\n\nClarify what icon buttons do before users click them:\n\n```jsx live-dev\nconst App = () => {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = () => {\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Text fontSize=\"sm\">\n Code snippet:\n </Text>\n <Stack\n direction=\"row\"\n gap=\"300\"\n padding=\"300\"\n backgroundColor=\"neutral.3\"\n borderRadius=\"200\"\n alignItems=\"center\"\n >\n <Text fontFamily=\"mono\" fontSize=\"sm\" flex=\"1\">\n npm install @commercetools/nimbus\n </Text>\n\n <Tooltip.Root>\n <IconButton\n aria-label=\"Copy to clipboard\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={handleCopy}\n >\n {copied ? <Icons.Check /> : <Icons.ContentCopy />}\n </IconButton>\n <Tooltip.Content placement=\"top\">\n {copied ? 'Copied!' : 'Copy to clipboard'}\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n </Stack>\n );\n}\n```\n\n### Contextual help in data tables\n\nProvide explanations for table headers or data values:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"300\">\n <Stack direction=\"row\" gap=\"400\" paddingX=\"300\" paddingY=\"200\">\n <Stack direction=\"row\" gap=\"100\" alignItems=\"center\" flex=\"1\">\n <Text fontWeight=\"600\" fontSize=\"sm\">Product Name</Text>\n </Stack>\n\n <Stack direction=\"row\" gap=\"100\" alignItems=\"center\" width=\"150px\">\n <Text fontWeight=\"600\" fontSize=\"sm\">Conversion Rate</Text>\n <Tooltip.Root>\n <IconButton\n aria-label=\"About conversion rate\"\n size=\"sm\"\n variant=\"ghost\"\n >\n <Icons.HelpOutline />\n </IconButton>\n <Tooltip.Content placement=\"top\">\n <Text fontSize=\"xs\">\n Percentage of visitors who make a purchase\n </Text>\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n\n <Stack direction=\"row\" gap=\"100\" alignItems=\"center\" width=\"100px\">\n <Text fontWeight=\"600\" fontSize=\"sm\">Status</Text>\n </Stack>\n </Stack>\n\n <Stack direction=\"row\" gap=\"400\" paddingX=\"300\" paddingY=\"200\" backgroundColor=\"neutral.2\">\n <Text fontSize=\"sm\" flex=\"1\">Premium Widget</Text>\n <Text fontSize=\"sm\" width=\"150px\">24.5%</Text>\n <Text fontSize=\"sm\" width=\"100px\">Active</Text>\n </Stack>\n </Stack>\n)\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Tooltip within your application. As the component's internal functionality is already tested by Nimbus, these patterns help you verify your integration and application-specific logic.\n\n### Basic Rendering Tests\n\nVerify the tooltip renders and displays content on interaction\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Basic rendering\", () => {\n it(\"renders trigger element\", () => {\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Hover me</Button>\n <Tooltip.Content>Tooltip text</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /hover me/i })\n ).toBeInTheDocument();\n });\n\n it(\"shows tooltip content on focus\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Hover me</Button>\n <Tooltip.Content>Tooltip text</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Focus the button using tab\n await user.tab();\n\n await waitFor(() => {\n expect(screen.getByRole(\"tooltip\")).toBeInTheDocument();\n expect(screen.getByText(\"Tooltip text\")).toBeInTheDocument();\n });\n });\n});\n```\n\n### Interaction Tests\n\nTest focus interactions (hover tests require browser environment)\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Interactions\", () => {\n it(\"shows tooltip on focus\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Action Button</Button>\n <Tooltip.Content>Action tooltip</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Use userEvent.tab() instead of element.focus() to avoid act() warnings\n await user.tab();\n\n await waitFor(() => {\n expect(screen.getByRole(\"tooltip\")).toBeInTheDocument();\n expect(screen.getByText(\"Action tooltip\")).toBeInTheDocument();\n });\n });\n\n it(\"hides tooltip when focus leaves\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>First</Button>\n <Tooltip.Content>First tooltip</Tooltip.Content>\n </Tooltip.Root>\n <Button>Second</Button>\n </NimbusProvider>\n );\n\n // Focus first button\n await user.tab();\n await waitFor(() => {\n expect(screen.getByRole(\"tooltip\")).toBeInTheDocument();\n });\n\n // Focus second button\n await user.tab();\n await waitFor(() => {\n expect(screen.queryByRole(\"tooltip\")).not.toBeInTheDocument();\n });\n });\n});\n```\n\n### Testing Placement\n\nVerify tooltip positioning\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Placement\", () => {\n it(\"renders with top placement\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Trigger</Button>\n <Tooltip.Content placement=\"top\">Top tooltip</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Tab to focus the button\n await user.tab();\n\n await waitFor(() => {\n const tooltip = screen.getByRole(\"tooltip\");\n expect(tooltip).toBeInTheDocument();\n expect(tooltip).toHaveTextContent(\"Top tooltip\");\n });\n });\n});\n```\n\n### Testing with Portal Content\n\nTooltips render in a portal, so query from document when needed\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Portal rendering\", () => {\n it(\"renders tooltip in portal\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Show tooltip</Button>\n <Tooltip.Content>Portal content</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Use userEvent.tab() instead of element.focus() to avoid act() warnings\n await user.tab();\n\n // Wait for portal content to appear\n await waitFor(() => {\n const tooltip = screen.getByRole(\"tooltip\");\n expect(tooltip).toBeInTheDocument();\n });\n });\n});\n```\n\n### Testing Custom Delay Timing\n\nVerify delay configuration works as expected\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Timing\", () => {\n it(\"respects custom delay\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root delay={0} closeDelay={0}>\n <Button>Instant</Button>\n <Tooltip.Content>Shows immediately</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Use userEvent.tab() instead of element.focus() to avoid act() warnings\n await user.tab();\n\n // With delay={0}, tooltip should appear immediately\n await waitFor(\n () => {\n expect(screen.getByRole(\"tooltip\")).toBeInTheDocument();\n },\n { timeout: 100 }\n );\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-tooltip-tooltip--docs)\n- [React Aria Tooltip](https://react-spectrum.adobe.com/react-aria/Tooltip.html)\n- [ARIA Tooltip Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/)\n",
168
+ "mdx": "\n## Getting started\n\n### Import\n\n```tsx\nimport { Tooltip, MakeElementFocusable } from '@commercetools/nimbus';\n```\n\n### Basic usage\n\nThe simplest implementation wraps a trigger element and tooltip content:\n\n```jsx live-dev\nconst App = () => (\n <Tooltip.Root>\n <Button>Hover me</Button>\n <Tooltip.Content>\n Helpful information appears here\n </Tooltip.Content>\n </Tooltip.Root>\n)\n```\n\n## Usage examples\n\n### Placement options\n\nThe `placement` prop controls where the tooltip appears relative to the trigger element. The default placement is `\"bottom\"`:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Tooltip.Root>\n <Button>Top</Button>\n <Tooltip.Content placement=\"top\">\n Top placement\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <Button>Bottom</Button>\n <Tooltip.Content placement=\"bottom\">\n Bottom placement\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <Button>Left</Button>\n <Tooltip.Content placement=\"left\">\n Left placement\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <Button>Right</Button>\n <Tooltip.Content placement=\"right\">\n Right placement\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n)\n```\n\n**Available placements:**\n\nThe component supports 26 placement options:\n- **Basic**: `top`, `bottom`, `left`, `right`, `start`, `end`\n- **With alignment**: `top left`, `top right`, `top start`, `top end`, `bottom left`, `bottom right`, `bottom start`, `bottom end`, `left top`, `left bottom`, `right top`, `right bottom`, `start top`, `start bottom`, `end top`, `end bottom`\n\n### Delay configuration\n\nControl when tooltips appear and disappear using `delay` and `closeDelay` props on `Tooltip.Root`:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"400\" wrap=\"wrap\">\n <Tooltip.Root delay={0} closeDelay={0}>\n <Button>Instant</Button>\n <Tooltip.Content>\n Shows immediately\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root delay={300} closeDelay={200}>\n <Button>Default timing</Button>\n <Tooltip.Content>\n 300ms delay to show, 200ms to hide\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root delay={1000}>\n <Button>Slow</Button>\n <Tooltip.Content>\n 1000ms delay to show\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n)\n```\n\n**Timing considerations:**\n- `delay`: Time in milliseconds before the tooltip appears (default: `300`)\n- `closeDelay`: Time in milliseconds before the tooltip disappears (default: `200`)\n- Shorter delays improve responsiveness but may feel too aggressive\n- Longer delays reduce accidental triggers but may feel sluggish\n\n### Complex content\n\nTooltip content can include multiple elements using layout components:\n\n```jsx live-dev\nconst App = () => (\n <Tooltip.Root>\n <Button>Feature info</Button>\n <Tooltip.Content>\n <Stack direction=\"column\" gap=\"100\">\n <Text fontWeight=\"600\" fontSize=\"sm\">Premium Feature</Text>\n <Text fontSize=\"xs\">\n Available on Pro and Enterprise plans\n </Text>\n </Stack>\n </Tooltip.Content>\n </Tooltip.Root>\n)\n```\n\n### With icon buttons\n\nA common pattern is providing context for icon-only buttons:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"row\" gap=\"300\">\n <Tooltip.Root>\n <IconButton aria-label=\"Edit\">\n <Icons.Edit />\n </IconButton>\n <Tooltip.Content placement=\"top\">\n Edit item\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <IconButton aria-label=\"Delete\">\n <Icons.Delete />\n </IconButton>\n <Tooltip.Content placement=\"top\">\n Delete item\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <IconButton aria-label=\"Share\">\n <Icons.Share />\n </IconButton>\n <Tooltip.Content placement=\"top\">\n Share with team\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n)\n```\n\n### With focusable elements\n\nTooltips work with any focusable element. For non-interactive elements like Text, use `MakeElementFocusable`:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"400\">\n <Tooltip.Root>\n <MakeElementFocusable>\n <Text\n as=\"span\"\n textDecoration=\"underline\"\n cursor=\"help\"\n >\n Hover or focus me\n </Text>\n </MakeElementFocusable>\n <Tooltip.Content>\n This text element is focusable and shows a tooltip\n </Tooltip.Content>\n </Tooltip.Root>\n\n <Tooltip.Root>\n <Button variant=\"ghost\">\n <Icons.Info />\n Learn more\n </Button>\n <Tooltip.Content placement=\"right\">\n Opens detailed documentation\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n)\n```\n\n### Disabling the tooltip\n\nUse the `isDisabled` prop on `Tooltip.Root` to suppress the tooltip without\nremoving the trigger from the tree. When `isDisabled` is `true`, hover and focus\nlisteners are inactive, the overlay never opens, and no `aria-describedby` is\nadded to the trigger. The trigger element renders unchanged with the same DOM\nand focus order.\n\nThis is useful when tooltip text is conditionally irrelevant (e.g. the trigger\nlabel already conveys the information) or when a parent feature flag should\nsuppress all tooltips in a region.\n\n```jsx live-dev\nconst App = () => {\n const [isDisabled, setIsDisabled] = useState(true);\n return (\n <Stack direction=\"row\" alignItems=\"center\" gap=\"400\">\n <Switch isSelected={isDisabled} onChange={setIsDisabled}>\n Tooltip disabled\n </Switch>\n <Tooltip.Root isDisabled={isDisabled}>\n <Button>Hover me</Button>\n <Tooltip.Content>\n This tooltip can be toggled\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n );\n}\n```\n\n> **Note:** `isDisabled` on `Tooltip.Root` disables the *tooltip overlay*. To\n> keep a tooltip visible on a *disabled button*, see\n> [With disabled buttons](#with-disabled-buttons) below.\n\n### With disabled buttons\n\nA natively disabled button cannot host a tooltip: the `disabled` attribute\nremoves it from the tab order and stops it firing hover/focus events, so the\ntooltip has nothing to open from. This is unfortunate precisely when a tooltip\nis most useful — explaining _why_ an action is unavailable.\n\nPass `allowFocusWhenDisabled` together with `isDisabled` on the `Button`. The\nbutton stays focusable and hoverable and is announced as disabled via\n`aria-disabled` (instead of the native `disabled` attribute), while its action\nstays fully suppressed. The tooltip then opens on hover and keyboard focus like\nany other trigger.\n\n```jsx live-dev\nconst App = () => (\n <Tooltip.Root>\n <Button isDisabled allowFocusWhenDisabled>\n Publish\n </Button>\n <Tooltip.Content>\n Complete all required fields before publishing.\n </Tooltip.Content>\n </Tooltip.Root>\n)\n```\n\nPrefer this to wrapping a disabled button in `MakeElementFocusable`: the\n`allowFocusWhenDisabled` path keeps the disabled semantics on the button itself\n(`aria-disabled`) and suppresses activation, whereas a focusable wrapper only\nadds focus and describes the wrong element.\n\n## Component requirements\n\n## Accessibility\n\nThe Tooltip component is built with React Aria Components and provides accessible tooltip behavior by default. The component:\n\n- Automatically associates tooltip content with the trigger using `aria-describedby`\n- Assigns the `tooltip` ARIA role to the content\n- Shows on both hover and keyboard focus\n- Hides when focus leaves or `Escape` is pressed\n\n**Important:** Ensure the trigger element has an accessible label. For icon-only buttons, always provide an `aria-label`:\n\n```tsx\n<Tooltip.Root>\n <IconButton aria-label=\"Delete item\">\n <Icons.Delete />\n </IconButton>\n <Tooltip.Content>Delete this item</Tooltip.Content>\n</Tooltip.Root>\n```\n\nIf your use case requires tracking and analytics for this component, it is good practice to add a **persistent**, **unique** id to the trigger element:\n\n```tsx\nconst PERSISTENT_ID = \"delete-button-tooltip\";\n\nexport const Example = () => (\n <Tooltip.Root>\n <IconButton id={PERSISTENT_ID} aria-label=\"Delete\">\n <Icons.Delete />\n </IconButton>\n <Tooltip.Content>Delete item</Tooltip.Content>\n </Tooltip.Root>\n);\n```\n\n#### Keyboard navigation\n\nThe component supports full keyboard interaction:\n\n- `Tab` / `Shift+Tab`: Move focus to/from the trigger element\n- Tooltip appears automatically when trigger receives focus\n- `Escape`: Dismisses the tooltip (if shown)\n- Tooltip disappears automatically when focus leaves the trigger\n\n### Trigger element requirements\n\nThe first child of `Tooltip.Root` must be a focusable element:\n\n- Use buttons, links, or other interactive elements (recommended)\n- For non-interactive elements like Text or div, wrap with `MakeElementFocusable`\n- Avoid wrapping non-focusable elements without proper handling\n\n```tsx\n// ✅ Good - button is natively focusable\n<Tooltip.Root>\n <Button>Trigger</Button>\n <Tooltip.Content>Content</Tooltip.Content>\n</Tooltip.Root>\n\n// ✅ Good - non-interactive element with MakeElementFocusable\n<Tooltip.Root>\n <MakeElementFocusable>\n <Text as=\"span\">Trigger</Text>\n </MakeElementFocusable>\n <Tooltip.Content>Content</Tooltip.Content>\n</Tooltip.Root>\n\n// ❌ Bad - Text with tabIndex won't work properly\n<Tooltip.Root>\n <Text as=\"span\" tabIndex={0}>Trigger</Text>\n <Tooltip.Content>Content</Tooltip.Content>\n</Tooltip.Root>\n```\n\n## API reference\n\n<PropsTable id=\"Tooltip\" />\n\n## Common patterns\n\n### Info tooltips for form fields\n\nProvide additional context for form fields without cluttering the interface:\n\n```jsx live-dev\nconst App = () => {\n const [value, setValue] = useState('');\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Stack direction=\"row\" gap=\"200\" alignItems=\"center\">\n <Text as=\"label\" htmlFor=\"username-input\" fontWeight=\"500\">\n Username\n </Text>\n <Tooltip.Root>\n <IconButton\n aria-label=\"Username requirements\"\n size=\"sm\"\n variant=\"ghost\"\n >\n <Icons.Info />\n </IconButton>\n <Tooltip.Content placement=\"right\">\n <Stack direction=\"column\" gap=\"100\">\n <Text fontSize=\"xs\" fontWeight=\"600\">Requirements:</Text>\n <Text fontSize=\"xs\">• 3-20 characters</Text>\n <Text fontSize=\"xs\">• Letters and numbers only</Text>\n <Text fontSize=\"xs\">• No spaces</Text>\n </Stack>\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n <TextInput\n id=\"username-input\"\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder=\"Enter username\"\n />\n </Stack>\n );\n}\n```\n\n### Action descriptions for icon buttons\n\nClarify what icon buttons do before users click them:\n\n```jsx live-dev\nconst App = () => {\n const [copied, setCopied] = useState(false);\n\n const handleCopy = () => {\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n };\n\n return (\n <Stack direction=\"column\" gap=\"400\">\n <Text fontSize=\"sm\">\n Code snippet:\n </Text>\n <Stack\n direction=\"row\"\n gap=\"300\"\n padding=\"300\"\n backgroundColor=\"neutral.3\"\n borderRadius=\"200\"\n alignItems=\"center\"\n >\n <Text fontFamily=\"mono\" fontSize=\"sm\" flex=\"1\">\n npm install @commercetools/nimbus\n </Text>\n\n <Tooltip.Root>\n <IconButton\n aria-label=\"Copy to clipboard\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={handleCopy}\n >\n {copied ? <Icons.Check /> : <Icons.ContentCopy />}\n </IconButton>\n <Tooltip.Content placement=\"top\">\n {copied ? 'Copied!' : 'Copy to clipboard'}\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n </Stack>\n );\n}\n```\n\n### Contextual help in data tables\n\nProvide explanations for table headers or data values:\n\n```jsx live-dev\nconst App = () => (\n <Stack direction=\"column\" gap=\"300\">\n <Stack direction=\"row\" gap=\"400\" paddingX=\"300\" paddingY=\"200\">\n <Stack direction=\"row\" gap=\"100\" alignItems=\"center\" flex=\"1\">\n <Text fontWeight=\"600\" fontSize=\"sm\">Product Name</Text>\n </Stack>\n\n <Stack direction=\"row\" gap=\"100\" alignItems=\"center\" width=\"150px\">\n <Text fontWeight=\"600\" fontSize=\"sm\">Conversion Rate</Text>\n <Tooltip.Root>\n <IconButton\n aria-label=\"About conversion rate\"\n size=\"sm\"\n variant=\"ghost\"\n >\n <Icons.HelpOutline />\n </IconButton>\n <Tooltip.Content placement=\"top\">\n <Text fontSize=\"xs\">\n Percentage of visitors who make a purchase\n </Text>\n </Tooltip.Content>\n </Tooltip.Root>\n </Stack>\n\n <Stack direction=\"row\" gap=\"100\" alignItems=\"center\" width=\"100px\">\n <Text fontWeight=\"600\" fontSize=\"sm\">Status</Text>\n </Stack>\n </Stack>\n\n <Stack direction=\"row\" gap=\"400\" paddingX=\"300\" paddingY=\"200\" backgroundColor=\"neutral.2\">\n <Text fontSize=\"sm\" flex=\"1\">Premium Widget</Text>\n <Text fontSize=\"sm\" width=\"150px\">24.5%</Text>\n <Text fontSize=\"sm\" width=\"100px\">Active</Text>\n </Stack>\n </Stack>\n)\n```\n\n## Testing your implementation\n\nThese examples demonstrate how to test your implementation when using Tooltip within your application. As the component's internal functionality is already tested by Nimbus, these patterns help you verify your integration and application-specific logic.\n\n### Basic Rendering Tests\n\nVerify the tooltip renders and displays content on interaction\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Basic rendering\", () => {\n it(\"renders trigger element\", () => {\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Hover me</Button>\n <Tooltip.Content>Tooltip text</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n expect(\n screen.getByRole(\"button\", { name: /hover me/i })\n ).toBeInTheDocument();\n });\n\n it(\"shows tooltip content on focus\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Hover me</Button>\n <Tooltip.Content>Tooltip text</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Focus the button using tab\n await user.tab();\n\n await waitFor(() => {\n expect(screen.getByRole(\"tooltip\")).toBeInTheDocument();\n expect(screen.getByText(\"Tooltip text\")).toBeInTheDocument();\n });\n });\n});\n```\n\n### Interaction Tests\n\nTest focus interactions (hover tests require browser environment)\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Interactions\", () => {\n it(\"shows tooltip on focus\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Action Button</Button>\n <Tooltip.Content>Action tooltip</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Use userEvent.tab() instead of element.focus() to avoid act() warnings\n await user.tab();\n\n await waitFor(() => {\n expect(screen.getByRole(\"tooltip\")).toBeInTheDocument();\n expect(screen.getByText(\"Action tooltip\")).toBeInTheDocument();\n });\n });\n\n it(\"hides tooltip when focus leaves\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>First</Button>\n <Tooltip.Content>First tooltip</Tooltip.Content>\n </Tooltip.Root>\n <Button>Second</Button>\n </NimbusProvider>\n );\n\n // Focus first button\n await user.tab();\n await waitFor(() => {\n expect(screen.getByRole(\"tooltip\")).toBeInTheDocument();\n });\n\n // Focus second button\n await user.tab();\n await waitFor(() => {\n expect(screen.queryByRole(\"tooltip\")).not.toBeInTheDocument();\n });\n });\n});\n```\n\n### Testing Placement\n\nVerify tooltip positioning\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Placement\", () => {\n it(\"renders with top placement\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Trigger</Button>\n <Tooltip.Content placement=\"top\">Top tooltip</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Tab to focus the button\n await user.tab();\n\n await waitFor(() => {\n const tooltip = screen.getByRole(\"tooltip\");\n expect(tooltip).toBeInTheDocument();\n expect(tooltip).toHaveTextContent(\"Top tooltip\");\n });\n });\n});\n```\n\n### Testing with Portal Content\n\nTooltips render in a portal, so query from document when needed\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Portal rendering\", () => {\n it(\"renders tooltip in portal\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root>\n <Button>Show tooltip</Button>\n <Tooltip.Content>Portal content</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Use userEvent.tab() instead of element.focus() to avoid act() warnings\n await user.tab();\n\n // Wait for portal content to appear\n await waitFor(() => {\n const tooltip = screen.getByRole(\"tooltip\");\n expect(tooltip).toBeInTheDocument();\n });\n });\n});\n```\n\n### Testing Custom Delay Timing\n\nVerify delay configuration works as expected\n\n```tsx\nimport { describe, it, expect } from \"vitest\";\nimport { render, screen, waitFor } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport { Tooltip, Button, NimbusProvider } from \"@commercetools/nimbus\";\n\ndescribe(\"Tooltip - Timing\", () => {\n it(\"respects custom delay\", async () => {\n const user = userEvent.setup();\n render(\n <NimbusProvider>\n <Tooltip.Root delay={0} closeDelay={0}>\n <Button>Instant</Button>\n <Tooltip.Content>Shows immediately</Tooltip.Content>\n </Tooltip.Root>\n </NimbusProvider>\n );\n\n // Use userEvent.tab() instead of element.focus() to avoid act() warnings\n await user.tab();\n\n // With delay={0}, tooltip should appear immediately\n await waitFor(\n () => {\n expect(screen.getByRole(\"tooltip\")).toBeInTheDocument();\n },\n { timeout: 100 }\n );\n });\n});\n```\n\n\n## Resources\n\n- [Storybook](https://nimbus-storybook.vercel.app/?path=/docs/components-tooltip-tooltip--docs)\n- [React Aria Tooltip](https://react-spectrum.adobe.com/react-aria/Tooltip.html)\n- [ARIA Tooltip Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/)\n",
169
169
  "toc": [
170
170
  {
171
171
  "value": "Getting started",
@@ -264,6 +264,28 @@
264
264
  ],
265
265
  "parent": "root"
266
266
  },
267
+ {
268
+ "value": "Disabling the tooltip",
269
+ "href": "#disabling-the-tooltip",
270
+ "depth": 3,
271
+ "numbering": [
272
+ 1,
273
+ 2,
274
+ 6
275
+ ],
276
+ "parent": "root"
277
+ },
278
+ {
279
+ "value": "With disabled buttons",
280
+ "href": "#with-disabled-buttons",
281
+ "depth": 3,
282
+ "numbering": [
283
+ 1,
284
+ 2,
285
+ 7
286
+ ],
287
+ "parent": "root"
288
+ },
267
289
  {
268
290
  "value": "Component requirements",
269
291
  "href": "#component-requirements",