Skip to content

Commit ab46f60

Browse files
davidagustinclaude
andcommitted
fix: Resolve React hooks and a11y issues in ThemeToggle stories
- Extract VisualColorChangeTest render function to proper React component (VisualColorChangeTestComponent) to satisfy React hooks rules - Add type="button" to button elements - Add aria-hidden="true" to decorative SVG icons - Replace anchor links with spans in story decorators (Storybook doesn't need Next.js router) - Prefix unused context parameter with underscore Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 63bf2d0 commit ab46f60

1 file changed

Lines changed: 128 additions & 113 deletions

File tree

stories/ThemeToggle.stories.tsx

Lines changed: 128 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ function ThemeToggleComponent() {
6666

6767
return (
6868
<button
69+
type="button"
6970
onClick={toggleTheme}
7071
className="relative p-2 rounded-lg bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2 dark:focus:ring-offset-gray-800 active:scale-95"
7172
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
@@ -78,6 +79,7 @@ function ThemeToggleComponent() {
7879
stroke="currentColor"
7980
viewBox="0 0 24 24"
8081
xmlns="http://www.w3.org/2000/svg"
82+
aria-hidden="true"
8183
>
8284
<path
8385
strokeLinecap="round"
@@ -93,6 +95,7 @@ function ThemeToggleComponent() {
9395
stroke="currentColor"
9496
viewBox="0 0 24 24"
9597
xmlns="http://www.w3.org/2000/svg"
98+
aria-hidden="true"
9699
>
97100
<path
98101
strokeLinecap="round"
@@ -186,7 +189,7 @@ export const DarkTheme: Story = {
186189
},
187190
},
188191
decorators: [
189-
(Story, context) => {
192+
(Story, _context) => {
190193
React.useEffect(() => {
191194
document.documentElement.classList.add('dark');
192195
return () => document.documentElement.classList.remove('dark');
@@ -313,18 +316,12 @@ export const InNavbarContext: Story = {
313316
<div className="flex items-center gap-4">
314317
<div className="text-xl font-bold text-gray-900 dark:text-white">Coding Tricks</div>
315318
<div className="hidden md:flex items-center gap-4">
316-
<a
317-
href="#"
318-
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
319-
>
319+
<span className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white cursor-pointer">
320320
Problems
321-
</a>
322-
<a
323-
href="#"
324-
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
325-
>
321+
</span>
322+
<span className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white cursor-pointer">
326323
Topics
327-
</a>
324+
</span>
328325
</div>
329326
</div>
330327
<div className="flex items-center gap-4">
@@ -742,120 +739,138 @@ export const MultipleToggles: Story = {
742739
};
743740

744741
/**
745-
* Interactive Test: Visual Color Change - Shows the visible background color change.
746-
* Captures before and after states for visual regression testing.
742+
* Component for VisualColorChangeTest story - extracted to satisfy React hooks rules.
747743
*/
748-
export const VisualColorChangeTest: Story = {
749-
parameters: {
750-
layout: 'fullscreen',
751-
chromatic: {
752-
disableSnapshot: false,
753-
delay: 500, // Wait for animations to complete
754-
},
755-
},
756-
render: () => {
757-
const [clickCount, setClickCount] = useState(0);
758-
const { theme, toggleTheme } = useMockTheme();
744+
function VisualColorChangeTestComponent(): React.ReactElement {
745+
const [clickCount, setClickCount] = useState(0);
746+
const { theme, toggleTheme } = useMockTheme();
759747

760-
const handleClick = () => {
761-
toggleTheme();
762-
setClickCount((prev) => prev + 1);
763-
};
748+
const handleClick = () => {
749+
toggleTheme();
750+
setClickCount((prev) => prev + 1);
751+
};
764752

765-
return (
766-
<div
767-
data-testid="visual-test-container"
768-
className={`min-h-screen p-8 transition-all duration-300 ${
769-
theme === 'dark' ? 'bg-gray-900' : 'bg-gray-50'
770-
}`}
771-
>
772-
<div className="max-w-md mx-auto">
773-
<div
774-
className={`p-6 rounded-xl shadow-lg transition-all duration-300 ${
775-
theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'
776-
}`}
777-
>
778-
<h2 className="text-xl font-bold mb-4">Theme Toggle Test</h2>
779-
<p className={`mb-4 ${theme === 'dark' ? 'text-gray-300' : 'text-gray-600'}`}>
780-
Click the button below to test the theme toggle functionality. The entire background
781-
and card should change colors.
782-
</p>
783-
<div className="flex items-center justify-between">
784-
<div>
785-
<p className="text-sm font-medium">Current Theme:</p>
786-
<p
787-
data-testid="current-theme"
788-
className={`text-lg font-bold ${
789-
theme === 'dark' ? 'text-blue-400' : 'text-blue-600'
790-
}`}
791-
>
792-
{theme === 'dark' ? 'Dark Mode' : 'Light Mode'}
793-
</p>
794-
<p className="text-xs text-gray-500 mt-1">
795-
Toggled {clickCount} time{clickCount !== 1 ? 's' : ''}
796-
</p>
797-
</div>
798-
<button
799-
onClick={handleClick}
800-
data-testid="theme-toggle-btn"
801-
className={`p-3 rounded-lg transition-all duration-200 ${
802-
theme === 'dark'
803-
? 'bg-gray-700 hover:bg-gray-600 text-gray-300'
804-
: 'bg-gray-100 hover:bg-gray-200 text-gray-700'
753+
return (
754+
<div
755+
data-testid="visual-test-container"
756+
className={`min-h-screen p-8 transition-all duration-300 ${
757+
theme === 'dark' ? 'bg-gray-900' : 'bg-gray-50'
758+
}`}
759+
>
760+
<div className="max-w-md mx-auto">
761+
<div
762+
className={`p-6 rounded-xl shadow-lg transition-all duration-300 ${
763+
theme === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'
764+
}`}
765+
>
766+
<h2 className="text-xl font-bold mb-4">Theme Toggle Test</h2>
767+
<p className={`mb-4 ${theme === 'dark' ? 'text-gray-300' : 'text-gray-600'}`}>
768+
Click the button below to test the theme toggle functionality. The entire background and
769+
card should change colors.
770+
</p>
771+
<div className="flex items-center justify-between">
772+
<div>
773+
<p className="text-sm font-medium">Current Theme:</p>
774+
<p
775+
data-testid="current-theme"
776+
className={`text-lg font-bold ${
777+
theme === 'dark' ? 'text-blue-400' : 'text-blue-600'
805778
}`}
806-
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
807779
>
808-
{theme === 'light' ? (
809-
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
810-
<path
811-
strokeLinecap="round"
812-
strokeLinejoin="round"
813-
strokeWidth={2}
814-
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
815-
/>
816-
</svg>
817-
) : (
818-
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
819-
<path
820-
strokeLinecap="round"
821-
strokeLinejoin="round"
822-
strokeWidth={2}
823-
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
824-
/>
825-
</svg>
826-
)}
827-
</button>
828-
</div>
829-
</div>
830-
831-
{/* Color samples that change with theme */}
832-
<div className="mt-6 grid grid-cols-3 gap-4">
833-
<div
834-
className={`p-4 rounded-lg text-center ${
835-
theme === 'dark' ? 'bg-blue-900 text-blue-200' : 'bg-blue-100 text-blue-800'
836-
}`}
837-
>
838-
Primary
839-
</div>
840-
<div
841-
className={`p-4 rounded-lg text-center ${
842-
theme === 'dark' ? 'bg-green-900 text-green-200' : 'bg-green-100 text-green-800'
843-
}`}
844-
>
845-
Success
780+
{theme === 'dark' ? 'Dark Mode' : 'Light Mode'}
781+
</p>
782+
<p className="text-xs text-gray-500 mt-1">
783+
Toggled {clickCount} time{clickCount !== 1 ? 's' : ''}
784+
</p>
846785
</div>
847-
<div
848-
className={`p-4 rounded-lg text-center ${
849-
theme === 'dark' ? 'bg-red-900 text-red-200' : 'bg-red-100 text-red-800'
786+
<button
787+
type="button"
788+
onClick={handleClick}
789+
data-testid="theme-toggle-btn"
790+
className={`p-3 rounded-lg transition-all duration-200 ${
791+
theme === 'dark'
792+
? 'bg-gray-700 hover:bg-gray-600 text-gray-300'
793+
: 'bg-gray-100 hover:bg-gray-200 text-gray-700'
850794
}`}
795+
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
851796
>
852-
Danger
853-
</div>
797+
{theme === 'light' ? (
798+
<svg
799+
className="w-6 h-6"
800+
fill="none"
801+
stroke="currentColor"
802+
viewBox="0 0 24 24"
803+
aria-hidden="true"
804+
>
805+
<path
806+
strokeLinecap="round"
807+
strokeLinejoin="round"
808+
strokeWidth={2}
809+
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
810+
/>
811+
</svg>
812+
) : (
813+
<svg
814+
className="w-6 h-6"
815+
fill="none"
816+
stroke="currentColor"
817+
viewBox="0 0 24 24"
818+
aria-hidden="true"
819+
>
820+
<path
821+
strokeLinecap="round"
822+
strokeLinejoin="round"
823+
strokeWidth={2}
824+
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
825+
/>
826+
</svg>
827+
)}
828+
</button>
829+
</div>
830+
</div>
831+
832+
{/* Color samples that change with theme */}
833+
<div className="mt-6 grid grid-cols-3 gap-4">
834+
<div
835+
className={`p-4 rounded-lg text-center ${
836+
theme === 'dark' ? 'bg-blue-900 text-blue-200' : 'bg-blue-100 text-blue-800'
837+
}`}
838+
>
839+
Primary
840+
</div>
841+
<div
842+
className={`p-4 rounded-lg text-center ${
843+
theme === 'dark' ? 'bg-green-900 text-green-200' : 'bg-green-100 text-green-800'
844+
}`}
845+
>
846+
Success
847+
</div>
848+
<div
849+
className={`p-4 rounded-lg text-center ${
850+
theme === 'dark' ? 'bg-red-900 text-red-200' : 'bg-red-100 text-red-800'
851+
}`}
852+
>
853+
Danger
854854
</div>
855855
</div>
856856
</div>
857-
);
857+
</div>
858+
);
859+
}
860+
861+
/**
862+
* Interactive Test: Visual Color Change - Shows the visible background color change.
863+
* Captures before and after states for visual regression testing.
864+
*/
865+
export const VisualColorChangeTest: Story = {
866+
parameters: {
867+
layout: 'fullscreen',
868+
chromatic: {
869+
disableSnapshot: false,
870+
delay: 500, // Wait for animations to complete
871+
},
858872
},
873+
render: () => <VisualColorChangeTestComponent />,
859874
decorators: [
860875
(Story) => (
861876
<MockThemeProvider initialTheme="light">

0 commit comments

Comments
 (0)