Icons/anthropic midjourney ollama aistudio xai lmstudio#71
Conversation
|
@itstheanurag is attempting to deploy a commit to the itshover's projects Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThe PR introduces six new animated brand icon components (Anthropic, AI Studio, xAI, LM Studio, Midjourney, Ollama) with hover-triggered animations and imperative animation APIs, registered in icon catalogs and made available through the icon index. Changes
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
icons/index.ts (1)
1-1490: Fix Prettier formatting issues.The CI pipeline detected code style issues in this file. Run Prettier to auto-format the code before merging.
#!/bin/bash # Run Prettier to fix formatting npx prettier --write icons/index.ts
🤖 Fix all issues with AI agents
In @icons/index.ts:
- Around line 1477-1489: Update the keywords for the "midjourney-icon" entry
(icon: MidJourneyIcon) to remove misleading and irrelevant terms: delete "llm",
"ships", and "boat" from the keywords array and replace/add more accurate tags
such as "image-generation", "text-to-image", and "generative-ai" so the keywords
reflect MidJourney's image-generation domain.
- Around line 1452-1465: The keywords array for the "xai-icon" entry (object
with name "xai-icon" and icon symbol XaiIcon) incorrectly contains "studio";
remove the "studio" token from that keywords list so searches won't associate
xAI with Google's AI Studio, leaving the other keywords unchanged.
In @icons/lmstudio-icon.tsx:
- Around line 12-19: The constant array lines is defined inside the component
causing missing dependency warnings for useCallback; move the lines declaration
to module scope (outside the component) so it is not recreated on each render
and update references in the component (e.g., in any useCallback that currently
closes over lines) to use the top-level lines constant; this keeps the array
immutable, satisfies exhaustive-deps, and removes the lint warnings for the
component that uses lines.
In @public/r/xai-icon.json:
- Line 10: The SVG title text "Grok" does not match the React component name
XaiIcon; update the <title> element inside the XaiIcon component (the JSX
returned by motion.svg) to use a consistent name — either change "Grok" to
"XaiIcon" (or "xAI") or include both (e.g., "xAI (Grok)") so the component name
XaiIcon and the SVG title align for discoverability.
🧹 Nitpick comments (7)
icons/ollama-icon.tsx (1)
38-41: Add dependency array touseImperativeHandlefor consistency.The
useImperativeHandlehook is missing its dependency array. While this works, other icons in this PR (e.g.,lmstudio-icon.tsx,xai-icon.tsx) include[start, stop]as dependencies. Adding it ensures the handle updates if the callbacks change and maintains consistency across the codebase.Suggested fix
useImperativeHandle(ref, () => ({ startAnimation: start, stopAnimation: stop, - })); + }), [start, stop]);icons/midjourney-icon.tsx (1)
26-29: Add dependency array touseImperativeHandlefor consistency.Same as noted for
OllamaIcon— add[start, stop]as the dependency array to match the pattern used inLmStudioIconandXaiIcon.Suggested fix
useImperativeHandle(ref, () => ({ startAnimation: start, stopAnimation: stop, - })); + }), [start, stop]);public/r/xai-icon.json (1)
10-10: SVG path uses both fill and stroke with identical color.The path applies both
fill={color}andstroke={color}with the same value, creating a heavier visual weight. Most icon libraries use either fill or stroke, not both, unless intentionally creating an outlined effect.Suggested refinement
If the intent is a simple filled icon:
fill={color} - stroke={color} - strokeWidth=\"1.5\" strokeLinecap=\"round\"Or if the intent is a stroked icon:
- fill={color} + fill=\"none\" stroke={color} strokeWidth=\"1.5\"public/r/aistudio-icon.json (1)
10-10: Consider adding accessibility attributes to the SVG.The icon includes a
<title>element, which is good, but for better screen reader support, consider addingrole="img"andaria-labelledby(oraria-label) attributes to themotion.svgelement. This ensures assistive technologies properly announce the icon.♿ Suggested accessibility improvements
return ( <motion.svg ref={scope} width={size} height={size} viewBox="0 0 24 24" fill={color} className={className} xmlns="http://www.w3.org/2000/svg" + role="img" + aria-label="Google AI Studio" onHoverStart={start} onHoverEnd={stop} > - <title>Google AI Studio</title> + <title id="aistudio-title">Google AI Studio</title>public/r/ollama-icon.json (1)
10-10: Consider adding accessibility attributes to the SVG.Similar to other icons in this PR, the Ollama icon should include
role="img"and proper ARIA attributes for better screen reader support.icons/anthropic-icon.tsx (1)
40-78: Consider adding accessibility attributes.For consistency with accessibility best practices, add
role="img"andaria-labelto the SVG element, similar to the recommendation for other icons in this PR.public/r/midjourney-icon.json (1)
10-10: Consider adding accessibility attributes.For consistency with other icons, add
role="img"and proper ARIA attributes to the SVG element for better screen reader support.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
icons/aistudio-icon.tsxicons/anthropic-icon.tsxicons/index.tsicons/lmstudio-icon.tsxicons/midjourney-icon.tsxicons/ollama-icon.tsxicons/xai-icon.tsxlib/icons.tspublic/r/aistudio-icon.jsonpublic/r/anthropic-icon.jsonpublic/r/lmstudio-icon.jsonpublic/r/midjourney-icon.jsonpublic/r/ollama-icon.jsonpublic/r/registry.jsonpublic/r/xai-icon.jsonregistry.json
🧰 Additional context used
🪛 GitHub Actions: CI
icons/index.ts
[warning] 1-1: Code style issues found in icons/index.ts. Run Prettier with --write to fix.
🪛 GitHub Check: build
icons/lmstudio-icon.tsx
[warning] 45-45:
React Hook useCallback has a missing dependency: 'lines'. Either include it or remove the dependency array
[warning] 37-37:
React Hook useCallback has a missing dependency: 'lines'. Either include it or remove the dependency array
🔇 Additional comments (17)
icons/ollama-icon.tsx (1)
1-102: Well-structured animated icon component.The OllamaIcon implementation follows the established pattern with proper forwardRef usage, imperative handle exposure, and hover-triggered animations. The animation effects (body breathing, eye blinking, nose wiggle) are creative and well-implemented.
public/r/lmstudio-icon.json (1)
1-20: Registry entry is correctly structured.The registry item follows the shadcn schema and correctly declares the motion dependency and file paths.
icons/lmstudio-icon.tsx (1)
56-106: LGTM on the SVG structure and animation setup.The SVG paths with individual class names for animation targeting and the hover event wiring are well implemented.
lib/icons.ts (1)
694-717: Icon registry entries are correctly added.The new icon entries follow the established naming convention and path structure consistent with existing icons in the registry.
icons/midjourney-icon.tsx (1)
1-62: Well-implemented sailing boat animation.The MidJourneyIcon component correctly implements the animated icon pattern with a creative bobbing boat effect on the sails.
icons/xai-icon.tsx (2)
45-45: Verify the SVG title: "Grok" vs "xAI".The component is named
XaiIconbut the<title>is set to "Grok". Note thatgrok-iconalready exists in the registry. If this icon represents xAI (the company), the title should likely be "xAI". If it's meant to be another Grok icon, consider the naming to avoid confusion.
1-64: Implementation follows the established pattern correctly.The XaiIcon properly implements the animated icon pattern with dependency arrays included in
useImperativeHandle.registry.json (1)
41-57: Registry entries for new icons are correctly structured.All six new icon entries (aistudio-icon, anthropic-icon, lmstudio-icon, midjourney-icon, ollama-icon, xai-icon) follow the established registry schema with proper dependencies and file references. The entries are correctly positioned in alphabetical order.
Also applies to: 177-193, 2030-2046, 2149-2165, 2251-2267, 3407-3423
public/r/anthropic-icon.json (1)
10-10: LGTM! Clean animation implementation.The component correctly uses
getBBox()to calculate dynamic spacing between SVG elements and animates them smoothly. The null checks and ref handling are appropriate.public/r/registry.json (1)
2-2: LGTM! Auto-generated registry entries are correctly structured.All six new icon entries (aistudio-icon, anthropic-icon, lmstudio-icon, midjourney-icon, ollama-icon, xai-icon) follow the correct structure and are properly integrated into the registry. The file is auto-generated as indicated by the header comment.
icons/aistudio-icon.tsx (1)
1-120: LGTM! Well-implemented animated icon component.The component follows best practices:
- Proper TypeScript typing with forwardRef
- Efficient getBBox() usage (only called during animation, not on every render)
- Clean animation logic with useCallback
- Appropriate null checks for refs
- Consistent with the pattern used in other icon components
icons/anthropic-icon.tsx (2)
12-19: LGTM! Proper null safety for getBBox() calls.The
getMergeXfunction correctly guards against null refs before callinggetBBox(). Since this is only invoked during animation start (after component mount and ref assignment), the timing is safe.
21-29: Animation implementation looks solid.The animation callbacks are properly memoized with correct dependencies, and the approach of measuring actual SVG bounding boxes ensures accurate positioning regardless of viewport size or scaling.
public/r/midjourney-icon.json (2)
10-10: Verify cleanup of infinite animations on unmount.Like OllamaIcon, this component uses
repeat: Infinityfor the continuous bobbing animation. Verify that motion/react properly cleans up the animation when the component unmounts.
10-10: Nice animation design with appropriate duration.The 1.2-second duration for the boat bobbing animation is well-chosen—it creates a smooth, natural wave-like motion without being too fast or jarring. The combination of scale, x, and y transformations effectively simulates realistic boat movement.
public/r/aistudio-icon.json (1)
4-4: No action needed.The
useAnimatehook andmotion.svgcomponent are fully compatible with motion v12.23.26, the version currently in use. The aistudio-icon.tsx implementation correctly uses these APIs with proper animation configurations.public/r/ollama-icon.json (1)
10-10: No issues found. The component correctly usesuseAnimate()from motion/react, which automatically cleans up all animations—including infinite ones—when the component unmounts. No explicit cleanup code or additional verification is needed.
| name: "midjourney-icon", | ||
| icon: MidJourneyIcon, | ||
| keywords: [ | ||
| "midjourney", | ||
| "midjourney-icon", | ||
| "ai", | ||
| "llm", | ||
| "ai-agents", | ||
| "journey", | ||
| "ships", | ||
| "boat", | ||
| ], | ||
| }, |
There was a problem hiding this comment.
Misleading keywords for midjourney-icon.
MidJourney is an image generation AI, not an LLM. The keywords include "llm" which is incorrect. Additionally, "ships" and "boat" keywords seem unusual for an AI tool.
Suggested fix
keywords: [
"midjourney",
"midjourney-icon",
"ai",
- "llm",
- "ai-agents",
- "journey",
- "ships",
- "boat",
+ "image",
+ "generation",
+ "art",
+ "design",
],🤖 Prompt for AI Agents
In @icons/index.ts around lines 1477 - 1489, Update the keywords for the
"midjourney-icon" entry (icon: MidJourneyIcon) to remove misleading and
irrelevant terms: delete "llm", "ships", and "boat" from the keywords array and
replace/add more accurate tags such as "image-generation", "text-to-image", and
"generative-ai" so the keywords reflect MidJourney's image-generation domain.
| const lines = [ | ||
| ".lm-line-1", | ||
| ".lm-line-2", | ||
| ".lm-line-3", | ||
| ".lm-line-4", | ||
| ".lm-line-5", | ||
| ".lm-line-6", | ||
| ]; |
There was a problem hiding this comment.
Move lines array outside the component to fix missing dependency warnings.
The static analysis correctly flags that lines is missing from the useCallback dependency arrays. Since lines is a constant array of class selectors that never changes, move it outside the component to avoid recreating it on every render and to satisfy the exhaustive-deps rule cleanly.
Suggested fix
import { forwardRef, useImperativeHandle, useCallback } from "react";
import type { AnimatedIconHandle, AnimatedIconProps } from "./types";
import { motion, useAnimate } from "motion/react";
+const lines = [
+ ".lm-line-1",
+ ".lm-line-2",
+ ".lm-line-3",
+ ".lm-line-4",
+ ".lm-line-5",
+ ".lm-line-6",
+];
+
const LmStudioIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(
(
{ size = 24, color = "currentColor", strokeWidth = 1, className = "" },
ref,
) => {
const [scope, animate] = useAnimate();
- const lines = [
- ".lm-line-1",
- ".lm-line-2",
- ".lm-line-3",
- ".lm-line-4",
- ".lm-line-5",
- ".lm-line-6",
- ];
-
const start = useCallback(() => {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const lines = [ | |
| ".lm-line-1", | |
| ".lm-line-2", | |
| ".lm-line-3", | |
| ".lm-line-4", | |
| ".lm-line-5", | |
| ".lm-line-6", | |
| ]; | |
| const lines = [ | |
| ".lm-line-1", | |
| ".lm-line-2", | |
| ".lm-line-3", | |
| ".lm-line-4", | |
| ".lm-line-5", | |
| ".lm-line-6", | |
| ]; | |
| const LmStudioIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>( | |
| ( | |
| { size = 24, color = "currentColor", strokeWidth = 1, className = "" }, | |
| ref, | |
| ) => { | |
| const [scope, animate] = useAnimate(); | |
| const start = useCallback(() => { |
🤖 Prompt for AI Agents
In @icons/lmstudio-icon.tsx around lines 12 - 19, The constant array lines is
defined inside the component causing missing dependency warnings for
useCallback; move the lines declaration to module scope (outside the component)
so it is not recreated on each render and update references in the component
(e.g., in any useCallback that currently closes over lines) to use the top-level
lines constant; this keeps the array immutable, satisfies exhaustive-deps, and
removes the lint warnings for the component that uses lines.
| "files": [ | ||
| { | ||
| "path": "icons/xai-icon.tsx", | ||
| "content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Grok</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n", |
There was a problem hiding this comment.
Title and component name mismatch.
The SVG title is "Grok" but the component is named "XaiIcon". While xAI created Grok, this inconsistency may confuse users searching for icons. Consider using either "xAI" or "Grok" consistently.
Suggested fix for consistency
Option 1: Change title to match component name
- <title>Grok</title>
+ <title>xAI</title>Option 2: Or use both for clarity
- <title>Grok</title>
+ <title>xAI (Grok)</title>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| "content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Grok</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n", | |
| "content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>xAI</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n", |
| "content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Grok</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n", | |
| "content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst XaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n animate(\n \".xai-icon\",\n {\n x: [0, -1, 1, -1, 0],\n y: [0, 1, -1, 1, 0],\n },\n { duration: 0.25, repeat: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".xai-icon\", { x: 0, y: 0 }, { duration: 0.15 });\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n className={className}\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>xAI (Grok)</title>\n\n <motion.path\n className=\"xai-icon\"\n d=\"M6.469 8.776L16.512 23h-4.464L2.005 8.776H6.47zm-.004 7.9l2.233 3.164L6.467 23H2l4.465-6.324zM22 2.582V23h-3.659V7.764L22 2.582zM22 1l-9.952 14.095-2.233-3.163L17.533 1H22z\"\n fill={color}\n stroke={color}\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n vectorEffect=\"non-scaling-stroke\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n />\n </motion.svg>\n );\n },\n);\n\nXaiIcon.displayName = \"XaiIcon\";\nexport default XaiIcon;\n", |
🤖 Prompt for AI Agents
In @public/r/xai-icon.json at line 10, The SVG title text "Grok" does not match
the React component name XaiIcon; update the <title> element inside the XaiIcon
component (the JSX returned by motion.svg) to use a consistent name — either
change "Grok" to "XaiIcon" (or "xAI") or include both (e.g., "xAI (Grok)") so
the component name XaiIcon and the SVG title align for discoverability.
|
Looks cool overall 👍 xAI: Let’s explore a different and more unique interaction for this logo. One more thing: |
|
Sure will do. |
|
I think LMStudio animation is proper. see there are just 6 lines in that. Currently, they are animating by increasing and decreasing in size. Gemini has a rotation animation that will not work for the LMStudio animation. What do you think? |
|
So I have added animations for Anthropic. The brand is prefixed ahead of the icons. anthropic-xai-midjourney.mp4 |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Fix all issues with AI agents
In @icons/brand-lmstudio-icon.tsx:
- Around line 4-44: The start() function currently calls animate(...) with
repeat: Infinity for each selector but never stores the returned animation
controls, so infinite animations stack; update BrandLmStudioIcon to capture the
controls returned by animate for each selector (from the lines array) into a
persistent ref (or state) and, before starting, explicitly stop any existing
controls to avoid stacking; change stop() to iterate those stored controls and
call .stop() (then clear the storage) instead of only animating scaleX back to
1, and ensure start() also uses that stop-step at its beginning to prevent
overlapping runs.
In @icons/brand-midjourney-icon.tsx:
- Around line 36-39: The useImperativeHandle call for ref is missing its
dependency array, so update the hook invocation to include [start, stop] as the
dependency array to ensure the imperative handle (startAnimation/stopAnimation)
stays in sync when the start or stop callbacks change; locate the
useImperativeHandle(ref, () => ({ startAnimation: start, stopAnimation: stop }))
and add the dependency array [start, stop].
In @icons/brand-ollama-icon.tsx:
- Around line 38-41: The useImperativeHandle call in the component currently
omits the dependency array; update the useImperativeHandle(ref, () => ({
startAnimation: start, stopAnimation: stop })) invocation to include the
dependency array [start, stop] so that the imperative handle updates when start
or stop change (match the pattern used in brand-midjourney-icon.tsx and other
brand icons).
In @public/r/brand-aistudio-icon.json:
- Around line 1-20: The getSparkCenterOffset function can throw when calling
element.getBBox() on rendered-but-not-layouted SVG nodes; wrap both getBBox()
calls in a try/catch inside getSparkCenterOffset and return { x: 0, y: 0 } on
any exception so startAnimation (exposed by useImperativeHandle) is safe to call
even if SVG parts are hidden or not yet laid out; update the
getSparkCenterOffset implementation (referenced by frameRef, sparkRef, and
start) to catch errors from getBBox() and fall back to zeros.
In @public/r/brand-ollama-icon.json:
- Around line 1-20: The start callback launches three infinite animations via
animate(...) but never keeps the returned animation controls, so repeated starts
stack loops; update BrandOllamaIcon to store each animate(...) return (for
".ollama-body", ".ollama-eye", ".ollama-nose") in persistent refs (e.g., useRef)
when start runs and in stop call .cancel() on each stored control before calling
animate(...) to reset properties; ensure start replaces/refills the refs and
stop clears them after cancelling so repeated hover/imperative toggles do not
accumulate animation loops.
🧹 Nitpick comments (10)
lib/icons.ts (1)
694-717: Consider inserting new brand icons in alphabetical order.The existing icons in the
ICONSarray follow alphabetical ordering. The new brand icons are appended at the end rather than inserted near the existingbrand-googleandbrand-nextjsentries (around lines 134-141). Consider placing them alphabetically for consistency and easier maintenance.icons/brand-xai-icon.tsx (1)
33-54: Consider cleaning up thesetTimeoutto avoid stale callbacks.The
setTimeoutinsidestartis not cleared if the component unmounts or ifstartis called again rapidly. For a short hover animation this is unlikely to cause visible issues, but for robustness you could store the timeout ID in a ref and clear it instopor on unmount.Optional cleanup pattern
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); + const start = useCallback(() => { + // Clear any pending merge animation + if (timeoutRef.current) clearTimeout(timeoutRef.current); + // Explode: all 4 parts move outward... ... // Then merge back - setTimeout(() => { + timeoutRef.current = setTimeout(() => { ... }, 250); }, [animate]); const stop = useCallback(() => { + if (timeoutRef.current) clearTimeout(timeoutRef.current); ... }, [animate]);icons/brand-lmstudio-icon.tsx (1)
55-67: Consider droppingstrokeWidth(or addstroke) to avoid a misleading prop.This icon is
fill={color}and doesn’t setstroke, sostrokeWidthis likely a no-op. Either remove it from props for this icon, or addstroke={color}and setfill="none"if you intended strokes.public/r/brand-ollama-icon.json (1)
1-20: Add deps touseImperativeHandlefor consistency with other icons.This file uses
useImperativeHandle(ref, () => ({ ... }))without the deps array; consider adding[start, stop]to match the pattern used elsewhere and avoid stale-handle surprises if callbacks ever change.public/r/brand-lmstudio-icon.json (1)
1-20: Keep embedded TSX content in lockstep withicons/brand-lmstudio-icon.tsx(avoid drift).Since the registry item embeds the TSX as a string, it’s easy for it to diverge from the real file unless it’s always generated from source. If this is generated, consider adding a CI check that fails on diffs after running
npm run registry:build.public/r/brand-xai-icon.json (2)
7-13: AvoidsetTimeout-queued animations that can stack / fire after hover end (or unmount).
start()schedules a merge-back viasetTimeout, butstop()doesn’t cancel it—rapid hover in/out can cause delayed “merge” to run after you’ve already reset, and repeated hovers can stack timers. Consider tracking/clearing the timer (and ideally sequencing via Motion’s animation primitives if available).Proposed change (in
icons/brand-xai-icon.tsx)import { forwardRef, useImperativeHandle, useCallback } from "react"; +import { useRef, useEffect } from "react"; import type { AnimatedIconHandle, AnimatedIconProps } from "./types"; import { motion, useAnimate } from "motion/react"; const BrandXaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>( ({ size = 24, color = "currentColor", className = "" }, ref) => { const [scope, animate] = useAnimate(); + const mergeTimeoutRef = useRef<number | null>(null); + + useEffect(() => { + return () => { + if (mergeTimeoutRef.current != null) window.clearTimeout(mergeTimeoutRef.current); + }; + }, []); const start = useCallback(() => { + if (mergeTimeoutRef.current != null) window.clearTimeout(mergeTimeoutRef.current); // Explode... animate(".xai-part-1", { x: -4, y: -2 }, { duration: 0.25, ease: "easeOut" }); animate(".xai-part-2", { x: -3, y: 3 }, { duration: 0.25, ease: "easeOut" }); animate(".xai-part-3", { x: 4, y: -3 }, { duration: 0.25, ease: "easeOut" }); animate(".xai-part-4", { x: 3, y: 2 }, { duration: 0.25, ease: "easeOut" }); // Then merge back - setTimeout(() => { + mergeTimeoutRef.current = window.setTimeout(() => { animate(".xai-part-1", { x: 0, y: 0 }, { duration: 0.3, ease: "easeInOut" }); animate(".xai-part-2", { x: 0, y: 0 }, { duration: 0.3, ease: "easeInOut" }); animate(".xai-part-3", { x: 0, y: 0 }, { duration: 0.3, ease: "easeInOut" }); animate(".xai-part-4", { x: 0, y: 0 }, { duration: 0.3, ease: "easeInOut" }); }, 250); }, [animate]); const stop = useCallback(() => { + if (mergeTimeoutRef.current != null) window.clearTimeout(mergeTimeoutRef.current); animate(".xai-part-1", { x: 0, y: 0 }, { duration: 0.2 }); animate(".xai-part-2", { x: 0, y: 0 }, { duration: 0.2 }); animate(".xai-part-3", { x: 0, y: 0 }, { duration: 0.2 }); animate(".xai-part-4", { x: 0, y: 0 }, { duration: 0.2 }); }, [animate]);
13-18:icons/types.tsduplication across registry items can drift or overwrite.
Since multiple brand registry items appear to embed the sameicons/types.ts, ensure it’s identical everywhere (or better: ship it once via a shared registry dependency and reference it).public/r/brand-anthropic-icon.json (3)
7-13: Verify SVGrotateX/3D transforms behave consistently (or prefer 2D transforms).
rotateXon an SVG<g>can be spotty depending on how Motion applies transforms to SVG elements; consider switching to a 2D transform (rotate/scale/skew) unless you’ve verified this across your target browsers.
7-13: Optional: align Anthropic hover interaction with the “AI” (\ → I) suggestion.
Current hover is a nice pulse, but it doesn’t match the requested “\ becomes I” interaction. If that feedback is still desired, consider implementing it (path swap/morph, or a subtle translate/scale that visually reads as “I”).
13-18: Sameicons/types.tsregistry duplication concern here.
If this is intentional, keep the file content 100% identical across items (or extract into a shared registry dependency).
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (16)
icons/brand-aistudio-icon.tsxicons/brand-anthropic-icon.tsxicons/brand-lmstudio-icon.tsxicons/brand-midjourney-icon.tsxicons/brand-ollama-icon.tsxicons/brand-xai-icon.tsxicons/index.tslib/icons.tspublic/r/brand-aistudio-icon.jsonpublic/r/brand-anthropic-icon.jsonpublic/r/brand-lmstudio-icon.jsonpublic/r/brand-midjourney-icon.jsonpublic/r/brand-ollama-icon.jsonpublic/r/brand-xai-icon.jsonpublic/r/registry.jsonregistry.json
🚧 Files skipped from review as they are similar to previous changes (1)
- registry.json
🔇 Additional comments (6)
icons/brand-aistudio-icon.tsx (1)
1-120: LGTM!The component is well-structured with proper use of
forwardRef,useImperativeHandle, and the motion library. ThegetBBox-based centering logic for the spark animation is a nice approach for dynamic positioning. The hover interaction and imperative API are correctly wired.icons/brand-anthropic-icon.tsx (1)
1-70: LGTM!The component follows the established pattern consistently. The 3D transform animation (rotateX, skewX, scaleY) on the backslash creates a subtle hover effect. The imperative API and hover handlers are correctly implemented.
public/r/brand-midjourney-icon.json (1)
1-20: Registry file will need regeneration after source fix.This registry JSON embeds the source code. Once the
useImperativeHandledependency array is added to the source file (icons/brand-midjourney-icon.tsx), regenerate this registry file to keep it in sync.icons/brand-xai-icon.tsx (1)
64-71: LGTM on imperative handle.The
useImperativeHandlecorrectly includes the[start, stop]dependency array, consistent withBrandAiStudioIcon.public/r/registry.json (1)
687-822: Registry additions are correctly structured and ordered.All six new brand-icon entries (aistudio, anthropic, lmstudio, midjourney, ollama, xai) follow the established pattern with
motiondependency, proper file references, and alphabetical placement. All referenced icon files andicons/types.tsexist in the repository.icons/index.ts (1)
198-203: Imports look consistent with the “brand-*” naming objective.
| useImperativeHandle(ref, () => ({ | ||
| startAnimation: start, | ||
| stopAnimation: stop, | ||
| })); |
There was a problem hiding this comment.
Add dependency array to useImperativeHandle.
The useImperativeHandle hook is missing its dependency array. Other brand icon components in this PR (e.g., BrandAiStudioIcon, BrandXaiIcon) include [start, stop] as dependencies. This ensures the imperative handle stays in sync if the callbacks change.
Proposed fix
- useImperativeHandle(ref, () => ({
- startAnimation: start,
- stopAnimation: stop,
- }));
+ useImperativeHandle(
+ ref,
+ () => ({
+ startAnimation: start,
+ stopAnimation: stop,
+ }),
+ [start, stop],
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useImperativeHandle(ref, () => ({ | |
| startAnimation: start, | |
| stopAnimation: stop, | |
| })); | |
| useImperativeHandle( | |
| ref, | |
| () => ({ | |
| startAnimation: start, | |
| stopAnimation: stop, | |
| }), | |
| [start, stop], | |
| ); |
🤖 Prompt for AI Agents
In @icons/brand-midjourney-icon.tsx around lines 36 - 39, The
useImperativeHandle call for ref is missing its dependency array, so update the
hook invocation to include [start, stop] as the dependency array to ensure the
imperative handle (startAnimation/stopAnimation) stays in sync when the start or
stop callbacks change; locate the useImperativeHandle(ref, () => ({
startAnimation: start, stopAnimation: stop })) and add the dependency array
[start, stop].
| useImperativeHandle(ref, () => ({ | ||
| startAnimation: start, | ||
| stopAnimation: stop, | ||
| })); |
There was a problem hiding this comment.
Add dependency array to useImperativeHandle.
Same issue as in brand-midjourney-icon.tsx — the dependency array is missing. Add [start, stop] for consistency with other brand icons.
Proposed fix
- useImperativeHandle(ref, () => ({
- startAnimation: start,
- stopAnimation: stop,
- }));
+ useImperativeHandle(
+ ref,
+ () => ({
+ startAnimation: start,
+ stopAnimation: stop,
+ }),
+ [start, stop],
+ );📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useImperativeHandle(ref, () => ({ | |
| startAnimation: start, | |
| stopAnimation: stop, | |
| })); | |
| useImperativeHandle( | |
| ref, | |
| () => ({ | |
| startAnimation: start, | |
| stopAnimation: stop, | |
| }), | |
| [start, stop], | |
| ); |
🤖 Prompt for AI Agents
In @icons/brand-ollama-icon.tsx around lines 38 - 41, The useImperativeHandle
call in the component currently omits the dependency array; update the
useImperativeHandle(ref, () => ({ startAnimation: start, stopAnimation: stop }))
invocation to include the dependency array [start, stop] so that the imperative
handle updates when start or stop change (match the pattern used in
brand-midjourney-icon.tsx and other brand icons).
| { | ||
| "$schema": "https://ui.shadcn.com/schema/registry-item.json", | ||
| "name": "brand-aistudio-icon", | ||
| "dependencies": ["motion"], | ||
| "devDependencies": [], | ||
| "registryDependencies": [], | ||
| "files": [ | ||
| { | ||
| "path": "icons/brand-aistudio-icon.tsx", | ||
| "content": "import { forwardRef, useImperativeHandle, useCallback, useRef } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst BrandAiStudioIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const frameRef = useRef<SVGGElement | null>(null);\n const sparkRef = useRef<SVGGElement | null>(null);\n\n const getSparkCenterOffset = () => {\n if (!frameRef.current || !sparkRef.current) return { x: 0, y: 0 };\n\n const frame = frameRef.current.getBBox();\n const spark = sparkRef.current.getBBox();\n\n const frameCenterX = frame.x + frame.width / 2;\n const frameCenterY = frame.y + frame.height / 2;\n\n const sparkCenterX = spark.x + spark.width / 2;\n const sparkCenterY = spark.y + spark.height / 2;\n\n return {\n x: frameCenterX - sparkCenterX,\n y: frameCenterY - sparkCenterY,\n };\n };\n\n const start = useCallback(() => {\n const { x, y } = getSparkCenterOffset();\n\n // Move Gemini into the IDE\n animate(\n \".google-spark\",\n { x, y, scale: 0.9 },\n { duration: 0.35, ease: \"easeInOut\" },\n );\n\n // Pull the frame inward slightly so it \"closes\"\n animate(\n \".google-frame\",\n { scale: 0.94 },\n { duration: 0.35, ease: \"easeInOut\" },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\n \".google-spark\",\n { x: 0, y: 0, scale: 1 },\n { duration: 0.3, ease: \"easeInOut\" },\n );\n\n animate(\n \".google-frame\",\n { scale: 1 },\n { duration: 0.3, ease: \"easeInOut\" },\n );\n }, [animate]);\n\n useImperativeHandle(\n ref,\n () => ({\n startAnimation: start,\n stopAnimation: stop,\n }),\n [start, stop],\n );\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill={color}\n className={className}\n xmlns=\"http://www.w3.org/2000/svg\"\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Google AI Studio</title>\n\n {/* Frame */}\n <motion.g\n ref={frameRef}\n className=\"google-frame\"\n style={{\n transformBox: \"fill-box\",\n transformOrigin: \"center\",\n }}\n >\n <path\n fillRule=\"evenodd\"\n d=\"M9.921 4.196H6.328A2.705 2.705 0 003.623 6.9v11.362a2.705 2.705 0 002.705 2.705h11.363a2.705 2.705 0 002.705-2.705v-4.756l1.623-1.113v5.87a4.329 4.329 0 01-4.328 4.328H6.328A4.329 4.329 0 012 18.263V6.901a4.328 4.328 0 014.328-4.329h4.545l-.952 1.624z\"\n />\n </motion.g>\n\n {/* Gemini sparkle */}\n <motion.g\n ref={sparkRef}\n className=\"google-spark\"\n style={{\n transformBox: \"fill-box\",\n transformOrigin: \"center\",\n }}\n >\n <path\n fillRule=\"evenodd\"\n d=\"M17.82 0c.145 0 .268.104.299.246a7 7 0 001.9 3.484 7 7 0 003.485 1.901c.142.031.246.154.246.3a.308.308 0 01-.246.298A7 7 0 0020.02 8.13a7 7 0 00-1.912 3.535.297.297 0 01-.288.238.297.297 0 01-.288-.238A7 7 0 0015.62 8.13a7 7 0 00-3.535-1.912.297.297 0 01-.238-.288c0-.14.1-.26.238-.288A7 7 0 0015.62 3.73 7.001 7.001 0 0017.521.246.308.308 0 0117.82 0z\"\n />\n </motion.g>\n </motion.svg>\n );\n },\n);\n\nBrandAiStudioIcon.displayName = \"BrandAiStudioIcon\";\nexport default BrandAiStudioIcon;\n", | ||
| "type": "registry:ui" | ||
| }, | ||
| { | ||
| "path": "icons/types.ts", | ||
| "content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n", | ||
| "type": "registry:ui" | ||
| } | ||
| ], | ||
| "type": "registry:ui" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
getBBox() throws exception SVG element not rendered
💡 Result:
Cause
- getBBox() requires the element to be "rendered" (connected and not display:none or inside a non-rendered ancestor). If the element is detached, has display:none, or is a descendant of a non‑rendered element, some UAs throw or return invalid results. [1][2]
How to fix (pick one that fits your case)
-
Make sure the element is in the document and visible before calling getBBox:
- append it to the DOM (or to an that is in the DOM) and ensure CSS display ≠ none.
- call getBBox after the element is inserted and the browser had a chance to render (e.g. in requestAnimationFrame or after load). [1][2]
Example:
el = document.querySelector('svg > g');
requestAnimationFrame(() => { const bb = el.getBBox(); /* use bb */ }); -
If you must measure while hidden, temporarily make a measuring SVG that is visible (but offscreen or 0‑opacity) and clone the element into it, measure, then remove the clone.
-
Use getBoundingClientRect() when you need viewport coordinates (works only for rendered/painted elements) as an alternative for layout/position needs.
-
For elements in or elements that are never rendered, compute geometry manually or clone them into a rendered container first—those elements do not contribute to rendering and getBBox may not apply. [2]
References
- MDN: SVGGraphicsElement.getBBox(). [1]
- SVG spec: Rendered vs non‑rendered elements (when elements do/don't contribute to geometry/getBBox). [2]
If you want, tell me which browser and code snippet you're using and I’ll suggest the minimal change.
🌐 Web query:
SVG getBBox() can throw TypeError display none
💡 Result:
Yes — calling SVGElement.getBBox() can throw (or return empty/zero) when the element isn’t rendered (for example it or an ancestor uses display: none, or the element has zero geometry). Browsers differ: Chrome is more tolerant, but Firefox/Edge will treat display:none as non‑existent and can throw or return invalid results. (developer.mozilla.org)
Workarounds
- Avoid calling getBBox on elements with display: none. Check first:
- if (getComputedStyle(el).display === 'none') skip or unhide. (stackoverflow.com)
- Use visibility: hidden (keeps layout/rendering) instead of display: none, then getBBox works. (stackoverflow.com)
- If you must measure while hidden, temporarily insert a copy into a visible (or visibility:hidden) SVG in the DOM, measure, then remove. Example pattern:
- clone the node, append to an offscreen SVG container with visibility:hidden (not display:none), call getBBox(), then remove.
- As alternatives, for viewport-based sizes use getBoundingClientRect(), or for paths use path.getTotalLength()/getPointAtLength to compute extents.
References: MDN getBBox (behavior/spec) and community reports/solutions. (developer.mozilla.org)
Citations:
- 1: https://developer.mozilla.org/docs/Web/API/SVGGraphicsElement/getBBox?utm_source=openai
- 2: https://stackoverflow.com/questions/45184101/error-ns-error-failure-in-firefox-while-use-getbbox?utm_source=openai
- 3: https://stackoverflow.com/questions/44438215/svg-getbbox-function-returns-zero-in-firefox-edge-but-not-in-chrome?utm_source=openai
- 4: https://developer.mozilla.org/docs/Web/API/SVGGraphicsElement/getBBox?utm_source=openai
🌐 Web query:
SVG getBBox() specification browser behavior exceptions
💡 Result:
Short summary — spec vs actual browser behavior
-
Spec (SVG 2): getBBox(options) must return a DOMRect for the computed bounding box. If called on a non‑rendered element and the user agent is not able to compute the geometry, the UA must throw an InvalidStateError. [1]
-
Reality / interop exceptions:
- Implementations differ for malformed or non‑rendered shapes (e.g. negative/missing width on , detached elements). Some browsers return zeros (x=0,y=0,width=0,height=0) instead of throwing. Others return partial/derived values (Chrome historically returned the element’s x/y but zero width for an invalid rect). These inconsistencies are visible in WPT/issue discussion and tests. [2][3]
- Chromium has code/commits and issue discussion showing behavior was adjusted to avoid throwing when the UA can compute geometry from computed styles (i.e., implementations have been hardened/changed for interoperability). [4]
Sources
- SVG 2 specification (getBBox / rendered vs non‑rendered element rules). [1]
- WPT / svgwg issue demonstrating interop differences for invalid/non‑rendered shapes (examples and per‑browser table). [2]
- MDN entry for SVGGraphicsElement.getBBox (behavior notes & compatibility). [3]
- Chromium commit/discussion referencing change to avoid throwing when geometry can be computed. [4]
If you want, I can paste the exact WPT test cases and a tiny repro showing the differing outputs per browser.
Wrap getBBox() calls in try/catch—null refs don't guarantee rendered elements.
getSparkCenterOffset() checks for null refs but doesn't guard against exceptions when getBBox() is called on SVG elements that exist in the DOM but aren't rendered (e.g., display: none, hidden ancestor, or detached). The SVG spec allows (and some browsers including Firefox/Edge do) throw InvalidStateError in these cases. Since startAnimation() is exposed via imperative handle, callers can trigger it before layout/visibility is guaranteed.
Wrap both getBBox() calls in a try/catch block and return { x: 0, y: 0 } on exception:
Suggested fix
const getSparkCenterOffset = () => {
if (!frameRef.current || !sparkRef.current) return { x: 0, y: 0 };
try {
const frame = frameRef.current.getBBox();
const spark = sparkRef.current.getBBox();
const frameCenterX = frame.x + frame.width / 2;
const frameCenterY = frame.y + frame.height / 2;
const sparkCenterX = spark.x + spark.width / 2;
const sparkCenterY = spark.y + spark.height / 2;
return {
x: frameCenterX - sparkCenterX,
y: frameCenterY - sparkCenterY,
};
} catch {
return { x: 0, y: 0 };
}
};🤖 Prompt for AI Agents
In @public/r/brand-aistudio-icon.json around lines 1 - 20, The
getSparkCenterOffset function can throw when calling element.getBBox() on
rendered-but-not-layouted SVG nodes; wrap both getBBox() calls in a try/catch
inside getSparkCenterOffset and return { x: 0, y: 0 } on any exception so
startAnimation (exposed by useImperativeHandle) is safe to call even if SVG
parts are hidden or not yet laid out; update the getSparkCenterOffset
implementation (referenced by frameRef, sparkRef, and start) to catch errors
from getBBox() and fall back to zeros.
| { | ||
| "$schema": "https://ui.shadcn.com/schema/registry-item.json", | ||
| "name": "brand-ollama-icon", | ||
| "dependencies": ["motion"], | ||
| "devDependencies": [], | ||
| "registryDependencies": [], | ||
| "files": [ | ||
| { | ||
| "path": "icons/brand-ollama-icon.tsx", | ||
| "content": "import { forwardRef, useImperativeHandle, useCallback } from \"react\";\nimport type { AnimatedIconHandle, AnimatedIconProps } from \"./types\";\nimport { motion, useAnimate } from \"motion/react\";\n\nconst BrandOllamaIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(\n ({ size = 24, color = \"currentColor\", className = \"\" }, ref) => {\n const [scope, animate] = useAnimate();\n\n const start = useCallback(() => {\n // Body breathing effect - subtle scale\n animate(\n \".ollama-body\",\n { scale: [1, 1.03, 1] },\n { duration: 0.8, repeat: Infinity, ease: \"easeInOut\" },\n );\n\n // Eyes blink - immediate and noticeable\n animate(\n \".ollama-eye\",\n { scaleY: [1, 0.1, 1] },\n { duration: 0.2, delay: 0.3, repeat: Infinity, repeatDelay: 2 },\n );\n\n // Nose wiggle - playful\n animate(\n \".ollama-nose\",\n { y: [0, -1, 1, 0] },\n { duration: 0.3, repeat: Infinity, repeatDelay: 1 },\n );\n }, [animate]);\n\n const stop = useCallback(() => {\n animate(\".ollama-body\", { scale: 1 }, { duration: 0.2 });\n animate(\".ollama-eye\", { scaleY: 1 }, { duration: 0.2 });\n animate(\".ollama-nose\", { y: 0 }, { duration: 0.2 });\n }, [animate]);\n\n useImperativeHandle(ref, () => ({\n startAnimation: start,\n stopAnimation: stop,\n }));\n\n return (\n <motion.svg\n ref={scope}\n width={size}\n height={size}\n viewBox=\"0 0 24 24\"\n fill={color}\n fillRule=\"evenodd\"\n className={className}\n xmlns=\"http://www.w3.org/2000/svg\"\n onHoverStart={start}\n onHoverEnd={stop}\n >\n <title>Ollama</title>\n\n {/* Outer body/shell (without ears) */}\n <motion.path\n className=\"ollama-body\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center bottom\" }}\n d=\"M7.905 1.09c.216.085.411.225.588.41.295.306.544.744.734 1.263.191.522.315 1.1.362 1.68a5.054 5.054 0 012.049-.636l.051-.004c.87-.07 1.73.087 2.48.474.101.053.2.11.297.17.05-.569.172-1.134.36-1.644.19-.52.439-.957.733-1.264a1.67 1.67 0 01.589-.41c.257-.1.53-.118.796-.042.401.114.745.368 1.016.737.248.337.434.769.561 1.287.23.934.27 2.163.115 3.645l.053.04.026.019c.757.576 1.284 1.397 1.563 2.35.435 1.487.216 3.155-.534 4.088l-.018.021.002.003c.417.762.67 1.567.724 2.4l.002.03c.064 1.065-.2 2.137-.814 3.19l-.007.01.01.024c.472 1.157.62 2.322.438 3.486l-.006.039a.651.651 0 01-.747.536.648.648 0 01-.54-.742c.167-1.033.01-2.069-.48-3.123a.643.643 0 01.04-.617l.004-.006c.604-.924.854-1.83.8-2.72-.046-.779-.325-1.544-.8-2.273a.644.644 0 01.18-.886l.009-.006c.243-.159.467-.565.58-1.12a4.229 4.229 0 00-.095-1.974c-.205-.7-.58-1.284-1.105-1.683-.595-.454-1.383-.673-2.38-.61a.653.653 0 01-.632-.371c-.314-.665-.772-1.141-1.343-1.436a3.288 3.288 0 00-1.772-.332c-1.245.099-2.343.801-2.67 1.686a.652.652 0 01-.61.425c-1.067.002-1.893.252-2.497.703-.522.39-.878.935-1.066 1.588a4.07 4.07 0 00-.068 1.886c.112.558.331 1.02.582 1.269l.008.007c.212.207.257.53.109.785-.36.622-.629 1.549-.673 2.44-.05 1.018.186 1.902.719 2.536l.016.019a.643.643 0 01.095.69c-.576 1.236-.753 2.252-.562 3.052a.652.652 0 01-1.269.298c-.243-1.018-.078-2.184.473-3.498l.014-.035-.008-.012a4.339 4.339 0 01-.598-1.309l-.005-.019a5.764 5.764 0 01-.177-1.785c.044-.91.278-1.842.622-2.59l.012-.026-.002-.002c-.293-.418-.51-.953-.63-1.545l-.005-.024a5.352 5.352 0 01.093-2.49c.262-.915.777-1.701 1.536-2.269.06-.045.123-.09.186-.132-.159-1.493-.119-2.73.112-3.67.127-.518.314-.95.562-1.287.27-.368.614-.622 1.015-.737.266-.076.54-.059.797.042z\"\n />\n\n {/* Face outline */}\n <motion.path\n className=\"ollama-face\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n d=\"M12.021 10.18c.936 0 1.8.313 2.446.855.63.527 1.005 1.235 1.005 1.94 0 .888-.406 1.58-1.133 2.022-.62.375-1.451.557-2.403.557-1.009 0-1.871-.259-2.493-.734-.617-.47-.963-1.13-.963-1.845 0-.707.398-1.417 1.056-1.946.668-.537 1.55-.849 2.485-.849zm0 .896a3.07 3.07 0 00-1.916.65c-.461.37-.722.835-.722 1.25 0 .428.21.829.61 1.134.455.347 1.124.548 1.943.548.799 0 1.473-.147 1.932-.426.463-.28.7-.686.7-1.257 0-.423-.246-.89-.683-1.256-.484-.405-1.14-.643-1.864-.643z\"\n />\n\n {/* Nose */}\n <motion.path\n className=\"ollama-nose\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n d=\"M12.683 12.286l.004.004c.12.151.095.37-.056.49l-.292.23v.446a.375.375 0 01-.376.373.375.375 0 01-.376-.373v-.46l-.271-.218a.347.347 0 01-.052-.49.353.353 0 01.494-.051l.215.172.22-.174a.353.353 0 01.49.051z\"\n />\n\n {/* Left eye */}\n <motion.circle\n className=\"ollama-eye\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n cx=\"7.643\"\n cy=\"11.238\"\n r=\"0.868\"\n />\n\n {/* Right eye */}\n <motion.circle\n className=\"ollama-eye\"\n style={{ transformBox: \"fill-box\", transformOrigin: \"center\" }}\n cx=\"16.349\"\n cy=\"11.238\"\n r=\"0.868\"\n />\n </motion.svg>\n );\n },\n);\n\nBrandOllamaIcon.displayName = \"BrandOllamaIcon\";\nexport default BrandOllamaIcon;\n", | ||
| "type": "registry:ui" | ||
| }, | ||
| { | ||
| "path": "icons/types.ts", | ||
| "content": "export interface AnimatedIconProps {\n /** Icon size in pixels or CSS string */\n size?: number | string;\n /** Icon color (defaults to currentColor) */\n color?: string;\n /** SVG stroke width */\n strokeWidth?: number;\n /** Additional CSS classes */\n className?: string;\n}\n\nexport interface AnimatedIconHandle {\n startAnimation: () => void;\n stopAnimation: () => void;\n}\n", | ||
| "type": "registry:ui" | ||
| } | ||
| ], | ||
| "type": "registry:ui" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
motion/react animate function cancellation infinite repeat
💡 Result:
Short answer: animate() returns playback controls you can call to stop/cancel an infinite repeat. Use animation.stop() to commit the current value, or animation.cancel() to revert to the initial state. If you start animations with useAnimate(), those animations are automatically cleaned up when the component unmounts. [1][2][3]
Example:
-
standalone animate():
const animation = animate(x, [0, 100], { repeat: Infinity, duration: 2 });
// stop (commits current value)
animation.stop();
// or cancel (reverts to initial)
animation.cancel();. [1] -
useAnimate within a component (automatic cleanup on unmount):
const [scope, animate] = useAnimate();
useEffect(() => {
const a = animate(scope.current, { opacity: 1 }, { repeat: Infinity });
return () => a.cancel?.() /* optional explicit cleanup, but useAnimate auto-cleans on unmount */;
}, []);. [2]
Also: motion values expose stop() and jump() for fine-grained control of running animations. [3]
Sources:
[1] animate() API (controls, stop, cancel).
[2] useAnimate() hook (scoped animate + automatic cleanup).
[3] MotionValue API (stop(), jump()).
🌐 Web query:
framer motion useAnimate cancel stop repeat Infinity
💡 Result:
Short answer: store the animation object returned by animate() (or use AnimationControls) and call its cancel()/stop() method.
Examples:
-
useAnimate (motion.animate): const animation = await animate(el, { x: 100 }, { repeat: Infinity }); animation.cancel(); // or animation.stop();. The animate() return provides control methods like cancel(), complete(), play(), pause(), stop(). [1][2]
-
useAnimationControls / manual controls: const controls = useAnimationControls(); controls.start({ x: 100, transition:{ repeat: Infinity } }); controls.stop(); // stops the running animation. [3]
References
- useAnimate docs (Motion): https://motion.dev/motion/use-animate/ [7].
- framer-motion release notes (animate() added cancel/complete/stop/play/pause): framer-motion changelog/package notes. [8].
- useAnimationControls docs (stop method): motion docs for useAnimationControls. [5].
🏁 Script executed:
cd /tmp && npm info motion 2>/dev/null | grep -A 5 "description\|version" | head -20Repository: itshover/itshover
Length of output: 331
Store and cancel animation controls to prevent infinite repeat stacking.
start() launches three repeat: Infinity animations but doesn't store the returned animation controls. stop() calls animate() again to reset values, but this doesn't cancel the running loops—rapid hover cycles or repeated imperative calls will stack animations. Refactor to store the animation controls returned by each animate() call, then call .cancel() on them in stop() to properly halt the loops before resetting.
🤖 Prompt for AI Agents
In @public/r/brand-ollama-icon.json around lines 1 - 20, The start callback
launches three infinite animations via animate(...) but never keeps the returned
animation controls, so repeated starts stack loops; update BrandOllamaIcon to
store each animate(...) return (for ".ollama-body", ".ollama-eye",
".ollama-nose") in persistent refs (e.g., useRef) when start runs and in stop
call .cancel() on each stored control before calling animate(...) to reset
properties; ensure start replaces/refills the refs and stop clears them after
cancelling so repeated hover/imperative toggles do not accumulate animation
loops.
|
Hey @itstheanurag are you working on this? |
|
hey @Abhijit-Jha i did work on it, added a new video for the latest changes above, never heard your views on the new one.
|
|
LGTM @itstheanurag. |
233ec0c to
01073eb
Compare
|
@Abhijit-Jha i have resolved the merge conflicts. After this is merged, there will be 217 icons here. please have it merged before other changes are merged. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@icons/brand-xai-icon.tsx`:
- Around line 33-54: The setTimeout in start() creates an untracked timer that
can fire after unmount or after stop(); store its ID in a scoped variable or
class/Ref (e.g., mergeTimeoutId) and before scheduling a new timeout clear any
existing one to avoid stacking; call clearTimeout(mergeTimeoutId) inside stop()
and in the component unmount/useEffect cleanup so the animate(...) callbacks for
".xai-part-1"…".xai-part-4" never run on an unmounted component.
♻️ Duplicate comments (4)
icons/brand-lmstudio-icon.tsx (1)
21-45: Infinite animations stack on rapid hover cycles—store and cancel animation controls.The
start()function schedulesrepeat: Infinityanimations without storing the returned controls. Thestop()function only animates properties back to defaults but doesn't cancel the running infinite loops. Rapid hover triggers will stack animations, causing CPU overhead and visual glitches.🔧 Recommended fix
+import { forwardRef, useImperativeHandle, useCallback, useRef } from "react"; -import { forwardRef, useImperativeHandle, useCallback } from "react"; import type { AnimatedIconHandle, AnimatedIconProps } from "./types"; import { motion, useAnimate } from "motion/react"; // ... inside component: + const controlsRef = useRef<Array<{ stop?: () => void }> | null>(null); const start = useCallback(() => { + // Prevent stacking if already running + if (controlsRef.current) return; + const controls: Array<{ stop?: () => void }> = []; lines.forEach((line, i) => { const strength = 1 + i * 0.06; - animate( + const c = animate( line, { scaleX: [1, strength, 0.9, strength * 0.95, 1] }, { duration: 1.4, delay: i * 0.08, ease: "easeInOut", repeat: Infinity }, ); + controls.push(c); }); + controlsRef.current = controls; }, [animate]); const stop = useCallback(() => { + controlsRef.current?.forEach((c) => c.stop?.()); + controlsRef.current = null; animate( lines.join(", "), { scaleX: 1 }, { duration: 0.2, ease: "easeOut" }, ); }, [animate]);public/r/brand-ollama-icon.json (1)
8-12: Embedded component has the same infinite animation stacking issue.The embedded
BrandOllamaIconcode in this registry file has the same problem asbrand-lmstudio-icon.tsx:start()launchesrepeat: Infinityanimations without storing controls, andstop()doesn't cancel running loops. Apply the same fix pattern—store animation controls in a ref and call.stop()on them before resetting.public/r/brand-lmstudio-icon.json (1)
8-12: Embedded component code mirrors the source file issues.The embedded
BrandLmStudioIconcode has the same infinite animation stacking issue flagged inicons/brand-lmstudio-icon.tsx. Once the source file is fixed, regenerate this registry JSON to keep them in sync.icons/index.ts (1)
1545-1549: Inaccurate keywords for brand-midjourney-icon.MidJourney is an image generation AI, not a chatbot. The keywords "chatbot" and "conversation" are misleading for this icon.
Suggested fix
{ name: "brand-midjourney-icon", icon: BrandMidJourneyIcon, - keywords: ["midjourney", "ai", "chatbot", "conversation", "brand"], + keywords: ["midjourney", "ai", "image", "generation", "art", "brand"], },
🧹 Nitpick comments (2)
icons/brand-lmstudio-icon.tsx (1)
12-19: Hoistlinesarray outside the component to stabilize references.The
linesarray is recreated on every render. Since it's a constant, move it outside the component to avoid unnecessary allocations and to satisfy the ESLint hook dependency warning.♻️ Proposed fix
import { forwardRef, useImperativeHandle, useCallback } from "react"; import type { AnimatedIconHandle, AnimatedIconProps } from "./types"; import { motion, useAnimate } from "motion/react"; +const lines = [ + ".lm-line-1", + ".lm-line-2", + ".lm-line-3", + ".lm-line-4", + ".lm-line-5", + ".lm-line-6", +]; + const BrandLmStudioIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>( ( { size = 24, color = "currentColor", strokeWidth = 1, className = "" }, ref, ) => { const [scope, animate] = useAnimate(); - const lines = [ - ".lm-line-1", - ".lm-line-2", - ".lm-line-3", - ".lm-line-4", - ".lm-line-5", - ".lm-line-6", - ]; - const start = useCallback(() => {public/r/brand-anthropic-icon.json (1)
9-10: Misleading comment in the embedded component code.The comment says "Simple scale pulse on the slash" but the animation applies
rotateX,skewX, andscaleYtransforms, which create a 3D perspective effect rather than a pulse. Consider updating the comment to accurately describe the transform effect.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (17)
icons/brand-aistudio-icon.tsxicons/brand-anthropic-icon.tsxicons/brand-lmstudio-icon.tsxicons/brand-midjourney-icon.tsxicons/brand-ollama-icon.tsxicons/brand-xai-icon.tsxicons/index.tslib/icons.tspublic/r/brand-aistudio-icon.jsonpublic/r/brand-anthropic-icon.jsonpublic/r/brand-bags-fm-icon.jsonpublic/r/brand-lmstudio-icon.jsonpublic/r/brand-midjourney-icon.jsonpublic/r/brand-ollama-icon.jsonpublic/r/brand-xai-icon.jsonpublic/r/registry.jsonregistry.json
✅ Files skipped from review due to trivial changes (1)
- public/r/brand-bags-fm-icon.json
🚧 Files skipped from review as they are similar to previous changes (6)
- icons/brand-ollama-icon.tsx
- icons/brand-midjourney-icon.tsx
- public/r/brand-xai-icon.json
- registry.json
- public/r/brand-aistudio-icon.json
- icons/brand-anthropic-icon.tsx
🧰 Additional context used
🪛 GitHub Check: build
icons/brand-lmstudio-icon.tsx
[warning] 45-45:
React Hook useCallback has a missing dependency: 'lines'. Either include it or remove the dependency array
[warning] 37-37:
React Hook useCallback has a missing dependency: 'lines'. Either include it or remove the dependency array
🔇 Additional comments (12)
icons/brand-lmstudio-icon.tsx (1)
47-112: Imperative handle and SVG structure look good.The
useImperativeHandlecorrectly exposes the animation API, hover handlers are properly wired, and the SVG paths have appropriate transform-origin settings for the scale animation.public/r/brand-ollama-icon.json (1)
1-7: Registry metadata is correctly structured.The schema reference, dependencies, and file paths follow the expected shadcn registry format.
icons/brand-aistudio-icon.tsx (2)
30-69: Clean finite animation implementation.Unlike the other icons using
repeat: Infinity, this component uses finite-duration animations that complete naturally. No animation stacking concerns here.
12-28: No action needed—the concern is invalid.
getSparkCenterOffset()is called only via theonHoverStartevent handler, which cannot fire before the SVG is rendered and painted. By the time a user can hover, the refs are guaranteed to be attached andgetBBox()will return correct values. The guard check is defensive but not addressing a real issue here.Likely an incorrect or invalid review comment.
public/r/brand-lmstudio-icon.json (1)
1-7: Registry entry structure is correct.Dependencies and file references are properly configured.
icons/brand-xai-icon.tsx (2)
57-71: Stop and imperative handle implementation is correct.The
stop()function properly resets all parts, and the imperative handle correctly exposes the animation API. Just ensure the timeout cleanup is added as noted above.
73-120: SVG structure and hover wiring look good.The four-part X logo decomposition with individual transform origins enables the explode/merge effect nicely.
public/r/brand-midjourney-icon.json (1)
1-20: LGTM!The registry entry is well-structured. The embedded component correctly implements the animated icon pattern with
forwardRef,useAnimate, anduseImperativeHandlefor the imperative API. The sail and wave animations provide a nice nautical effect.icons/index.ts (2)
210-218: LGTM!The imports for the six new brand icons are correctly added and follow the existing naming conventions.
1525-1529: LGTM on remaining brand icon entries.The keywords for Anthropic, AI Studio, xAI, LM Studio, and Ollama are appropriate for their respective AI tools.
Also applies to: 1530-1534, 1535-1539, 1540-1544, 1550-1554
lib/icons.ts (1)
738-785: LGTM!The new icon entries correctly provide both the
-iconsuffixed names and shorter aliases, all pointing to the correct paths. This maintains consistency with the existing pattern in the file.public/r/registry.json (1)
687-720: LGTM!The six new brand icon registry entries are correctly structured with proper dependencies and file references. Since this is an auto-generated file, ensure these entries are regenerated via
npm run registry:buildif any source files change.Also applies to: 823-856, 891-907, 1095-1111
✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.
| setTimeout(() => { | ||
| animate( | ||
| ".xai-part-1", | ||
| { x: 0, y: 0 }, | ||
| { duration: 0.3, ease: "easeInOut" }, | ||
| ); | ||
| animate( | ||
| ".xai-part-2", | ||
| { x: 0, y: 0 }, | ||
| { duration: 0.3, ease: "easeInOut" }, | ||
| ); | ||
| animate( | ||
| ".xai-part-3", | ||
| { x: 0, y: 0 }, | ||
| { duration: 0.3, ease: "easeInOut" }, | ||
| ); | ||
| animate( | ||
| ".xai-part-4", | ||
| { x: 0, y: 0 }, | ||
| { duration: 0.3, ease: "easeInOut" }, | ||
| ); | ||
| }, 250); |
There was a problem hiding this comment.
setTimeout is not cleaned up—can cause stale callbacks and memory leaks.
The setTimeout in start() is not stored or cleared. If the component unmounts or stop() is called within 250ms, the merge animation callback will still execute on a potentially unmounted component or cause visual conflicts. Rapid start() calls will also stack multiple timeouts.
🔧 Recommended fix
+import { forwardRef, useImperativeHandle, useCallback, useRef, useEffect } from "react";
-import { forwardRef, useImperativeHandle, useCallback } from "react";
// ...
const BrandXaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>(
({ size = 24, color = "currentColor", className = "" }, ref) => {
const [scope, animate] = useAnimate();
+ const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const start = useCallback(() => {
+ // Clear any pending merge animation
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+
// Explode animations...
animate(".xai-part-1", { x: -4, y: -2 }, { duration: 0.25, ease: "easeOut" });
// ... other explode animations
- setTimeout(() => {
+ timeoutRef.current = setTimeout(() => {
// Merge animations...
+ timeoutRef.current = null;
}, 250);
}, [animate]);
const stop = useCallback(() => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ timeoutRef.current = null;
+ }
animate(".xai-part-1", { x: 0, y: 0 }, { duration: 0.2 });
// ... other stop animations
}, [animate]);
+ // Cleanup on unmount
+ useEffect(() => {
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+ }, []);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| setTimeout(() => { | |
| animate( | |
| ".xai-part-1", | |
| { x: 0, y: 0 }, | |
| { duration: 0.3, ease: "easeInOut" }, | |
| ); | |
| animate( | |
| ".xai-part-2", | |
| { x: 0, y: 0 }, | |
| { duration: 0.3, ease: "easeInOut" }, | |
| ); | |
| animate( | |
| ".xai-part-3", | |
| { x: 0, y: 0 }, | |
| { duration: 0.3, ease: "easeInOut" }, | |
| ); | |
| animate( | |
| ".xai-part-4", | |
| { x: 0, y: 0 }, | |
| { duration: 0.3, ease: "easeInOut" }, | |
| ); | |
| }, 250); | |
| import { forwardRef, useImperativeHandle, useCallback, useRef, useEffect } from "react"; | |
| // ... other imports | |
| const BrandXaiIcon = forwardRef<AnimatedIconHandle, AnimatedIconProps>( | |
| ({ size = 24, color = "currentColor", className = "" }, ref) => { | |
| const [scope, animate] = useAnimate(); | |
| const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); | |
| const start = useCallback(() => { | |
| // Clear any pending merge animation | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| } | |
| // Explode animations... | |
| animate(".xai-part-1", { x: -4, y: -2 }, { duration: 0.25, ease: "easeOut" }); | |
| animate(".xai-part-2", { x: 4, y: -2 }, { duration: 0.25, ease: "easeOut" }); | |
| animate(".xai-part-3", { x: -4, y: 2 }, { duration: 0.25, ease: "easeOut" }); | |
| animate(".xai-part-4", { x: 4, y: 2 }, { duration: 0.25, ease: "easeOut" }); | |
| timeoutRef.current = setTimeout(() => { | |
| animate( | |
| ".xai-part-1", | |
| { x: 0, y: 0 }, | |
| { duration: 0.3, ease: "easeInOut" }, | |
| ); | |
| animate( | |
| ".xai-part-2", | |
| { x: 0, y: 0 }, | |
| { duration: 0.3, ease: "easeInOut" }, | |
| ); | |
| animate( | |
| ".xai-part-3", | |
| { x: 0, y: 0 }, | |
| { duration: 0.3, ease: "easeInOut" }, | |
| ); | |
| animate( | |
| ".xai-part-4", | |
| { x: 0, y: 0 }, | |
| { duration: 0.3, ease: "easeInOut" }, | |
| ); | |
| timeoutRef.current = null; | |
| }, 250); | |
| }, [animate]); | |
| const stop = useCallback(() => { | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| timeoutRef.current = null; | |
| } | |
| animate(".xai-part-1", { x: 0, y: 0 }, { duration: 0.2 }); | |
| animate(".xai-part-2", { x: 0, y: 0 }, { duration: 0.2 }); | |
| animate(".xai-part-3", { x: 0, y: 0 }, { duration: 0.2 }); | |
| animate(".xai-part-4", { x: 0, y: 0 }, { duration: 0.2 }); | |
| }, [animate]); | |
| useEffect(() => { | |
| return () => { | |
| if (timeoutRef.current) { | |
| clearTimeout(timeoutRef.current); | |
| } | |
| }; | |
| }, []); | |
| // ... rest of component (useImperativeHandle and SVG) | |
| }, | |
| ); |
🤖 Prompt for AI Agents
In `@icons/brand-xai-icon.tsx` around lines 33 - 54, The setTimeout in start()
creates an untracked timer that can fire after unmount or after stop(); store
its ID in a scoped variable or class/Ref (e.g., mergeTimeoutId) and before
scheduling a new timeout clear any existing one to avoid stacking; call
clearTimeout(mergeTimeoutId) inside stop() and in the component
unmount/useEffect cleanup so the animate(...) callbacks for
".xai-part-1"…".xai-part-4" never run on an unmounted component.
added icons for
anthropic
midjourney
ollam
xai
aistudio (google)
lmStudio
these are all kinda llm related, here's the video.
anthropic-aistudio-lmstudio-midjourney-ollama-xai.mp4
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.