Skip to content

Commit

Permalink
feat(ui/docs/overview): add neon gradient card to hero
Browse files Browse the repository at this point in the history
  • Loading branch information
anakafeel authored and MFarabi619 committed Jan 1, 2025
1 parent b4d45ac commit d773380
Show file tree
Hide file tree
Showing 6 changed files with 501 additions and 4 deletions.
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>


<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

0 comments on commit d773380

Please sign in to comment.