Skip to content
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
950 changes: 950 additions & 0 deletions docs/motion-enhancement-plan.md

Large diffs are not rendered by default.

12 changes: 9 additions & 3 deletions lib/components/Card/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { CardInitials } from './CardInitials'
import { CardPeopleMeta } from './CardPeopleMeta'
import { CardStats } from './CardStats'
import { CardVideo } from './CardVideo'
import { useScrollReveal } from '../../hooks/useScrollReveal'

export interface CardProps {
children: React.ReactNode
Expand All @@ -24,6 +25,7 @@ export interface CardProps {
noHover?: boolean
leftBorder?: boolean
isDark?: boolean
revealOnScroll?: boolean
}

export const CardWrapper = ({
Expand All @@ -35,18 +37,22 @@ export const CardWrapper = ({
noHover,
leftBorder,
isDark,
revealOnScroll = true,
}: CardProps) => {
const { ref, isVisible } = useScrollReveal<HTMLDivElement>({ disabled: !revealOnScroll })

const waveBg = hasWave ? 'pb-28 sm:pb-44 md:pb-20' : ''
const bgStyles = isGrey ? `bg-cu-black-50 ${waveBg}` : 'bg-white shadow-lg shadow-cu-black-100 dark:shadow-none'
const centerText = isCenter ? 'text-center' : ''
const centerTextDesktop = isCenterDesktop ? 'md:text-center' : ''
const hoverStyles = noHover
? ''
: 'group duration-300 ease-in hover:scale-[1.02] hover:shadow-cu-black-200 dark:hover:shadow-none'
const hoverStyles = noHover ? '' : 'group hover:shadow-cu-black-200 dark:hover:shadow-none'
const addRedBorder = leftBorder ? 'border-l-8 border-l-cu-red' : ''

return (
<div
ref={ref}
data-cu-reveal={revealOnScroll ? '' : undefined}
data-revealed={isVisible ? 'true' : 'false'}
className={`not-prose cu-card relative rounded-lg @container md:max-w-xl flex flex-col gap-3 ${bgStyles} ${centerText} ${centerTextDesktop} ${addRedBorder} ${hoverStyles} ${isDark ? 'dark' : ''} dark:text-white dark:bg-cu-black`}
>
{children}
Expand Down
64 changes: 64 additions & 0 deletions lib/components/Card/styles.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,67 @@
.cu-card {
transition: box-shadow 250ms cubic-bezier(0.2, 0, 0, 1);
}

.cu-card[data-cu-reveal] {
--cu-card-stagger: 0ms;
opacity: 0;
transform: translateY(12px);
transition:
opacity 700ms cubic-bezier(0.215, 0.61, 0.355, 1) var(--cu-card-stagger),
transform 700ms cubic-bezier(0.215, 0.61, 0.355, 1) var(--cu-card-stagger),
box-shadow 250ms cubic-bezier(0.2, 0, 0, 1);
}

.cu-card[data-cu-reveal]:nth-child(2) {
--cu-card-stagger: 120ms;
}
.cu-card[data-cu-reveal]:nth-child(3) {
--cu-card-stagger: 240ms;
}
.cu-card[data-cu-reveal]:nth-child(4) {
--cu-card-stagger: 360ms;
}
.cu-card[data-cu-reveal]:nth-child(5) {
--cu-card-stagger: 480ms;
}
.cu-card[data-cu-reveal]:nth-child(6) {
--cu-card-stagger: 600ms;
}
.cu-card[data-cu-reveal]:nth-child(7) {
--cu-card-stagger: 720ms;
}
.cu-card[data-cu-reveal]:nth-child(8) {
--cu-card-stagger: 840ms;
}
.cu-card[data-cu-reveal]:nth-child(9) {
--cu-card-stagger: 960ms;
}
.cu-card[data-cu-reveal]:nth-child(10) {
--cu-card-stagger: 1080ms;
}
.cu-card[data-cu-reveal]:nth-child(11) {
--cu-card-stagger: 1200ms;
}
.cu-card[data-cu-reveal]:nth-child(12) {
--cu-card-stagger: 1320ms;
}
.cu-card[data-cu-reveal]:nth-child(n + 13) {
--cu-card-stagger: 1320ms;
}

.cu-card[data-cu-reveal][data-revealed='true'] {
opacity: 1;
transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
.cu-card[data-cu-reveal] {
opacity: 1;
transform: none;
transition: box-shadow 250ms cubic-bezier(0.2, 0, 0, 1);
}
}

.cu-card > figure:last-child {
@apply mb-6;
}
Expand Down
13 changes: 11 additions & 2 deletions lib/components/Listing/Listing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@ import { ListingInitials } from './ListingInitials'
import { ListingEventMeta } from './ListingEventMeta'
import { ListingPeopleMeta } from './ListingPeopleMeta'
import { ListingFooter } from './ListingFooter'
import { useScrollReveal } from '../../hooks/useScrollReveal'

export interface ListingProps {
children: React.ReactNode
revealOnScroll?: boolean
}

export const ListingWrapper = ({ children }: ListingProps) => {
export const ListingWrapper = ({ children, revealOnScroll = true }: ListingProps) => {
const { ref, isVisible } = useScrollReveal<HTMLLIElement>({ disabled: !revealOnScroll })

return (
<li className={`cu-listing not-prose relative overflow-hidden @container bg-white p-6 md:p-8 !my-0`}>
<li
ref={ref}
data-cu-reveal={revealOnScroll ? '' : undefined}
data-revealed={isVisible ? 'true' : 'false'}
className={`cu-listing not-prose relative overflow-hidden @container bg-white p-6 md:p-8 !my-0`}
>
<div className="flex flex-col @lg:md:flex-row gap-5 @lg:md:gap-7 h-full">{children}</div>
</li>
)
Expand Down
59 changes: 59 additions & 0 deletions lib/components/Listing/styles.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,62 @@
.cu-listing[data-cu-reveal] {
--cu-listing-stagger: 0ms;
opacity: 0;
transform: translateY(12px);
transition:
opacity 700ms cubic-bezier(0.215, 0.61, 0.355, 1) var(--cu-listing-stagger),
transform 700ms cubic-bezier(0.215, 0.61, 0.355, 1) var(--cu-listing-stagger);
}

.cu-listing[data-cu-reveal]:nth-child(2) {
--cu-listing-stagger: 120ms;
}
.cu-listing[data-cu-reveal]:nth-child(3) {
--cu-listing-stagger: 240ms;
}
.cu-listing[data-cu-reveal]:nth-child(4) {
--cu-listing-stagger: 360ms;
}
.cu-listing[data-cu-reveal]:nth-child(5) {
--cu-listing-stagger: 480ms;
}
.cu-listing[data-cu-reveal]:nth-child(6) {
--cu-listing-stagger: 600ms;
}
.cu-listing[data-cu-reveal]:nth-child(7) {
--cu-listing-stagger: 720ms;
}
.cu-listing[data-cu-reveal]:nth-child(8) {
--cu-listing-stagger: 840ms;
}
.cu-listing[data-cu-reveal]:nth-child(9) {
--cu-listing-stagger: 960ms;
}
.cu-listing[data-cu-reveal]:nth-child(10) {
--cu-listing-stagger: 1080ms;
}
.cu-listing[data-cu-reveal]:nth-child(11) {
--cu-listing-stagger: 1200ms;
}
.cu-listing[data-cu-reveal]:nth-child(12) {
--cu-listing-stagger: 1320ms;
}
.cu-listing[data-cu-reveal]:nth-child(n + 13) {
--cu-listing-stagger: 1320ms;
}

.cu-listing[data-cu-reveal][data-revealed='true'] {
opacity: 1;
transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
.cu-listing[data-cu-reveal] {
opacity: 1;
transform: none;
transition: none;
}
}

/* Listing Images */

.cu-listing a {
Expand Down
22 changes: 22 additions & 0 deletions lib/hooks/useReducedMotion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useEffect, useState } from 'react'

const QUERY = '(prefers-reduced-motion: reduce)'

const getInitial = (): boolean => {
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return false
return window.matchMedia(QUERY).matches
}

export const useReducedMotion = (): boolean => {
const [reduced, setReduced] = useState<boolean>(getInitial)

useEffect(() => {
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') return
const mql = window.matchMedia(QUERY)
const onChange = (event: MediaQueryListEvent) => setReduced(event.matches)
mql.addEventListener('change', onChange)
return () => mql.removeEventListener('change', onChange)
}, [])

return reduced
}
48 changes: 48 additions & 0 deletions lib/hooks/useScrollReveal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { useEffect, useRef, useState } from 'react'
import { useReducedMotion } from './useReducedMotion'

export interface ScrollRevealOptions {
threshold?: number
rootMargin?: string
once?: boolean
disabled?: boolean
}

export const useScrollReveal = <T extends HTMLElement = HTMLDivElement>(options: ScrollRevealOptions = {}) => {
const { threshold = 0.15, rootMargin = '0px 0px -10% 0px', once = true, disabled = false } = options
const reducedMotion = useReducedMotion()
const ref = useRef<T>(null)
const [isVisible, setIsVisible] = useState<boolean>(() => reducedMotion || disabled)

useEffect(() => {
if (reducedMotion || disabled) {
setIsVisible(true)
return
}

const node = ref.current
if (!node || typeof IntersectionObserver === 'undefined') {
setIsVisible(true)
return
}

const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setIsVisible(true)
if (once) observer.unobserve(entry.target)
} else if (!once) {
setIsVisible(false)
}
})
},
{ threshold, rootMargin },
)

observer.observe(node)
return () => observer.disconnect()
}, [threshold, rootMargin, once, disabled, reducedMotion])

return { ref, isVisible }
}
4 changes: 4 additions & 0 deletions lib/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,9 @@ export { iconNames } from '../public/assets/font-awesome/icon-list.js'
export { bgImageNames } from '../public/assets/bg-images/bg-image-list.js'
export { videoBanner, videoSplash } from './utils/json-lists.js'

// Hooks
export { useReducedMotion } from './hooks/useReducedMotion'
export { useScrollReveal } from './hooks/useScrollReveal'

// Utils
export * as utils from './utils/propClasses'
28 changes: 22 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
"import": "./dist/rds.es.js",
"require": "./dist/rds.umd.js"
},
"./dist/style.css": "./dist/style.css"
"./dist/style.css": "./dist/style.css",
"./dist/vanilla-js/cu-motion.js": "./dist/vanilla-js/cu-motion.js"
},
"files": [
"dist"
Expand Down
Loading
Loading