-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat!: dark Theme Rollout #5253
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ac344c6
dcba9ae
702e050
dee1e65
7bf9bb5
202143b
930a6a1
e5e3786
de79d8c
883dc1c
c0982f9
b1d8c7b
e4324a8
c80f7bb
f30ef81
440ae3c
2820a8d
5badb2b
d73034e
7c2813c
29da255
6dc898b
2af50b8
714f545
80655ae
7c21ddf
06b0a05
c508ecd
e39ca3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,3 +25,5 @@ config/tools-ignored.json | |
| deno.lock | ||
| cypress/videos | ||
| cypress/screenshots | ||
| next-env.d.ts | ||
| *.tsbuildinfo | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,18 @@ | ||
| import Link from 'next/link'; | ||
| import React from 'react'; | ||
|
|
||
| import type { ICaseStudies } from '@/types/post'; | ||
| import type { ICaseStudies, ICaseStudy } from '@/types/post'; | ||
| import { ParagraphTypeStyle } from '@/types/typography/Paragraph'; | ||
|
|
||
| import Button from './buttons/Button'; | ||
| import Paragraph from './typography/Paragraph'; | ||
|
|
||
| interface ICaseStudyCardProps { | ||
| studies?: ICaseStudies; | ||
| } | ||
|
|
||
| /** | ||
| * @description A component that displays a list of case studies in a card format | ||
| * @description Displays case studies in a card grid layout | ||
| * @param {ICaseStudies} props.studies - The list of case studies to display | ||
| */ | ||
| export default function CaseStudyCard({ studies = [] }: ICaseStudyCardProps) { | ||
|
|
@@ -19,21 +21,26 @@ export default function CaseStudyCard({ studies = [] }: ICaseStudyCardProps) { | |
| } | ||
|
|
||
| return ( | ||
| <div className='flex flex-wrap justify-center gap-3 pt-10 lg:gap-8 lg:text-center'> | ||
| {studies.map((study, index) => ( | ||
| <a key={index} className='lg:w-[30%]' href={`casestudies/${study.id}`}> | ||
| <div | ||
| className='h-full min-h-[300px] max-w-sm overflow-hidden rounded-md border border-gray-200 bg-white p-4' | ||
| data-testid='CaseStudyCard-main' | ||
| > | ||
| <span className='mr-2'> | ||
| <img className='m-auto h-16' src={study.company.logo} alt={study.company.name} /> | ||
| </span> | ||
| <Paragraph typeStyle={ParagraphTypeStyle.md} className='my-4'> | ||
| <div className='grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 lg:gap-8'> | ||
| {studies.map((study: ICaseStudy) => ( | ||
| <Link key={study.id} href={`/casestudies/${study.id}`} className='group block h-full'> | ||
| <div className='h-full bg-white dark:bg-dark-background border border-gray-200 dark:border-gray-700 rounded-2xl p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1'> | ||
| <div className='flex items-center justify-center h-20 mb-6'> | ||
| <img src={study.company.logo} alt={study.company.name} className='max-h-16 max-w-full object-contain' /> | ||
| </div> | ||
|
|
||
| <Paragraph typeStyle={ParagraphTypeStyle.md} className='text-gray-600 dark:text-gray-400 mb-6 line-clamp-4'> | ||
| {study.company.description} | ||
| </Paragraph> | ||
|
|
||
| <div className='mt-auto'> | ||
| <Button | ||
| text='Read case study →' | ||
| className='w-full bg-primary-500 hover:bg-primary-600 text-white group-hover:bg-primary-600 transition-colors' | ||
| /> | ||
| </div> | ||
| </div> | ||
| </a> | ||
| </Link> | ||
|
Comment on lines
+26
to
+43
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Inspect Button to determine the rendered element(s).
fd -t f 'Button.tsx' components/buttons
fd -t f 'Button.tsx' components/buttons --exec cat {}Repository: asyncapi/website Length of output: 18240 Replace the The Suggested fix<Link key={study.id} href={`/casestudies/${study.id}`} className='group block h-full'>
<div className='h-full bg-white dark:bg-dark-background border border-gray-200 dark:border-gray-700 rounded-2xl p-6 transition-all duration-300 hover:shadow-xl hover:-translate-y-1'>
<div className='flex items-center justify-center h-20 mb-6'>
<img src={study.company.logo} alt={study.company.name} className='max-h-16 max-w-full object-contain' />
</div>
<Paragraph typeStyle={ParagraphTypeStyle.md} className='text-gray-600 dark:text-gray-400 mb-6 line-clamp-4'>
{study.company.description}
</Paragraph>
<div className='mt-auto'>
<span className='inline-block w-full bg-primary-500 hover:bg-primary-600 text-white group-hover:bg-primary-600 transition-colors rounded-md px-4 py-3 text-md font-semibold tracking-heading'>
Read case study →
</span>
</div>
</div>
</Link>🤖 Prompt for AI Agents |
||
| ))} | ||
| </div> | ||
| ); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| 'use client'; | ||
|
|
||
| import { useEffect, useState } from 'react'; | ||
|
|
||
| // SVG Icons as components | ||
| const SunIcon = ({ className = 'w-5 h-5' }) => ( | ||
| <svg | ||
| className={className} | ||
| fill='none' | ||
| stroke='white' | ||
| strokeWidth='2' | ||
| viewBox='0 0 24 24' | ||
| xmlns='http://www.w3.org/2000/svg' | ||
| > | ||
| <circle cx='12' cy='12' r='5' /> | ||
| <path d='M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42' /> | ||
| </svg> | ||
| ); | ||
|
|
||
| const MoonIcon = ({ className = 'w-5 h-5' }) => ( | ||
| <svg | ||
| className={`stroke-current text-opacity-85 text-zinc-600 ${className}`} | ||
| fill='none' | ||
| strokeWidth='2' | ||
| viewBox='0 0 24 24' | ||
| xmlns='http://www.w3.org/2000/svg' | ||
| > | ||
| <path d='M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z' /> | ||
| </svg> | ||
| ); | ||
|
|
||
| /** | ||
| * A modern toggle button component that switches between light and dark mode. | ||
| * Remembers the selected mode using localStorage with smooth animations. | ||
| */ | ||
| export default function DarkModeToggle() { | ||
| const [isDark, setIsDark] = useState(false); | ||
| const [mounted, setMounted] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| setMounted(true); | ||
| // Load preference on mount | ||
| const storedTheme = localStorage.getItem('theme'); | ||
| const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; // NOSONAR | ||
|
|
||
| const shouldUseDark = storedTheme === 'dark' || (!storedTheme && systemPrefersDark); | ||
|
|
||
| document.documentElement.classList.toggle('dark', shouldUseDark); | ||
| setIsDark(shouldUseDark); | ||
| }, []); | ||
|
|
||
| const toggleDarkMode = () => { | ||
| const newTheme = !isDark; | ||
|
|
||
| document.documentElement.classList.toggle('dark', newTheme); | ||
| localStorage.setItem('theme', newTheme ? 'dark' : 'light'); | ||
| setIsDark(newTheme); | ||
| }; | ||
|
|
||
| // Prevent hydration mismatch | ||
| if (!mounted) { | ||
| return <div className='w-10 h-10 rounded-lg bg-gray-100 dark:bg-gray-800 animate-pulse' />; | ||
| } | ||
|
|
||
| return ( | ||
| <button | ||
| onClick={toggleDarkMode} | ||
| className='relative p-2 mx-2 text-zinc-800 dark:text-zinc-800 hover:text-black dark:hover:text-white hover:bg-gray-100 dark:hover:bg-primary-500 rounded-lg transition-all duration-300 group' | ||
| aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'} | ||
| > | ||
| {/* Icon container with smooth rotation */} | ||
| <div className='relative w-5 h-5 transition-transform duration-300 group-hover:scale-110'> | ||
| {isDark ? ( | ||
| <SunIcon className='w-5 h-5 transition-all duration-300 rotate-0 group-hover:rotate-12' /> | ||
| ) : ( | ||
| <MoonIcon className='w-5 h-5 transition-all duration-300 rotate-0 group-hover:-rotate-12' /> | ||
| )} | ||
| </div> | ||
|
|
||
| {/* Tooltip */} | ||
| <div className='absolute -bottom-10 left-1/2 transform -translate-x-1/2 px-2 py-1 bg-gray-900 dark:bg-gray-100 text-white dark:text-gray-900 text-xs rounded opacity-0 group-hover:opacity-100 transition-opacity duration-300 pointer-events-none whitespace-nowrap'> | ||
| {isDark ? 'Light mode' : 'Dark mode'} | ||
| </div> | ||
| </button> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mt-autowill not push the button to the bottom without a flex container.The
mt-autoclass on line 36 is intended to push the button to the bottom of the card, but it only works in a flex column layout. The parentdivon line 27 lacksflex flex-col, so the button won't be anchored at the bottom when card heights vary.🐛 Proposed fix to enable flex layout
📝 Committable suggestion
🤖 Prompt for AI Agents