Fix shadows for static and mod modals

This commit is contained in:
Justin Edmund 2023-01-21 06:04:40 -08:00
parent c9315ab397
commit 7ad9032e86
12 changed files with 139 additions and 75 deletions

View file

@ -2,7 +2,7 @@
gap: 0;
padding-bottom: $unit;
& > div:not(.DialogHeader) {
.content {
display: flex;
flex-direction: column;
gap: $unit-2x;

View file

@ -19,6 +19,7 @@ import './index.scss'
const AboutModal = () => {
const { t } = useTranslation('common')
const headerRef = React.createRef<HTMLDivElement>()
return (
<Dialog>
@ -29,11 +30,20 @@ const AboutModal = () => {
</DialogTrigger>
<DialogContent
className="About"
title={t('menu.about')}
headerref={headerRef}
onOpenAutoFocus={(event) => event.preventDefault()}
onEscapeKeyDown={() => {}}
>
<div className="sections">
<div className="DialogHeader" ref={headerRef}>
<DialogTitle className="DialogTitle">{t('menu.about')}</DialogTitle>
<DialogClose className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</DialogClose>
</div>
<div className="content">
<section>
<p>
Granblue.team is a tool to save and share team comps for{' '}

View file

@ -85,6 +85,9 @@ const AccountModal = (props: Props) => {
const [languageOpen, setLanguageOpen] = useState(false)
const [themeOpen, setThemeOpen] = useState(false)
// Refs
const headerRef = React.createRef<HTMLDivElement>()
// UI management
function openChange(open: boolean) {
setOpen(open)
@ -286,10 +289,11 @@ const AccountModal = (props: Props) => {
</DialogTrigger>
<DialogContent
className="Account"
headerref={headerRef}
onOpenAutoFocus={(event: Event) => {}}
onEscapeKeyDown={onEscapeKeyDown}
>
<div className="DialogHeader">
<div className="DialogHeader" ref={headerRef}>
<div className="DialogTop">
<DialogTitle className="SubTitle">
{t('modals.settings.title')}

View file

@ -1,7 +1,7 @@
.Changelog.DialogContent {
gap: 0;
& > div:not(.DialogHeader) {
.updates {
padding: 0 $unit-4x;
}

View file

@ -16,6 +16,7 @@ import './index.scss'
const ChangelogModal = () => {
const { t } = useTranslation('common')
const headerRef = React.createRef<HTMLDivElement>()
return (
<Dialog>
@ -27,9 +28,21 @@ const ChangelogModal = () => {
<DialogContent
className="Changelog"
title={t('menu.changelog')}
headerref={headerRef}
onOpenAutoFocus={(event) => event.preventDefault()}
onEscapeKeyDown={() => {}}
>
<div className="DialogHeader" ref={headerRef}>
<DialogTitle className="DialogTitle">
{t('menu.changelog')}
</DialogTitle>
<DialogClose className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</DialogClose>
</div>
<div className="updates">
<section className="version" data-version="1.0">
<div className="top">

View file

@ -35,7 +35,7 @@
display: flex;
flex-direction: column;
gap: $unit-4x;
padding: 0 $unit-4x;
padding: 0 $unit-4x $unit-2x;
section {
display: flex;

View file

@ -81,45 +81,23 @@ const CharacterModal = ({
// UI state
const [open, setOpen] = useState(false)
const [scrolled, setScrolled] = useState(false)
const [formValid, setFormValid] = useState(false)
// Refs
const headerRef = React.createRef<HTMLDivElement>()
const footerRef = React.createRef<HTMLDivElement>()
// Classes
const headerClasses = classNames({
DialogHeader: true,
Short: true,
Scrolled: scrolled,
})
// Callbacks and Hooks
const onScroll = useCallback((event: Event) => {
// const dialogContent = event.target as HTMLDivElement
// const { scrollTop } = dialogContent
// if (scrollTop > 150) {
// console.log(scrollTop, scrollTop % 5)
// if (scrollTop > 20) setScrolled(true)
// else setScrolled(false)
// console.log('scrollTop', scrollTop)
// }
}, [])
useEffect(() => {
setOpen(modalOpen)
}, [modalOpen])
useEffect(() => {
//add eventlistener to window
// const dialogContent = document.querySelector('.DialogContent')
// if (dialogContent) {
// dialogContent.addEventListener('scroll', onScroll, { passive: true })
// // remove event on unmount to prevent a memory leak with the cleanup
// return () => {
// // what does passive do?
// dialogContent.removeEventListener('scroll', onScroll)
// }
// }
}, [])
// Character properties: Perpetuity
const [perpetuity, setPerpetuity] = useState(false)
@ -309,10 +287,12 @@ const CharacterModal = ({
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent
className="Character"
headerref={headerRef}
footerref={footerRef}
onOpenAutoFocus={(event) => event.preventDefault()}
onEscapeKeyDown={() => {}}
>
<div className={headerClasses}>
<div className={headerClasses} ref={headerRef}>
<img
alt={gridCharacter.object.name[locale]}
className="DialogImage"
@ -339,7 +319,7 @@ const CharacterModal = ({
{earringSelect()}
{awakeningSelect()}
</div>
<div className="DialogFooter">
<div className="DialogFooter" ref={footerRef}>
<Button
contained={true}
onClick={updateCharacter}

View file

@ -143,6 +143,8 @@
align-items: flex-end;
background: var(--dialog-bg);
bottom: 0;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.16);
border-top: 1px solid rgba(0, 0, 0, 0.24);
display: flex;
flex-direction: column;
padding: ($unit * 1.5) ($unit * $multiplier) $unit-3x;
@ -204,7 +206,7 @@
align-items: center;
display: flex;
flex-direction: column;
gap: $unit * 2;
gap: $unit-2x;
}
.wrapper {

View file

@ -2,19 +2,17 @@ import React, { useEffect } from 'react'
import * as DialogPrimitive from '@radix-ui/react-dialog'
import classNames from 'classnames'
import { DialogClose, DialogTitle } from '~components/Dialog'
import Overlay from '~components/Overlay'
import CrossIcon from '~public/icons/Cross.svg'
import './index.scss'
import debounce from 'lodash.debounce'
interface Props
extends React.DetailedHTMLProps<
React.DialogHTMLAttributes<HTMLDivElement>,
HTMLDivElement
> {
header?: React.ReactNode
title?: string
headerref: React.RefObject<HTMLDivElement>
footerref?: React.RefObject<HTMLDivElement>
onEscapeKeyDown: (event: KeyboardEvent) => void
onOpenAutoFocus: (event: Event) => void
}
@ -28,45 +26,91 @@ const DialogContent = React.forwardRef<HTMLDivElement, Props>(function dialog(
DialogContent: true,
})
// Refs
const headerRef = React.createRef<HTMLDivElement>()
const containerRef = React.createRef<HTMLDivElement>()
// Elements
const genericHeader = (
<div className="DialogHeader" ref={headerRef}>
<DialogTitle className="DialogTitle">
{props.title ? props.title : ''}
</DialogTitle>
<DialogClose className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</DialogClose>
</div>
)
// Handlers
function handleScroll(event: React.UIEvent<HTMLDivElement, UIEvent>) {
const headerElement = headerRef.current
const scrollTop = event.currentTarget?.scrollTop
const scrollTop = event.currentTarget.scrollTop
const scrollHeight = event.currentTarget.scrollHeight
const clientHeight = event.currentTarget.clientHeight
if (props.headerref && props.headerref.current)
manipulateHeaderShadow(props.headerref.current, scrollTop)
if (props.footerref && props.footerref.current)
manipulateFooterShadow(
props.footerref.current,
scrollTop,
scrollHeight,
clientHeight
)
}
function manipulateHeaderShadow(header: HTMLDivElement, scrollTop: number) {
const boxShadowBase = '0 2px 8px'
const maxValue = 50
if (headerElement && scrollTop >= 0) {
if (scrollTop >= 0) {
const input = scrollTop > maxValue ? maxValue : scrollTop
const boxShadowOpacity = mapRange(input, 0, maxValue, 0.0, 0.16)
const borderOpacity = mapRange(input, 0, maxValue, 0.0, 0.24)
console.log(
`Scroll top: ${scrollTop}, interpolated opacity: ${boxShadowOpacity}`
)
headerElement.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, ${boxShadowOpacity})`
headerElement.style.borderBottomColor = `rgba(0, 0, 0, ${borderOpacity})`
header.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, ${boxShadowOpacity})`
header.style.borderBottomColor = `rgba(0, 0, 0, ${borderOpacity})`
}
}
function manipulateFooterShadow(
footer: HTMLDivElement,
scrollTop: number,
scrollHeight: number,
clientHeight: number
) {
const boxShadowBase = '0 -2px 8px'
const minValue = scrollHeight - 200
const currentScroll = scrollTop + clientHeight
if (currentScroll >= minValue) {
const input = currentScroll < minValue ? minValue : currentScroll
const boxShadowOpacity = mapRange(
input,
minValue,
scrollHeight,
0.16,
0.0
)
const borderOpacity = mapRange(input, minValue, scrollHeight, 0.24, 0.0)
footer.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, ${boxShadowOpacity})`
footer.style.borderTopColor = `rgba(0, 0, 0, ${borderOpacity})`
}
}
const calculateFooterShadow = debounce(() => {
const boxShadowBase = '0 -2px 8px'
const scrollable = document.querySelector('.Scrollable')
const footer = props.footerref
if (footer && footer.current) {
if (scrollable && scrollable.clientHeight >= scrollable.scrollHeight) {
footer.current.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, 0)`
footer.current.style.borderTopColor = `rgba(0, 0, 0, 0)`
} else {
footer.current.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, 0.16)`
footer.current.style.borderTopColor = `rgba(0, 0, 0, 0.24)`
}
}
}, 100)
useEffect(() => {
window.addEventListener('resize', calculateFooterShadow)
calculateFooterShadow()
return () => {
window.removeEventListener('resize', calculateFooterShadow)
}
}, [calculateFooterShadow])
function mapRange(
value: number,
low1: number,
@ -87,12 +131,7 @@ const DialogContent = React.forwardRef<HTMLDivElement, Props>(function dialog(
onEscapeKeyDown={props.onEscapeKeyDown}
ref={forwardedRef}
>
{props.title ? genericHeader : ''}
<div
className="Scrollable"
ref={containerRef}
onScroll={handleScroll}
>
<div className="Scrollable" onScroll={handleScroll}>
{children}
</div>
</DialogPrimitive.Content>

View file

@ -24,7 +24,7 @@
}
}
& > div:not(.DialogHeader) {
.content {
display: flex;
flex-direction: column;
gap: $unit-2x;

View file

@ -17,6 +17,7 @@ import './index.scss'
const RoadmapModal = () => {
const { t } = useTranslation('roadmap')
const headerRef = React.createRef<HTMLDivElement>()
return (
<Dialog>
@ -28,10 +29,20 @@ const RoadmapModal = () => {
<DialogContent
className="Roadmap"
title={t('title')}
headerref={headerRef}
onOpenAutoFocus={(event) => event.preventDefault()}
onEscapeKeyDown={() => {}}
>
<div>
<div className="DialogHeader" ref={headerRef}>
<DialogTitle className="DialogTitle">{t('title')}</DialogTitle>
<DialogClose className="DialogClose" asChild>
<span>
<CrossIcon />
</span>
</DialogClose>
</div>
<div className="content">
<section className="notes">
<p>{t('blurb')}</p>
<p>{t('link.intro')}</p>

View file

@ -93,6 +93,10 @@ const WeaponModal = ({
const [ax2Open, setAx2Open] = useState(false)
const [awakeningOpen, setAwakeningOpen] = useState(false)
// Refs
const headerRef = React.createRef<HTMLDivElement>()
const footerRef = React.createRef<HTMLDivElement>()
useEffect(() => {
setOpen(modalOpen)
}, [modalOpen])
@ -350,10 +354,11 @@ const WeaponModal = ({
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent
className="Weapon"
headerref={headerRef}
onOpenAutoFocus={(event) => event.preventDefault()}
onEscapeKeyDown={onEscapeKeyDown}
>
<div className="DialogHeader Short">
<div className="DialogHeader Short" ref={headerRef}>
<img
alt={gridWeapon.object.name[locale]}
className="DialogImage"