Skip to content
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

docs(overview): add neon gradient card to hero page #227

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 19 additions & 3 deletions apps/docs/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,27 @@ import {

import { Feature } from "@cuhacking/docs/components/ui/feature-section"

import { NeonGradientCard } from "@cuhacking/docs/components/ui/neon-gradient-card";

Welcome to the documentation site for the [cuHacking 2025](https://www.cuhacking.ca) hackathon platform.

![2025 Docs Site Cover](https://github.com/user-attachments/assets/1a0a5cb7-95fd-49ce-a8b5-4742cccb1cc8)
<NeonGradientCard
className="animate-background-position-spin flex items-center justify-center h-auto z-5"
>
<div className="w-full h-auto overflow-hidden rounded-[calc(19px-3px)] bg-transparent">
<img
className="block w-full h-full object-cover m-0 p-0"
src="https://github.com/user-attachments/assets/1a0a5cb7-95fd-49ce-a8b5-4742cccb1cc8"
alt="2025 Docs Site Cover"
/>
</div>
</NeonGradientCard>
Comment on lines +31 to +41
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Several improvements needed for the NeonGradientCard implementation

  1. The image source URL points to a GitHub user attachment which might not be permanent. Consider moving it to a more stable location within your project's assets.

  2. The border radius calculation (calc(19px-3px)) in the className seems brittle. Consider using Tailwind's built-in border radius classes or defining a custom theme value.

  3. The z-5 class seems arbitrary and might cause stacking context issues. Consider if this z-index is really necessary.

  4. The alt text could be more descriptive to improve accessibility.

Here's a suggested improvement:

 <NeonGradientCard
-  className="animate-background-position-spin flex items-center justify-center h-auto z-5"
+  className="animate-background-position-spin flex items-center justify-center h-auto"
 >
   <div className="w-full h-auto overflow-hidden rounded-[calc(19px-3px)] bg-transparent">
     <img
       className="block w-full h-full object-cover m-0 p-0"
-      src="https://github.com/user-attachments/assets/1a0a5cb7-95fd-49ce-a8b5-4742cccb1cc8"
+      src="/assets/images/docs-site-cover-2025.png"
-      alt="2025 Docs Site Cover"
+      alt="cuHacking 2025 Documentation Site Hero Image featuring gradient effects and modern design elements"
     />
   </div>
 </NeonGradientCard>

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~31-~31: A determiner appears to be missing. Consider inserting it.
Context: ...) hackathon platform. <NeonGradientCard className="animate-background-position-s...

(AI_EN_LECTOR_MISSING_DETERMINER)



<span className="text-orange-500 font-bold">Contribution Guidelines</span>, <span className="text-yellow-400 font-bold">Concepts</span>, and <span className="text-blue-400 font-bold">References</span> for our custom, batteries-supercharged <span className="text-primary font-bold">meta-framework</span> powering all projects reside here.
<span className="font-bold animate-color-cycle-orange">Contribution Guidelines</span>,
<span className="font-bold animate-color-cycle-yellow">Concepts</span>, and
<span className="font-bold animate-color-cycle-blue">References</span> for our custom, batteries-supercharged
<span className="font-bold animate-color-cycle-primary">meta-framework</span> powering all projects reside here.

Written with 💚 in a beginner-friendly language in order to provide a smooth onboarding experience for external contributors, and act as a general open-source reference for other projects.

Expand Down Expand Up @@ -198,4 +214,4 @@ TODOS:
height={24}
/>}
/>
</Cards>
</Cards>
53 changes: 52 additions & 1 deletion apps/docs/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default {
'./mdx-components.{ts,tsx}',
'{src,components,app,content}/**/*!(*.stories|*.spec).{ts,tsx,md,mdx,html}',
'../../node_modules/fumadocs-ui/dist/**/*.js',
'./content/docs/**/*.{md,mdx}',
],
theme: {
extend: {
Expand Down Expand Up @@ -63,10 +64,60 @@ export default {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' },
},
'background-position-spin': {
'0%': { backgroundPosition: 'top center' },
'100%': { backgroundPosition: 'bottom center' },
},
'text-gradient': {
'0%': {
backgroundPosition: '0% 50%',
},
'50%': {
backgroundPosition: '100% 50%',
},
'100%': {
backgroundPosition: '0% 50%',
},
},
'color-cycle-orange': {
'0%': { color: 'theme(colors.orange.500)' },
'25%': { color: 'theme(colors.yellow.400)' },
'50%': { color: 'theme(colors.blue.400)' },
'75%': { color: 'theme(colors.primary.DEFAULT)' },
'100%': { color: 'theme(colors.orange.500)' },
},
'color-cycle-yellow': {
'0%': { color: 'theme(colors.yellow.400)' },
'25%': { color: 'theme(colors.blue.400)' },
'50%': { color: 'theme(colors.primary.DEFAULT)' },
'75%': { color: 'theme(colors.orange.500)' },
'100%': { color: 'theme(colors.yellow.400)' },
},
'color-cycle-blue': {
'0%': { color: 'theme(colors.blue.400)' },
'25%': { color: 'theme(colors.primary.DEFAULT)' },
'50%': { color: 'theme(colors.orange.500)' },
'75%': { color: 'theme(colors.yellow.400)' },
'100%': { color: 'theme(colors.blue.400)' },
},
'color-cycle-primary': {
'0%': { color: 'theme(colors.primary.DEFAULT)' },
'25%': { color: 'theme(colors.orange.500)' },
'50%': { color: 'theme(colors.yellow.400)' },
'75%': { color: 'theme(colors.blue.400)' },
'100%': { color: 'theme(colors.primary.DEFAULT)' },
},
},
animation: {
animation:
{
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
'background-position-spin':
'background-position-spin 3000ms infinite alternate',
'color-cycle-orange': 'color-cycle-orange 8s infinite',
'color-cycle-yellow': 'color-cycle-yellow 8s infinite',
'color-cycle-blue': 'color-cycle-blue 8s infinite',
'color-cycle-primary': 'color-cycle-primary 8s infinite',
},
},
},
Expand Down
60 changes: 60 additions & 0 deletions libs/docs/components/ui/neon-gradient-card.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Meta, StoryObj } from '@storybook/react'
import { NeonGradientCard } from './neon-gradient-card'

const defaultChildren = (
<div className="w-full h-auto overflow-hidden rounded-[calc(19px-3px)] bg-transparent">
<img
className="block w-full h-full object-cover m-0 p-0"
src="https://github.com/user-attachments/assets/1a0a5cb7-95fd-49ce-a8b5-4742cccb1cc8"
alt="2025 Docs Site Cover"
/>
</div>
)

const meta: Meta<typeof NeonGradientCard> = {
title: '📚 Docs Site/Neon Gradient Card',
component: NeonGradientCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
args: {
children: defaultChildren,
borderSize: 5,
borderRadius: 20,
neonColors: {
firstColor: '#ff00aa',
secondColor: '#00FFF1',
},
className: '',
},
argTypes: {
'children': {
control: false, // Disable control for this prop
},
'borderSize': {
control: { type: 'number', min: 1, max: 20 },
},
'borderRadius': {
control: { type: 'number', min: 0, max: 50 },
},
'neonColors': {
control: 'object',
},
'neonColors.firstColor': {
control: 'color',
},
'neonColors.secondColor': {
control: 'color',
},
'className': {
control: 'text',
},
},
}

export default meta

type Story = StoryObj<typeof NeonGradientCard>

export const Default: Story = {}
156 changes: 156 additions & 0 deletions libs/docs/components/ui/neon-gradient-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
'use client'

import type {
CSSProperties,
ReactElement,
ReactNode,
} from 'react'
import { cn } from '@cuhacking/shared/utils/cn'

import {
useEffect,
useRef,
useState,
} from 'react'

interface NeonColorsProps {
firstColor: string
secondColor: string
}

interface NeonGradientCardProps {
/**
* @default <div/>
* @type ReactElement
* @description
* The component to be rendered as the card
*/
as?: ReactElement
/**
* @default ""
* @type string
* @description
* The className of the card
*/
className?: string

/**
* @default ""
* @type ReactNode
* @description
* The children of the card
*/
children?: ReactNode

/**
* @default 5
* @type number
* @description
* The size of the border in pixels
*/
borderSize?: number

/**
* @default 20
* @type number
* @description
* The size of the radius in pixels
*/
borderRadius?: number

/**
* @default "{ firstColor: '#ff00aa', secondColor: '#00FFF1' }"
* @type NeonColorsProps
* @description
* The colors of the neon gradient
*/
neonColors?: NeonColorsProps

[key: string]: any
}

// Define the default neon colors outside the component to avoid passing an object expression directly as a default prop
const defaultNeonColors: NeonColorsProps = {
firstColor: '#84cc16',
secondColor: '#f97316',
}

const NeonGradientCard: React.FC<NeonGradientCardProps> = ({
className,
children,
borderSize = 2,
borderRadius = 20,
neonColors = defaultNeonColors,
...props
}) => {
const containerRef = useRef<HTMLDivElement>(null)
const [dimensions, setDimensions] = useState({ width: 0, height: 0 })

useEffect(() => {
const updateDimensions = () => {
if (containerRef.current) {
const { offsetWidth, offsetHeight } = containerRef.current
setDimensions({ width: offsetWidth, height: offsetHeight })
}
}

updateDimensions()
window.addEventListener('resize', updateDimensions)

return () => {
window.removeEventListener('resize', updateDimensions)
}
}, [])

useEffect(() => {
if (containerRef.current) {
const { offsetWidth, offsetHeight } = containerRef.current
setDimensions({ width: offsetWidth, height: offsetHeight })
}
}, [children])

return (
<div
ref={containerRef}
style={
{
'--border-size': `${borderSize}px`,
'--border-radius': `${borderRadius}px`,
'--neon-first-color': neonColors.firstColor,
'--neon-second-color': neonColors.secondColor,
'--card-width': `${dimensions.width}px`,
'--card-height': `${dimensions.height}px`,
'--card-content-radius': `${borderRadius - borderSize}px`,
'--pseudo-element-background-image': `linear-gradient(0deg, ${neonColors.firstColor}, ${neonColors.secondColor})`,
'--pseudo-element-width': `${dimensions.width + borderSize * 2}px`,
'--pseudo-element-height': `${dimensions.height + borderSize * 2}px`,
'--after-blur': `${dimensions.width / 3}px`,
} as CSSProperties
}
className={cn(
'relative z-10 size-full rounded-[var(--border-radius)]',
className,
)}
{...props}
>
<div
className={cn(
'relative size-full min-h-[100%] rounded-[var(--card-content-radius)] bg-gray-100 ',
'before:absolute before:-left-[var(--border-size)] before:-top-[var(--border-size)] before:-z-10 before:block',
'before:h-[var(--pseudo-element-height)] before:w-[var(--pseudo-element-width)] before:rounded-[var(--border-radius)] before:content-[\'\']',
'before:bg-[linear-gradient(0deg,var(--neon-first-color),var(--neon-second-color))] before:bg-[length:100%_200%]',
'before:animate-background-position-spin',
'after:absolute after:-left-[var(--border-size)] after:-top-[var(--border-size)] after:-z-10 after:block',
'after:h-[var(--pseudo-element-height)] after:w-[var(--pseudo-element-width)] after:rounded-[var(--border-radius)] after:blur-[var(--after-blur)] after:content-[\'\']',
'after:bg-[linear-gradient(0deg,var(--neon-first-color),var(--neon-second-color))] after:bg-[length:100%_200%] after:opacity-80',
'after:animate-background-position-spin',
'dark:bg-neutral-900',
)}
>
{children}
</div>
</div>
)
}

export { NeonGradientCard }
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Meta, StoryObj } from '@storybook/react'
import { NeonGradientCard } from './neon-gradient-card'

const defaultChildren = (
<div className="w-full h-auto overflow-hidden rounded-[calc(19px-3px)] bg-transparent">
<img
className="block w-full h-full object-cover m-0 p-0"
src="https://github.com/user-attachments/assets/1a0a5cb7-95fd-49ce-a8b5-4742cccb1cc8"
alt="2025 Docs Site Cover"
/>
</div>
)

const meta: Meta<typeof NeonGradientCard> = {
title: '🪄 Magic UI/Neon Gradient Card',
component: NeonGradientCard,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
args: {
children: defaultChildren,
borderSize: 5,
borderRadius: 20,
neonColors: {
firstColor: '#ff00aa',
secondColor: '#00FFF1',
},
className: '',
},
argTypes: {
'children': {
control: false, // Disable control for this prop
},
'borderSize': {
control: { type: 'number', min: 1, max: 20 },
},
'borderRadius': {
control: { type: 'number', min: 0, max: 50 },
},
'neonColors': {
control: 'object',
},
'neonColors.firstColor': {
control: 'color',
},
'neonColors.secondColor': {
control: 'color',
},
'className': {
control: 'text',
},
},
}

export default meta

type Story = StoryObj<typeof NeonGradientCard>

export const Default: Story = {}
Loading
Loading