@sproutsocial/seeds-react-tooltip 1.0.8 → 1.0.10

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.
@@ -8,14 +8,14 @@ $ tsup --dts
8
8
  CLI Cleaning output folder
9
9
  CJS Build start
10
10
  ESM Build start
11
- ESM dist/esm/index.js 4.49 KB
12
- ESM dist/esm/index.js.map 10.17 KB
13
- ESM ⚡️ Build success in 86ms
14
- CJS dist/index.js 6.48 KB
15
- CJS dist/index.js.map 10.28 KB
16
- CJS ⚡️ Build success in 94ms
11
+ CJS dist/index.js 6.50 KB
12
+ CJS dist/index.js.map 10.32 KB
13
+ CJS ⚡️ Build success in 71ms
14
+ ESM dist/esm/index.js 4.51 KB
15
+ ESM dist/esm/index.js.map 10.21 KB
16
+ ESM ⚡️ Build success in 70ms
17
17
  DTS Build start
18
- DTS ⚡️ Build success in 5563ms
18
+ DTS ⚡️ Build success in 5899ms
19
19
  DTS dist/index.d.ts 1.91 KB
20
20
  DTS dist/index.d.mts 1.91 KB
21
- Done in 6.75s.
21
+ Done in 7.13s.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @sproutsocial/seeds-react-tooltip
2
2
 
3
+ ## 1.0.10
4
+
5
+ ### Patch Changes
6
+
7
+ - @sproutsocial/seeds-react-popout@2.4.11
8
+
9
+ ## 1.0.9
10
+
11
+ ### Patch Changes
12
+
13
+ - 5e20b0b: Fix Tooltip ARIA attributes for improved accessibility
14
+
15
+ - `aria-describedby` is now only set when the tooltip is open, rather than always being present. This ensures the association between the trigger element and tooltip content is only established when the tooltip is visible.
16
+ - `aria-expanded` is now only set when the child element already has this attribute (e.g., for drawer/disclosure patterns). Previously, it was incorrectly set to the tooltip's open state for all elements.
17
+
18
+ These changes improve screen reader behavior and align with ARIA best practices for tooltip components.
19
+
20
+ - Updated dependencies [8f6ba8d]
21
+ - @sproutsocial/seeds-react-theme@3.2.0
22
+ - @sproutsocial/seeds-react-box@1.1.6
23
+ - @sproutsocial/seeds-react-popout@2.4.10
24
+
3
25
  ## 1.0.8
4
26
 
5
27
  ### Patch Changes
package/dist/esm/index.js CHANGED
@@ -150,9 +150,9 @@ var Tooltip = ({
150
150
  whiteSpace: "nowrap"
151
151
  } : {},
152
152
  children: React.isValidElement(children) ? React.cloneElement(children, {
153
- //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that propery takes priority */
154
- "aria-expanded": hasAttribute(children, "aria-expanded") ? children.props["aria-expanded"] : isOpen,
155
- "aria-describedby": id
153
+ //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that property takes priority */
154
+ "aria-expanded": hasAttribute(children, "aria-expanded") ? children.props["aria-expanded"] : void 0,
155
+ "aria-describedby": isOpen ? id : void 0
156
156
  }) : children
157
157
  }
158
158
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/Tooltip.tsx","../../src/styles.ts","../../src/TooltipTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport { useState, useEffect } from \"react\";\nimport MOTION from \"@sproutsocial/seeds-motion/unitless\";\nimport Popout from \"@sproutsocial/seeds-react-popout\";\nimport { StyledTooltipContent } from \"./styles\";\nimport type { TypeTooltipProps, TypeTooltipContent } from \"./TooltipTypes\";\n\nlet idCounter = 0;\n\nconst hasAttribute = (child: React.ReactNode, attribute: string) => {\n return React.isValidElement(child) && child.props[attribute] !== undefined;\n};\n\n/** Tooltip Styled Popout wrapper for handling events */\nconst TooltipBubble = ({\n appearance = \"pill\",\n children,\n onFocus,\n onBlur,\n ...rest\n}: TypeTooltipContent) => {\n // @ts-ignore Will fix during refactor\n const handleFocus = (e) => {\n onFocus(e);\n };\n // @ts-ignore Will fix during refactor\n const handleBlur = (e) => {\n onBlur(e);\n };\n return (\n <StyledTooltipContent\n role=\"tooltip\"\n appearance={appearance}\n borderRadius={appearance === \"box\" ? 500 : \"5000em\"}\n px={400}\n py={appearance === \"box\" ? 400 : 200}\n m={200}\n color=\"text.body\"\n bg=\"container.background.base\"\n boxShadow=\"medium\"\n border={500}\n borderColor=\"container.border.base\"\n onFocus={handleFocus}\n onBlur={handleBlur}\n onMouseEnter={handleFocus}\n onMouseLeave={handleBlur}\n tabIndex={0}\n {...rest}\n >\n {children}\n </StyledTooltipContent>\n );\n};\n\n/** Core component */\nconst Tooltip = ({\n content,\n children,\n enterDelay = MOTION.MOTION_DURATION_FAST * 1000,\n placement = \"auto\",\n appearance,\n zIndex = 7,\n qa,\n popoutProps,\n truncated = false,\n onFocus,\n onBlur,\n ...rest\n}: TypeTooltipProps) => {\n const [shouldShow, setShouldShow] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [id] = useState(`Racine-tooltip-${idCounter++}`);\n const isInvalidContent = content === null || content === undefined;\n\n // @ts-ignore Will fix during refactor\n const show = (e) => {\n onFocus?.(e);\n setShouldShow(true);\n };\n // @ts-ignore Will fix during refactor\n const hide = (e) => {\n onBlur?.(e);\n setShouldShow(false);\n };\n\n const exitDelay = MOTION.MOTION_DURATION_FAST * 1000;\n const defaultAppearance =\n appearance || (typeof content === \"object\" ? \"box\" : \"pill\");\n\n /** Handles all the logic around whether to display/not display */\n useEffect(() => {\n const documentBody = document.body;\n let timeout;\n const onEsc = (e: KeyboardEvent): void => {\n // older browsers use \"Esc\"\n if ([\"Escape\", \"Esc\"].includes(e.key)) {\n setIsOpen(false);\n setShouldShow(false);\n }\n };\n\n if (shouldShow) {\n timeout = setTimeout(() => setIsOpen(true), enterDelay);\n } else {\n timeout = setTimeout(() => {\n setIsOpen(false);\n }, exitDelay);\n }\n\n // We only want listeners from the tooltip if its open in the first place\n if (isOpen) {\n documentBody.addEventListener(\"keydown\", onEsc, { capture: true });\n }\n return () => {\n documentBody.removeEventListener(\"keydown\", onEsc, { capture: true });\n clearTimeout(timeout);\n };\n }, [isOpen, setShouldShow, shouldShow, enterDelay, exitDelay]);\n\n /** The wrapped content of whats inside the Tooltip */\n const TooltipContent = () => (\n <TooltipBubble\n appearance={defaultAppearance}\n onFocus={show}\n onBlur={hide}\n aria-expanded={isOpen}\n id={id}\n {...rest}\n >\n {content}\n </TooltipBubble>\n );\n\n return (\n <Popout\n content={!isInvalidContent ? TooltipContent : undefined}\n isOpen={isOpen}\n placement={placement}\n qa={{\n \"data-qa-tooltip\": id,\n ...qa,\n }}\n id={id + \"-wrapper\"}\n focusOnContent={false}\n zIndex={zIndex}\n aria-haspopup=\"false\"\n display={truncated ? \"flex\" : undefined}\n disableWrapperAria={true} // required so that the child span doesnt take in redundant aria props\n {...popoutProps}\n >\n <span\n onBlur={hide}\n onFocus={show}\n onMouseEnter={show}\n onMouseLeave={hide}\n style={\n truncated\n ? {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }\n : {}\n }\n >\n {React.isValidElement(children)\n ? React.cloneElement(children as React.ReactElement, {\n //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that propery takes priority */\n \"aria-expanded\": hasAttribute(children, \"aria-expanded\")\n ? children.props[\"aria-expanded\"]\n : isOpen,\n \"aria-describedby\": id,\n })\n : children}\n </span>\n </Popout>\n );\n};\n\nexport default Tooltip;\n","import styled from \"styled-components\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport type { TypeTooltipContent } from \"./TooltipTypes\";\n\nexport const StyledTooltipContent = styled(Box)<\n Pick<TypeTooltipContent, \"appearance\">\n>`\n font-family: ${(props) => props.theme.fontFamily};\n ${(props) => props.theme.typography[200]}\n text-align: ${(props) => (props.appearance === \"box\" ? \"left\" : \"center\")};\n`;\n","import * as React from \"react\";\nimport type { TypePopoutProps } from \"@sproutsocial/seeds-react-popout\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\n\nexport interface TypeTooltipProps\n extends Omit<\n TypeBoxProps,\n \"children\" | \"content\" | \"onMouseEnter\" | \"onMouseLeave\"\n > {\n /** The content that the tooltip should be attached to. Hovering or focusing this element will cause the tooltip to appear */\n children: React.ReactNode;\n\n /** The content to be displayed within the tooltip. If there is no content, just the children are rendered */\n content: React.ReactNode;\n\n /** The placement of the tooltip in relation to the children */\n placement?: TypePopoutProps[\"placement\"];\n\n /** The time (in ms) that a user has to be hovered/focused before the tooltip will appear */\n enterDelay?: number;\n\n /** Used to override the appearance of the Tooltip content. By default, strings will have the 'pill' appearance, and more complex content will have the 'box' appearance. You can change those defaults by setting this prop. */\n appearance?: \"pill\" | \"box\";\n qa?: object;\n zIndex?: number;\n\n /** Props to be spread onto the underlying Popout component */\n popoutProps?: Partial<TypePopoutProps>;\n\n /** Truncates text into a single line with ellipsis */\n truncated?: boolean;\n\n ariaProps?: Record<string, string>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface TypeTooltipContent\n extends Pick<TypeTooltipProps, \"appearance\" | \"children\"> {\n onFocus: (e: React.FocusEvent<HTMLDivElement, FocusEvent>) => void;\n onBlur: (e: React.FocusEvent<HTMLDivElement, FocusEvent>) => void;\n}\n","import Tooltip from \"./Tooltip\";\n\nexport default Tooltip;\nexport { Tooltip };\nexport * from \"./TooltipTypes\";\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/Tooltip.tsx","../../src/styles.ts","../../src/TooltipTypes.ts","../../src/index.ts"],"sourcesContent":["import * as React from \"react\";\nimport { useState, useEffect } from \"react\";\nimport MOTION from \"@sproutsocial/seeds-motion/unitless\";\nimport Popout from \"@sproutsocial/seeds-react-popout\";\nimport { StyledTooltipContent } from \"./styles\";\nimport type { TypeTooltipProps, TypeTooltipContent } from \"./TooltipTypes\";\n\nlet idCounter = 0;\n\nconst hasAttribute = (child: React.ReactNode, attribute: string) => {\n return React.isValidElement(child) && child.props[attribute] !== undefined;\n};\n\n/** Tooltip Styled Popout wrapper for handling events */\nconst TooltipBubble = ({\n appearance = \"pill\",\n children,\n onFocus,\n onBlur,\n ...rest\n}: TypeTooltipContent) => {\n // @ts-ignore Will fix during refactor\n const handleFocus = (e) => {\n onFocus(e);\n };\n // @ts-ignore Will fix during refactor\n const handleBlur = (e) => {\n onBlur(e);\n };\n return (\n <StyledTooltipContent\n role=\"tooltip\"\n appearance={appearance}\n borderRadius={appearance === \"box\" ? 500 : \"5000em\"}\n px={400}\n py={appearance === \"box\" ? 400 : 200}\n m={200}\n color=\"text.body\"\n bg=\"container.background.base\"\n boxShadow=\"medium\"\n border={500}\n borderColor=\"container.border.base\"\n onFocus={handleFocus}\n onBlur={handleBlur}\n onMouseEnter={handleFocus}\n onMouseLeave={handleBlur}\n tabIndex={0}\n {...rest}\n >\n {children}\n </StyledTooltipContent>\n );\n};\n\n/** Core component */\nconst Tooltip = ({\n content,\n children,\n enterDelay = MOTION.MOTION_DURATION_FAST * 1000,\n placement = \"auto\",\n appearance,\n zIndex = 7,\n qa,\n popoutProps,\n truncated = false,\n onFocus,\n onBlur,\n ...rest\n}: TypeTooltipProps) => {\n const [shouldShow, setShouldShow] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [id] = useState(`Racine-tooltip-${idCounter++}`);\n const isInvalidContent = content === null || content === undefined;\n\n // @ts-ignore Will fix during refactor\n const show = (e) => {\n onFocus?.(e);\n setShouldShow(true);\n };\n // @ts-ignore Will fix during refactor\n const hide = (e) => {\n onBlur?.(e);\n setShouldShow(false);\n };\n\n const exitDelay = MOTION.MOTION_DURATION_FAST * 1000;\n const defaultAppearance =\n appearance || (typeof content === \"object\" ? \"box\" : \"pill\");\n\n /** Handles all the logic around whether to display/not display */\n useEffect(() => {\n const documentBody = document.body;\n let timeout;\n const onEsc = (e: KeyboardEvent): void => {\n // older browsers use \"Esc\"\n if ([\"Escape\", \"Esc\"].includes(e.key)) {\n setIsOpen(false);\n setShouldShow(false);\n }\n };\n\n if (shouldShow) {\n timeout = setTimeout(() => setIsOpen(true), enterDelay);\n } else {\n timeout = setTimeout(() => {\n setIsOpen(false);\n }, exitDelay);\n }\n\n // We only want listeners from the tooltip if its open in the first place\n if (isOpen) {\n documentBody.addEventListener(\"keydown\", onEsc, { capture: true });\n }\n return () => {\n documentBody.removeEventListener(\"keydown\", onEsc, { capture: true });\n clearTimeout(timeout);\n };\n }, [isOpen, setShouldShow, shouldShow, enterDelay, exitDelay]);\n\n /** The wrapped content of whats inside the Tooltip */\n const TooltipContent = () => (\n <TooltipBubble\n appearance={defaultAppearance}\n onFocus={show}\n onBlur={hide}\n aria-expanded={isOpen}\n id={id}\n {...rest}\n >\n {content}\n </TooltipBubble>\n );\n\n return (\n <Popout\n content={!isInvalidContent ? TooltipContent : undefined}\n isOpen={isOpen}\n placement={placement}\n qa={{\n \"data-qa-tooltip\": id,\n ...qa,\n }}\n id={id + \"-wrapper\"}\n focusOnContent={false}\n zIndex={zIndex}\n aria-haspopup=\"false\"\n display={truncated ? \"flex\" : undefined}\n disableWrapperAria={true} // required so that the child span doesnt take in redundant aria props\n {...popoutProps}\n >\n <span\n onBlur={hide}\n onFocus={show}\n onMouseEnter={show}\n onMouseLeave={hide}\n style={\n truncated\n ? {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }\n : {}\n }\n >\n {React.isValidElement(children)\n ? React.cloneElement(children as React.ReactElement, {\n //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that property takes priority */\n \"aria-expanded\": hasAttribute(children, \"aria-expanded\")\n ? children.props[\"aria-expanded\"]\n : undefined,\n \"aria-describedby\": isOpen ? id : undefined,\n })\n : children}\n </span>\n </Popout>\n );\n};\n\nexport default Tooltip;\n","import styled from \"styled-components\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport type { TypeTooltipContent } from \"./TooltipTypes\";\n\nexport const StyledTooltipContent = styled(Box)<\n Pick<TypeTooltipContent, \"appearance\">\n>`\n font-family: ${(props) => props.theme.fontFamily};\n ${(props) => props.theme.typography[200]}\n text-align: ${(props) => (props.appearance === \"box\" ? \"left\" : \"center\")};\n`;\n","import * as React from \"react\";\nimport type { TypePopoutProps } from \"@sproutsocial/seeds-react-popout\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\n\nexport interface TypeTooltipProps\n extends Omit<\n TypeBoxProps,\n \"children\" | \"content\" | \"onMouseEnter\" | \"onMouseLeave\"\n > {\n /** The content that the tooltip should be attached to. Hovering or focusing this element will cause the tooltip to appear */\n children: React.ReactNode;\n\n /** The content to be displayed within the tooltip. If there is no content, just the children are rendered */\n content: React.ReactNode;\n\n /** The placement of the tooltip in relation to the children */\n placement?: TypePopoutProps[\"placement\"];\n\n /** The time (in ms) that a user has to be hovered/focused before the tooltip will appear */\n enterDelay?: number;\n\n /** Used to override the appearance of the Tooltip content. By default, strings will have the 'pill' appearance, and more complex content will have the 'box' appearance. You can change those defaults by setting this prop. */\n appearance?: \"pill\" | \"box\";\n qa?: object;\n zIndex?: number;\n\n /** Props to be spread onto the underlying Popout component */\n popoutProps?: Partial<TypePopoutProps>;\n\n /** Truncates text into a single line with ellipsis */\n truncated?: boolean;\n\n ariaProps?: Record<string, string>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface TypeTooltipContent\n extends Pick<TypeTooltipProps, \"appearance\" | \"children\"> {\n onFocus: (e: React.FocusEvent<HTMLDivElement, FocusEvent>) => void;\n onBlur: (e: React.FocusEvent<HTMLDivElement, FocusEvent>) => void;\n}\n","import Tooltip from \"./Tooltip\";\n\nexport default Tooltip;\nexport { Tooltip };\nexport * from \"./TooltipTypes\";\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -187,9 +187,9 @@ var Tooltip = ({
187
187
  whiteSpace: "nowrap"
188
188
  } : {},
189
189
  children: React.isValidElement(children) ? React.cloneElement(children, {
190
- //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that propery takes priority */
191
- "aria-expanded": hasAttribute(children, "aria-expanded") ? children.props["aria-expanded"] : isOpen,
192
- "aria-describedby": id
190
+ //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that property takes priority */
191
+ "aria-expanded": hasAttribute(children, "aria-expanded") ? children.props["aria-expanded"] : void 0,
192
+ "aria-describedby": isOpen ? id : void 0
193
193
  }) : children
194
194
  }
195
195
  )
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/Tooltip.tsx","../src/styles.ts","../src/TooltipTypes.ts"],"sourcesContent":["import Tooltip from \"./Tooltip\";\n\nexport default Tooltip;\nexport { Tooltip };\nexport * from \"./TooltipTypes\";\n","import * as React from \"react\";\nimport { useState, useEffect } from \"react\";\nimport MOTION from \"@sproutsocial/seeds-motion/unitless\";\nimport Popout from \"@sproutsocial/seeds-react-popout\";\nimport { StyledTooltipContent } from \"./styles\";\nimport type { TypeTooltipProps, TypeTooltipContent } from \"./TooltipTypes\";\n\nlet idCounter = 0;\n\nconst hasAttribute = (child: React.ReactNode, attribute: string) => {\n return React.isValidElement(child) && child.props[attribute] !== undefined;\n};\n\n/** Tooltip Styled Popout wrapper for handling events */\nconst TooltipBubble = ({\n appearance = \"pill\",\n children,\n onFocus,\n onBlur,\n ...rest\n}: TypeTooltipContent) => {\n // @ts-ignore Will fix during refactor\n const handleFocus = (e) => {\n onFocus(e);\n };\n // @ts-ignore Will fix during refactor\n const handleBlur = (e) => {\n onBlur(e);\n };\n return (\n <StyledTooltipContent\n role=\"tooltip\"\n appearance={appearance}\n borderRadius={appearance === \"box\" ? 500 : \"5000em\"}\n px={400}\n py={appearance === \"box\" ? 400 : 200}\n m={200}\n color=\"text.body\"\n bg=\"container.background.base\"\n boxShadow=\"medium\"\n border={500}\n borderColor=\"container.border.base\"\n onFocus={handleFocus}\n onBlur={handleBlur}\n onMouseEnter={handleFocus}\n onMouseLeave={handleBlur}\n tabIndex={0}\n {...rest}\n >\n {children}\n </StyledTooltipContent>\n );\n};\n\n/** Core component */\nconst Tooltip = ({\n content,\n children,\n enterDelay = MOTION.MOTION_DURATION_FAST * 1000,\n placement = \"auto\",\n appearance,\n zIndex = 7,\n qa,\n popoutProps,\n truncated = false,\n onFocus,\n onBlur,\n ...rest\n}: TypeTooltipProps) => {\n const [shouldShow, setShouldShow] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [id] = useState(`Racine-tooltip-${idCounter++}`);\n const isInvalidContent = content === null || content === undefined;\n\n // @ts-ignore Will fix during refactor\n const show = (e) => {\n onFocus?.(e);\n setShouldShow(true);\n };\n // @ts-ignore Will fix during refactor\n const hide = (e) => {\n onBlur?.(e);\n setShouldShow(false);\n };\n\n const exitDelay = MOTION.MOTION_DURATION_FAST * 1000;\n const defaultAppearance =\n appearance || (typeof content === \"object\" ? \"box\" : \"pill\");\n\n /** Handles all the logic around whether to display/not display */\n useEffect(() => {\n const documentBody = document.body;\n let timeout;\n const onEsc = (e: KeyboardEvent): void => {\n // older browsers use \"Esc\"\n if ([\"Escape\", \"Esc\"].includes(e.key)) {\n setIsOpen(false);\n setShouldShow(false);\n }\n };\n\n if (shouldShow) {\n timeout = setTimeout(() => setIsOpen(true), enterDelay);\n } else {\n timeout = setTimeout(() => {\n setIsOpen(false);\n }, exitDelay);\n }\n\n // We only want listeners from the tooltip if its open in the first place\n if (isOpen) {\n documentBody.addEventListener(\"keydown\", onEsc, { capture: true });\n }\n return () => {\n documentBody.removeEventListener(\"keydown\", onEsc, { capture: true });\n clearTimeout(timeout);\n };\n }, [isOpen, setShouldShow, shouldShow, enterDelay, exitDelay]);\n\n /** The wrapped content of whats inside the Tooltip */\n const TooltipContent = () => (\n <TooltipBubble\n appearance={defaultAppearance}\n onFocus={show}\n onBlur={hide}\n aria-expanded={isOpen}\n id={id}\n {...rest}\n >\n {content}\n </TooltipBubble>\n );\n\n return (\n <Popout\n content={!isInvalidContent ? TooltipContent : undefined}\n isOpen={isOpen}\n placement={placement}\n qa={{\n \"data-qa-tooltip\": id,\n ...qa,\n }}\n id={id + \"-wrapper\"}\n focusOnContent={false}\n zIndex={zIndex}\n aria-haspopup=\"false\"\n display={truncated ? \"flex\" : undefined}\n disableWrapperAria={true} // required so that the child span doesnt take in redundant aria props\n {...popoutProps}\n >\n <span\n onBlur={hide}\n onFocus={show}\n onMouseEnter={show}\n onMouseLeave={hide}\n style={\n truncated\n ? {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }\n : {}\n }\n >\n {React.isValidElement(children)\n ? React.cloneElement(children as React.ReactElement, {\n //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that propery takes priority */\n \"aria-expanded\": hasAttribute(children, \"aria-expanded\")\n ? children.props[\"aria-expanded\"]\n : isOpen,\n \"aria-describedby\": id,\n })\n : children}\n </span>\n </Popout>\n );\n};\n\nexport default Tooltip;\n","import styled from \"styled-components\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport type { TypeTooltipContent } from \"./TooltipTypes\";\n\nexport const StyledTooltipContent = styled(Box)<\n Pick<TypeTooltipContent, \"appearance\">\n>`\n font-family: ${(props) => props.theme.fontFamily};\n ${(props) => props.theme.typography[200]}\n text-align: ${(props) => (props.appearance === \"box\" ? \"left\" : \"center\")};\n`;\n","import * as React from \"react\";\nimport type { TypePopoutProps } from \"@sproutsocial/seeds-react-popout\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\n\nexport interface TypeTooltipProps\n extends Omit<\n TypeBoxProps,\n \"children\" | \"content\" | \"onMouseEnter\" | \"onMouseLeave\"\n > {\n /** The content that the tooltip should be attached to. Hovering or focusing this element will cause the tooltip to appear */\n children: React.ReactNode;\n\n /** The content to be displayed within the tooltip. If there is no content, just the children are rendered */\n content: React.ReactNode;\n\n /** The placement of the tooltip in relation to the children */\n placement?: TypePopoutProps[\"placement\"];\n\n /** The time (in ms) that a user has to be hovered/focused before the tooltip will appear */\n enterDelay?: number;\n\n /** Used to override the appearance of the Tooltip content. By default, strings will have the 'pill' appearance, and more complex content will have the 'box' appearance. You can change those defaults by setting this prop. */\n appearance?: \"pill\" | \"box\";\n qa?: object;\n zIndex?: number;\n\n /** Props to be spread onto the underlying Popout component */\n popoutProps?: Partial<TypePopoutProps>;\n\n /** Truncates text into a single line with ellipsis */\n truncated?: boolean;\n\n ariaProps?: Record<string, string>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface TypeTooltipContent\n extends Pick<TypeTooltipProps, \"appearance\" | \"children\"> {\n onFocus: (e: React.FocusEvent<HTMLDivElement, FocusEvent>) => void;\n onBlur: (e: React.FocusEvent<HTMLDivElement, FocusEvent>) => void;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,YAAuB;AACvB,mBAAoC;AACpC,sBAAmB;AACnB,gCAAmB;;;ACHnB,+BAAmB;AACnB,6BAAgB;AAGT,IAAM,2BAAuB,yBAAAA,SAAO,uBAAAC,OAAG;AAAA,iBAG7B,CAAC,UAAU,MAAM,MAAM,UAAU;AAAA,IAC9C,CAAC,UAAU,MAAM,MAAM,WAAW,GAAG,CAAC;AAAA,gBAC1B,CAAC,UAAW,MAAM,eAAe,QAAQ,SAAS,QAAS;AAAA;;;ADqBvE;AAvBJ,IAAI,YAAY;AAEhB,IAAM,eAAe,CAAC,OAAwB,cAAsB;AAClE,SAAa,qBAAe,KAAK,KAAK,MAAM,MAAM,SAAS,MAAM;AACnE;AAGA,IAAM,gBAAgB,CAAC;AAAA,EACrB,aAAa;AAAA,EACb;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAA0B;AAExB,QAAM,cAAc,CAAC,MAAM;AACzB,YAAQ,CAAC;AAAA,EACX;AAEA,QAAM,aAAa,CAAC,MAAM;AACxB,WAAO,CAAC;AAAA,EACV;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,cAAc,eAAe,QAAQ,MAAM;AAAA,MAC3C,IAAI;AAAA,MACJ,IAAI,eAAe,QAAQ,MAAM;AAAA,MACjC,GAAG;AAAA,MACH,OAAM;AAAA,MACN,IAAG;AAAA,MACH,WAAU;AAAA,MACV,QAAQ;AAAA,MACR,aAAY;AAAA,MACZ,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,cAAc;AAAA,MACd,cAAc;AAAA,MACd,UAAU;AAAA,MACT,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAEJ;AAGA,IAAM,UAAU,CAAC;AAAA,EACf;AAAA,EACA;AAAA,EACA,aAAa,gBAAAC,QAAO,uBAAuB;AAAA,EAC3C,YAAY;AAAA,EACZ;AAAA,EACA,SAAS;AAAA,EACT;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,GAAG;AACL,MAAwB;AACtB,QAAM,CAAC,YAAY,aAAa,QAAI,uBAAS,KAAK;AAClD,QAAM,CAAC,QAAQ,SAAS,QAAI,uBAAS,KAAK;AAC1C,QAAM,CAAC,EAAE,QAAI,uBAAS,kBAAkB,WAAW,EAAE;AACrD,QAAM,mBAAmB,YAAY,QAAQ,YAAY;AAGzD,QAAM,OAAO,CAAC,MAAM;AAClB,cAAU,CAAC;AACX,kBAAc,IAAI;AAAA,EACpB;AAEA,QAAM,OAAO,CAAC,MAAM;AAClB,aAAS,CAAC;AACV,kBAAc,KAAK;AAAA,EACrB;AAEA,QAAM,YAAY,gBAAAA,QAAO,uBAAuB;AAChD,QAAM,oBACJ,eAAe,OAAO,YAAY,WAAW,QAAQ;AAGvD,8BAAU,MAAM;AACd,UAAM,eAAe,SAAS;AAC9B,QAAI;AACJ,UAAM,QAAQ,CAAC,MAA2B;AAExC,UAAI,CAAC,UAAU,KAAK,EAAE,SAAS,EAAE,GAAG,GAAG;AACrC,kBAAU,KAAK;AACf,sBAAc,KAAK;AAAA,MACrB;AAAA,IACF;AAEA,QAAI,YAAY;AACd,gBAAU,WAAW,MAAM,UAAU,IAAI,GAAG,UAAU;AAAA,IACxD,OAAO;AACL,gBAAU,WAAW,MAAM;AACzB,kBAAU,KAAK;AAAA,MACjB,GAAG,SAAS;AAAA,IACd;AAGA,QAAI,QAAQ;AACV,mBAAa,iBAAiB,WAAW,OAAO,EAAE,SAAS,KAAK,CAAC;AAAA,IACnE;AACA,WAAO,MAAM;AACX,mBAAa,oBAAoB,WAAW,OAAO,EAAE,SAAS,KAAK,CAAC;AACpE,mBAAa,OAAO;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,QAAQ,eAAe,YAAY,YAAY,SAAS,CAAC;AAG7D,QAAM,iBAAiB,MACrB;AAAA,IAAC;AAAA;AAAA,MACC,YAAY;AAAA,MACZ,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,iBAAe;AAAA,MACf;AAAA,MACC,GAAG;AAAA,MAEH;AAAA;AAAA,EACH;AAGF,SACE;AAAA,IAAC,0BAAAC;AAAA,IAAA;AAAA,MACC,SAAS,CAAC,mBAAmB,iBAAiB;AAAA,MAC9C;AAAA,MACA;AAAA,MACA,IAAI;AAAA,QACF,mBAAmB;AAAA,QACnB,GAAG;AAAA,MACL;AAAA,MACA,IAAI,KAAK;AAAA,MACT,gBAAgB;AAAA,MAChB;AAAA,MACA,iBAAc;AAAA,MACd,SAAS,YAAY,SAAS;AAAA,MAC9B,oBAAoB;AAAA,MACnB,GAAG;AAAA,MAEJ;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,cAAc;AAAA,UACd,cAAc;AAAA,UACd,OACE,YACI;AAAA,YACE,UAAU;AAAA,YACV,cAAc;AAAA,YACd,YAAY;AAAA,UACd,IACA,CAAC;AAAA,UAGN,UAAM,qBAAe,QAAQ,IACpB,mBAAa,UAAgC;AAAA;AAAA,YAEjD,iBAAiB,aAAa,UAAU,eAAe,IACnD,SAAS,MAAM,eAAe,IAC9B;AAAA,YACJ,oBAAoB;AAAA,UACtB,CAAC,IACD;AAAA;AAAA,MACN;AAAA;AAAA,EACF;AAEJ;AAEA,IAAO,kBAAQ;;;AEnLf,IAAAC,SAAuB;;;AHEvB,IAAO,gBAAQ;","names":["styled","Box","MOTION","Popout","React"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/Tooltip.tsx","../src/styles.ts","../src/TooltipTypes.ts"],"sourcesContent":["import Tooltip from \"./Tooltip\";\n\nexport default Tooltip;\nexport { Tooltip };\nexport * from \"./TooltipTypes\";\n","import * as React from \"react\";\nimport { useState, useEffect } from \"react\";\nimport MOTION from \"@sproutsocial/seeds-motion/unitless\";\nimport Popout from \"@sproutsocial/seeds-react-popout\";\nimport { StyledTooltipContent } from \"./styles\";\nimport type { TypeTooltipProps, TypeTooltipContent } from \"./TooltipTypes\";\n\nlet idCounter = 0;\n\nconst hasAttribute = (child: React.ReactNode, attribute: string) => {\n return React.isValidElement(child) && child.props[attribute] !== undefined;\n};\n\n/** Tooltip Styled Popout wrapper for handling events */\nconst TooltipBubble = ({\n appearance = \"pill\",\n children,\n onFocus,\n onBlur,\n ...rest\n}: TypeTooltipContent) => {\n // @ts-ignore Will fix during refactor\n const handleFocus = (e) => {\n onFocus(e);\n };\n // @ts-ignore Will fix during refactor\n const handleBlur = (e) => {\n onBlur(e);\n };\n return (\n <StyledTooltipContent\n role=\"tooltip\"\n appearance={appearance}\n borderRadius={appearance === \"box\" ? 500 : \"5000em\"}\n px={400}\n py={appearance === \"box\" ? 400 : 200}\n m={200}\n color=\"text.body\"\n bg=\"container.background.base\"\n boxShadow=\"medium\"\n border={500}\n borderColor=\"container.border.base\"\n onFocus={handleFocus}\n onBlur={handleBlur}\n onMouseEnter={handleFocus}\n onMouseLeave={handleBlur}\n tabIndex={0}\n {...rest}\n >\n {children}\n </StyledTooltipContent>\n );\n};\n\n/** Core component */\nconst Tooltip = ({\n content,\n children,\n enterDelay = MOTION.MOTION_DURATION_FAST * 1000,\n placement = \"auto\",\n appearance,\n zIndex = 7,\n qa,\n popoutProps,\n truncated = false,\n onFocus,\n onBlur,\n ...rest\n}: TypeTooltipProps) => {\n const [shouldShow, setShouldShow] = useState(false);\n const [isOpen, setIsOpen] = useState(false);\n const [id] = useState(`Racine-tooltip-${idCounter++}`);\n const isInvalidContent = content === null || content === undefined;\n\n // @ts-ignore Will fix during refactor\n const show = (e) => {\n onFocus?.(e);\n setShouldShow(true);\n };\n // @ts-ignore Will fix during refactor\n const hide = (e) => {\n onBlur?.(e);\n setShouldShow(false);\n };\n\n const exitDelay = MOTION.MOTION_DURATION_FAST * 1000;\n const defaultAppearance =\n appearance || (typeof content === \"object\" ? \"box\" : \"pill\");\n\n /** Handles all the logic around whether to display/not display */\n useEffect(() => {\n const documentBody = document.body;\n let timeout;\n const onEsc = (e: KeyboardEvent): void => {\n // older browsers use \"Esc\"\n if ([\"Escape\", \"Esc\"].includes(e.key)) {\n setIsOpen(false);\n setShouldShow(false);\n }\n };\n\n if (shouldShow) {\n timeout = setTimeout(() => setIsOpen(true), enterDelay);\n } else {\n timeout = setTimeout(() => {\n setIsOpen(false);\n }, exitDelay);\n }\n\n // We only want listeners from the tooltip if its open in the first place\n if (isOpen) {\n documentBody.addEventListener(\"keydown\", onEsc, { capture: true });\n }\n return () => {\n documentBody.removeEventListener(\"keydown\", onEsc, { capture: true });\n clearTimeout(timeout);\n };\n }, [isOpen, setShouldShow, shouldShow, enterDelay, exitDelay]);\n\n /** The wrapped content of whats inside the Tooltip */\n const TooltipContent = () => (\n <TooltipBubble\n appearance={defaultAppearance}\n onFocus={show}\n onBlur={hide}\n aria-expanded={isOpen}\n id={id}\n {...rest}\n >\n {content}\n </TooltipBubble>\n );\n\n return (\n <Popout\n content={!isInvalidContent ? TooltipContent : undefined}\n isOpen={isOpen}\n placement={placement}\n qa={{\n \"data-qa-tooltip\": id,\n ...qa,\n }}\n id={id + \"-wrapper\"}\n focusOnContent={false}\n zIndex={zIndex}\n aria-haspopup=\"false\"\n display={truncated ? \"flex\" : undefined}\n disableWrapperAria={true} // required so that the child span doesnt take in redundant aria props\n {...popoutProps}\n >\n <span\n onBlur={hide}\n onFocus={show}\n onMouseEnter={show}\n onMouseLeave={hide}\n style={\n truncated\n ? {\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n }\n : {}\n }\n >\n {React.isValidElement(children)\n ? React.cloneElement(children as React.ReactElement, {\n //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that property takes priority */\n \"aria-expanded\": hasAttribute(children, \"aria-expanded\")\n ? children.props[\"aria-expanded\"]\n : undefined,\n \"aria-describedby\": isOpen ? id : undefined,\n })\n : children}\n </span>\n </Popout>\n );\n};\n\nexport default Tooltip;\n","import styled from \"styled-components\";\nimport Box from \"@sproutsocial/seeds-react-box\";\nimport type { TypeTooltipContent } from \"./TooltipTypes\";\n\nexport const StyledTooltipContent = styled(Box)<\n Pick<TypeTooltipContent, \"appearance\">\n>`\n font-family: ${(props) => props.theme.fontFamily};\n ${(props) => props.theme.typography[200]}\n text-align: ${(props) => (props.appearance === \"box\" ? \"left\" : \"center\")};\n`;\n","import * as React from \"react\";\nimport type { TypePopoutProps } from \"@sproutsocial/seeds-react-popout\";\nimport type { TypeBoxProps } from \"@sproutsocial/seeds-react-box\";\n\nexport interface TypeTooltipProps\n extends Omit<\n TypeBoxProps,\n \"children\" | \"content\" | \"onMouseEnter\" | \"onMouseLeave\"\n > {\n /** The content that the tooltip should be attached to. Hovering or focusing this element will cause the tooltip to appear */\n children: React.ReactNode;\n\n /** The content to be displayed within the tooltip. If there is no content, just the children are rendered */\n content: React.ReactNode;\n\n /** The placement of the tooltip in relation to the children */\n placement?: TypePopoutProps[\"placement\"];\n\n /** The time (in ms) that a user has to be hovered/focused before the tooltip will appear */\n enterDelay?: number;\n\n /** Used to override the appearance of the Tooltip content. By default, strings will have the 'pill' appearance, and more complex content will have the 'box' appearance. You can change those defaults by setting this prop. */\n appearance?: \"pill\" | \"box\";\n qa?: object;\n zIndex?: number;\n\n /** Props to be spread onto the underlying Popout component */\n popoutProps?: Partial<TypePopoutProps>;\n\n /** Truncates text into a single line with ellipsis */\n truncated?: boolean;\n\n ariaProps?: Record<string, string>;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-empty-interface\nexport interface TypeTooltipContent\n extends Pick<TypeTooltipProps, \"appearance\" | \"children\"> {\n onFocus: (e: React.FocusEvent<HTMLDivElement, FocusEvent>) => void;\n onBlur: (e: React.FocusEvent<HTMLDivElement, FocusEvent>) => void;\n}\n"],"mappings":"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","names":["styled","Box","MOTION","Popout","React"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutsocial/seeds-react-tooltip",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Seeds React Tooltip",
5
5
  "author": "Sprout Social, Inc.",
6
6
  "license": "MIT",
@@ -18,10 +18,10 @@
18
18
  "test:watch": "jest --watch --coverage=false"
19
19
  },
20
20
  "dependencies": {
21
- "@sproutsocial/seeds-react-theme": "^3.1.1",
21
+ "@sproutsocial/seeds-react-theme": "^3.2.0",
22
22
  "@sproutsocial/seeds-react-system-props": "^3.0.1",
23
- "@sproutsocial/seeds-react-popout": "^2.4.9",
24
- "@sproutsocial/seeds-react-box": "^1.1.5",
23
+ "@sproutsocial/seeds-react-popout": "^2.4.11",
24
+ "@sproutsocial/seeds-react-box": "^1.1.6",
25
25
  "@sproutsocial/seeds-motion": "^1.8.0"
26
26
  },
27
27
  "devDependencies": {
@@ -35,9 +35,9 @@
35
35
  "@sproutsocial/seeds-tsconfig": "*",
36
36
  "@sproutsocial/seeds-testing": "*",
37
37
  "@sproutsocial/seeds-react-testing-library": "*",
38
- "@sproutsocial/seeds-react-icon": "^1.1.7",
39
- "@sproutsocial/seeds-react-banner": "^1.0.9",
40
- "@sproutsocial/seeds-react-button": "^1.3.3",
38
+ "@sproutsocial/seeds-react-icon": "^2.0.0",
39
+ "@sproutsocial/seeds-react-banner": "^1.0.11",
40
+ "@sproutsocial/seeds-react-button": "^1.3.5",
41
41
  "@sproutsocial/seeds-react-text": "^1.3.2",
42
42
  "@sproutsocial/seeds-react-portal": "^1.1.4"
43
43
  },
package/src/Tooltip.tsx CHANGED
@@ -165,11 +165,11 @@ const Tooltip = ({
165
165
  >
166
166
  {React.isValidElement(children)
167
167
  ? React.cloneElement(children as React.ReactElement, {
168
- //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that propery takes priority */
168
+ //** There may be cases where the Tooltip's child needs to properly describe its role as expanding a drawer, in which case that property takes priority */
169
169
  "aria-expanded": hasAttribute(children, "aria-expanded")
170
170
  ? children.props["aria-expanded"]
171
- : isOpen,
172
- "aria-describedby": id,
171
+ : undefined,
172
+ "aria-describedby": isOpen ? id : undefined,
173
173
  })
174
174
  : children}
175
175
  </span>
@@ -126,48 +126,65 @@ describe("Tooltip", () => {
126
126
  ).rejects.toBeTruthy();
127
127
  });
128
128
 
129
- it("should render as aria-expanded if tooltip is hovered", async () => {
129
+ it("should not add aria-expanded to elements without it", async () => {
130
130
  render(
131
131
  <Tooltip content="hello">
132
- <Button role="button" onClick={() => {}}>
133
- hi
134
- </Button>
132
+ <Button>hi</Button>
135
133
  </Tooltip>
136
134
  );
137
- // Hover over
135
+
136
+ const button = screen.getByRole("button", { name: "hi" });
137
+
138
+ // Should not have aria-expanded initially
139
+ expect(button).not.toHaveAttribute("aria-expanded");
140
+
141
+ // Hover to open tooltip
138
142
  act(() => {
139
- fireEvent.mouseOver(screen.getByText("hi"));
143
+ fireEvent.mouseOver(button);
140
144
  });
145
+
141
146
  // Wait for tooltip
142
147
  await screen.findByText(/hello/i);
143
- // check if expanded
144
- expect(
145
- screen.getByRole("button", {
146
- expanded: true,
147
- })
148
- ).toBeInTheDocument();
148
+
149
+ // Should still not have aria-expanded (tooltips don't use this attribute)
150
+ expect(button).not.toHaveAttribute("aria-expanded");
149
151
  });
150
152
 
151
- it("should not render as aria-expanded if child already defines one", async () => {
153
+ it("should preserve existing aria-expanded attribute", async () => {
152
154
  render(
153
155
  <Tooltip content="hello">
154
- <Button aria-expanded="false" role="button" onClick={() => {}}>
155
- hi
156
- </Button>
156
+ <Button aria-expanded="false">hi</Button>
157
157
  </Tooltip>
158
158
  );
159
- // Hover over
159
+
160
+ const button = screen.getByRole("button", { name: "hi" });
161
+
162
+ // Should preserve the existing aria-expanded value
163
+ expect(button).toHaveAttribute("aria-expanded", "false");
164
+
165
+ // Hover to open tooltip
160
166
  act(() => {
161
- fireEvent.mouseOver(screen.getByText("hi"));
167
+ fireEvent.mouseOver(button);
162
168
  });
169
+
163
170
  // Wait for tooltip
164
171
  await screen.findByText(/hello/i);
165
- // check if expanded
166
- expect(
167
- screen.getByRole("button", {
168
- expanded: false,
169
- })
170
- ).toBeInTheDocument();
172
+
173
+ // Should still preserve aria-expanded="false" (drawer/disclosure pattern)
174
+ expect(button).toHaveAttribute("aria-expanded", "false");
175
+ });
176
+
177
+ it("should not add aria-expanded to non-interactive elements", () => {
178
+ render(
179
+ <Tooltip content="Tooltip text">
180
+ <span data-testid="text-element">Static text</span>
181
+ </Tooltip>
182
+ );
183
+
184
+ const textElement = screen.getByTestId("text-element");
185
+
186
+ // Non-interactive elements should not get aria-expanded
187
+ expect(textElement).not.toHaveAttribute("aria-expanded");
171
188
  });
172
189
  });
173
190
 
@@ -191,4 +208,107 @@ describe("Tooltip", () => {
191
208
  })
192
209
  ).resolves.toBeTruthy();
193
210
  });
211
+
212
+ describe("ARIA attributes", () => {
213
+ it("should not have aria-describedby when tooltip is closed", () => {
214
+ render(
215
+ <Tooltip content="Tooltip content">
216
+ <Button data-testid="trigger">Trigger</Button>
217
+ </Tooltip>
218
+ );
219
+
220
+ const trigger = screen.getByTestId("trigger");
221
+ expect(trigger).not.toHaveAttribute("aria-describedby");
222
+ });
223
+
224
+ it("should have aria-describedby when tooltip is open", async () => {
225
+ render(
226
+ <Tooltip content="Tooltip content">
227
+ <Button data-testid="trigger">Trigger</Button>
228
+ </Tooltip>
229
+ );
230
+
231
+ const trigger = screen.getByTestId("trigger");
232
+
233
+ // Hover to open tooltip
234
+ act(() => {
235
+ fireEvent.mouseOver(trigger);
236
+ });
237
+
238
+ // Wait for tooltip to appear
239
+ await screen.findByText("Tooltip content");
240
+
241
+ // Check that aria-describedby is now present
242
+ expect(trigger).toHaveAttribute("aria-describedby");
243
+ const tooltipId = trigger.getAttribute("aria-describedby");
244
+ expect(tooltipId).toBeTruthy();
245
+ expect(tooltipId).toMatch(/^Racine-tooltip-\d+$/);
246
+ });
247
+
248
+ it("should remove aria-describedby when tooltip closes", async () => {
249
+ render(
250
+ <Tooltip content="Tooltip content">
251
+ <Button data-testid="trigger">Trigger</Button>
252
+ </Tooltip>
253
+ );
254
+
255
+ const trigger = screen.getByTestId("trigger");
256
+
257
+ // Open tooltip
258
+ act(() => {
259
+ fireEvent.mouseOver(trigger);
260
+ });
261
+
262
+ // Wait for tooltip to appear
263
+ await screen.findByText("Tooltip content");
264
+
265
+ // Verify aria-describedby is present
266
+ expect(trigger).toHaveAttribute("aria-describedby");
267
+
268
+ // Close tooltip
269
+ act(() => {
270
+ fireEvent.mouseOut(trigger);
271
+ });
272
+
273
+ // Wait for tooltip to close (using a small delay)
274
+ await act(async () => {
275
+ await new Promise((resolve) => setTimeout(resolve, 200));
276
+ });
277
+
278
+ // Verify aria-describedby is removed
279
+ expect(trigger).not.toHaveAttribute("aria-describedby");
280
+ });
281
+
282
+ it("should have aria-describedby pointing to element with role='tooltip'", async () => {
283
+ render(
284
+ <Tooltip content="Tooltip content">
285
+ <Button data-testid="trigger">Trigger</Button>
286
+ </Tooltip>
287
+ );
288
+
289
+ const trigger = screen.getByTestId("trigger");
290
+
291
+ // Open tooltip
292
+ act(() => {
293
+ fireEvent.mouseOver(trigger);
294
+ });
295
+
296
+ // Wait for tooltip to appear
297
+ const tooltipElement = await screen.findByText("Tooltip content");
298
+
299
+ // Get the aria-describedby value
300
+ const tooltipId = trigger.getAttribute("aria-describedby");
301
+ expect(tooltipId).toBeTruthy();
302
+
303
+ // Find the tooltip by role
304
+ const tooltipByRole = screen.getByRole("tooltip");
305
+ expect(tooltipByRole).toBeInTheDocument();
306
+
307
+ // Verify the ID matches
308
+ expect(tooltipByRole).toHaveAttribute("id", tooltipId);
309
+
310
+ // Verify the tooltip contains the content
311
+ expect(tooltipByRole).toContainElement(tooltipElement);
312
+ });
313
+ });
194
314
  });