Skip to content

Commit 2590849

Browse files
committed
chore: fix AnimatedIcon story import and catch unused var
1 parent 82597e8 commit 2590849

10 files changed

Lines changed: 218 additions & 7 deletions

File tree

coverage/clover.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<coverage generated="1769596402493" clover="3.2.0">
3-
<project timestamp="1769596402494" name="All files">
2+
<coverage generated="1769599883357" clover="3.2.0">
3+
<project timestamp="1769599883357" name="All files">
44
<metrics statements="315" coveredstatements="247" conditionals="310" coveredconditionals="168" methods="80" coveredmethods="62" elements="705" coveredelements="477" complexity="0" loc="315" ncloc="315" packages="6" files="6" classes="6"/>
55
<package name="jest">
66
<metrics statements="19" coveredstatements="19" conditionals="11" coveredconditionals="8" methods="5" coveredmethods="5"/>

coverage/coverage-final.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

coverage/lcov-report/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ <h1>All files</h1>
176176
<div class='footer quiet pad2 space-top1 center small'>
177177
Code coverage generated by
178178
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
179-
at 2026-01-28T10:33:22.462Z
179+
at 2026-01-28T11:31:23.321Z
180180
</div>
181181
<script src="prettify.js"></script>
182182
<script>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { Meta, Canvas, Story } from '@storybook/addon-docs/blocks'
2+
import AnimatedIcon from './index'
3+
import * as Stories from './AnimatedIcon.stories'
4+
import { Music, Heart, Play, Smartphone } from 'lucide-react'
5+
6+
<Meta title="Stateless/AnimatedIcon" of={Stories} />
7+
8+
# AnimatedIcon
9+
10+
A lightweight animated wrapper for icon components. Provides a small set of animation *variants* and *modes* and intentionally defaults to `mode="hover"` for performance. Animations respect the user's `prefers-reduced-motion` preference.
11+
12+
import { ArgsTable } from '../../../storybook/ArgsTable'
13+
14+
## Props
15+
16+
<ArgsTable
17+
rows={[
18+
{ name: 'variant', type: "'spin' | 'pulse' | 'bounce' | 'draw'", default: "'spin'", description: 'Animation variant to apply to the icon.' },
19+
{ name: 'mode', type: "'hover' | 'auto' | 'idle'", default: "'hover'", description: 'How the animation is triggered: hover interactions, always running, or idle.' },
20+
{ name: 'className', type: 'string', default: "''", description: 'Additional className forwarded to the motion wrapper.' },
21+
{ name: 'children', type: 'React.ReactElement', default: '', description: 'Icon element (SVG or icon component).' },
22+
]}
23+
/>
24+
25+
---
26+
27+
## Examples
28+
29+
### Basic playground
30+
31+
<Canvas>
32+
<Story name="Playground">
33+
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
34+
<AnimatedIcon variant="spin" mode="hover"><Music size={28} /></AnimatedIcon>
35+
<AnimatedIcon variant="pulse" mode="hover"><Heart size={28} /></AnimatedIcon>
36+
<AnimatedIcon variant="bounce" mode="hover"><Play size={28} /></AnimatedIcon>
37+
<AnimatedIcon variant="draw" mode="hover"><Music size={28} /></AnimatedIcon>
38+
</div>
39+
</Story>
40+
</Canvas>
41+
42+
### Inline usage (code)
43+
44+
```jsx
45+
<AnimatedIcon variant="spin" mode="hover">
46+
<Music />
47+
</AnimatedIcon>
48+
```
49+
50+
---
51+
52+
## Notes
53+
54+
- **Accessibility:** If the icon is interactive (button/link), ensure it has an accessible name (aria-label or visible label). For purely decorative icons use `aria-hidden="true"` on the child SVG.
55+
- **Performance:** Prefer `mode="hover"` for many icons; reserve `mode="auto"` for isolated or small numbers of continuously-animated icons.
56+
- **Reduced motion:** Animations are disabled when `prefers-reduced-motion` is enabled on the user's system.
57+
58+
> Tip: Use `variant="draw"` for decorative, one-off reveal animations and `variant="spin"` or `pulse` for status/feedback indicators.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import type { Meta, StoryObj } from '@storybook/react-vite'
2+
import AnimatedIcon from './index'
3+
import { Music, Heart, Play } from 'lucide-react'
4+
5+
const meta: Meta<typeof AnimatedIcon> = {
6+
title: 'Stateless/AnimatedIcon',
7+
component: AnimatedIcon,
8+
argTypes: {
9+
variant: { control: { type: 'radio' }, options: ['spin', 'pulse', 'bounce', 'draw'] },
10+
mode: { control: { type: 'radio' }, options: ['hover', 'auto', 'idle'] },
11+
className: { control: 'text' },
12+
},
13+
}
14+
15+
export default meta
16+
type Story = StoryObj<typeof AnimatedIcon>
17+
18+
export const Playground: Story = {
19+
args: {
20+
variant: 'spin',
21+
mode: 'hover',
22+
children: <Music size={24} />,
23+
},
24+
}
25+
26+
export const Variants: Story = {
27+
render: (args) => (
28+
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
29+
<AnimatedIcon {...args} variant="spin">
30+
<Music size={24} />
31+
</AnimatedIcon>
32+
<AnimatedIcon {...args} variant="pulse">
33+
<Heart size={24} />
34+
</AnimatedIcon>
35+
<AnimatedIcon {...args} variant="bounce">
36+
<Play size={24} />
37+
</AnimatedIcon>
38+
<AnimatedIcon {...args} variant="draw">
39+
<Music size={24} />
40+
</AnimatedIcon>
41+
</div>
42+
),
43+
args: { mode: 'hover' },
44+
}
45+
46+
export const AutoMode: Story = {
47+
render: (args) => (
48+
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
49+
<AnimatedIcon {...args} variant="spin" mode="auto">
50+
<Music size={28} />
51+
</AnimatedIcon>
52+
<AnimatedIcon {...args} variant="pulse" mode="auto">
53+
<Heart size={28} />
54+
</AnimatedIcon>
55+
<AnimatedIcon {...args} variant="bounce" mode="auto">
56+
<Play size={28} />
57+
</AnimatedIcon>
58+
</div>
59+
),
60+
args: {},
61+
}

src/components/stateless/AnimatedIcon/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@ const AnimatedIcon: React.FC<Props> = ({ children, variant = 'spin', mode = 'hov
5050

5151
if (childType && (childType === 'svg' || childType.render || childType.displayName)) {
5252
try {
53-
const MotionIcon = motion(children.type as any)
53+
const MotionIcon = motion.create(children.type as any)
5454
return React.createElement(MotionIcon, { ...(children.props as any), ...rest, ...motionProps })
55-
} catch (_) {
55+
} catch {
5656
return (
5757
<motion.span {...motionProps} {...rest}>
5858
{children}

src/pages/demo/index.jsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react'
2-
import { Smartphone, Monitor, Rocket } from 'lucide-react'
2+
import { Smartphone, Monitor, Rocket, Music } from 'lucide-react'
33
import PageContainer from '@stateless/PageContainer'
44
import FixTabPanel from '@stateless/FixTabPanel'
55

@@ -336,6 +336,31 @@ const ProDemo = () => {
336336
config={{}}
337337
/>
338338
</div>
339+
340+
<div className="mt-8 mb-6 rounded-lg border p-4 shadow-sm">
341+
<h3 className="mb-2 text-lg font-semibold">AnimatedIcon Demo</h3>
342+
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
343+
<AnimatedIcon variant="spin" mode="hover">
344+
<Smartphone className="h-6 w-6 text-gray-700" />
345+
</AnimatedIcon>
346+
<AnimatedIcon variant="pulse" mode="hover">
347+
<Monitor className="h-6 w-6 text-gray-700" />
348+
</AnimatedIcon>
349+
<AnimatedIcon variant="bounce" mode="hover">
350+
<Rocket className="h-6 w-6 text-gray-700" />
351+
</AnimatedIcon>
352+
<AnimatedIcon variant="draw" mode="hover">
353+
<Music className="h-6 w-6 text-gray-700" />
354+
</AnimatedIcon>
355+
<AnimatedIcon variant="spin" mode="auto">
356+
<Rocket className="h-6 w-6 text-red-500" />
357+
</AnimatedIcon>
358+
</div>
359+
<p className="text-muted-foreground mt-2 text-sm">
360+
Hover icons to see interaction animations; the rightmost uses <code>mode="auto"</code>.
361+
</p>
362+
</div>
363+
339364
<OrbitingCircles items={orbitingItems} />
340365
<StarBack />
341366

src/storybook/ArgsTable.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import ArgsTableFallback from './ArgsTableFallback'
2+
3+
export const ArgsTable = ArgsTableFallback
4+
export default ArgsTable
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react'
2+
3+
type Row = { name: string; type: string; default?: string; description?: string }
4+
5+
export type ArgsTableFallbackProps = {
6+
rows?: Row[]
7+
of?: any
8+
}
9+
10+
export const ArgsTableFallback: React.FC<ArgsTableFallbackProps> = ({ rows = [] }) => {
11+
// If rows not provided, show helpful message
12+
if (!rows || rows.length === 0) {
13+
return (
14+
<div style={{ padding: '12px 16px', border: '1px solid #eee', borderRadius: 6 }}>
15+
<strong>ArgsTable</strong>
16+
<div style={{ marginTop: 8 }}>
17+
Automatic prop table generation is not available in this environment. You can pass an explicit
18+
<code style={{ marginLeft: 6, padding: '2px 6px', background: '#f5f5f5', borderRadius: 4 }}>rows</code>
19+
prop to <code>ArgsTable</code>, or keep a manual table in MDX for full control.
20+
</div>
21+
</div>
22+
)
23+
}
24+
25+
return (
26+
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
27+
<thead>
28+
<tr>
29+
<th style={{ textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #e6e6e6' }}>Name</th>
30+
<th style={{ textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #e6e6e6' }}>Type</th>
31+
<th style={{ textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #e6e6e6' }}>Default</th>
32+
<th style={{ textAlign: 'left', padding: '6px 8px', borderBottom: '1px solid #e6e6e6' }}>Description</th>
33+
</tr>
34+
</thead>
35+
<tbody>
36+
{rows.map((r) => (
37+
<tr key={r.name}>
38+
<td style={{ padding: '6px 8px', borderBottom: '1px solid #f1f1f1' }}>
39+
<code>{r.name}</code>
40+
</td>
41+
<td style={{ padding: '6px 8px', borderBottom: '1px solid #f1f1f1' }}>{r.type}</td>
42+
<td style={{ padding: '6px 8px', borderBottom: '1px solid #f1f1f1' }}>{r.default ?? '—'}</td>
43+
<td style={{ padding: '6px 8px', borderBottom: '1px solid #f1f1f1' }}>{r.description}</td>
44+
</tr>
45+
))}
46+
</tbody>
47+
</table>
48+
)
49+
}
50+
51+
export default ArgsTableFallback

src/storybook/blocks-shim.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export * from '@storybook/addon-docs/blocks'
2+
3+
// Provide a safe fallback for ArgsTable when the real addon doesn't export it.
4+
import { ArgsTableFallback } from './ArgsTableFallback'
5+
6+
// Re-export ArgsTable if it's available; otherwise export our fallback implementation.
7+
import * as Blocks from '@storybook/addon-docs/blocks'
8+
9+
export const ArgsTable = (Blocks as any).ArgsTable ?? ArgsTableFallback
10+
11+
// Also provide a default export if consumers import the module as a namespace
12+
export default { ...(Blocks as any), ArgsTable }

0 commit comments

Comments
 (0)