Merge pull request #150 from jedmund/scroll-indicator
Add scroll indicators to new scrollable modals
This commit is contained in:
commit
b6d239121d
28 changed files with 446 additions and 212 deletions
|
|
@ -2,13 +2,19 @@
|
|||
gap: 0;
|
||||
padding-bottom: $unit;
|
||||
|
||||
& > div:not(.DialogHeader) {
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
padding: 0 $unit-4x;
|
||||
}
|
||||
|
||||
.sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: $unit;
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import './index.scss'
|
|||
|
||||
const AboutModal = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
|
|
@ -29,10 +30,11 @@ const AboutModal = () => {
|
|||
</DialogTrigger>
|
||||
<DialogContent
|
||||
className="About"
|
||||
headerref={headerRef}
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onEscapeKeyDown={() => {}}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogHeader" ref={headerRef}>
|
||||
<DialogTitle className="DialogTitle">{t('menu.about')}</DialogTitle>
|
||||
<DialogClose className="DialogClose" asChild>
|
||||
<span>
|
||||
|
|
@ -41,7 +43,7 @@ const AboutModal = () => {
|
|||
</DialogClose>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="content">
|
||||
<section>
|
||||
<p>
|
||||
Granblue.team is a tool to save and share team comps for{' '}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
width: $unit * 64;
|
||||
overflow-y: hidden;
|
||||
|
||||
.Fields {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ const AccountModal = (props: Props) => {
|
|||
const [languageOpen, setLanguageOpen] = useState(false)
|
||||
const [themeOpen, setThemeOpen] = useState(false)
|
||||
|
||||
// Refs
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
const footerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
// UI management
|
||||
function openChange(open: boolean) {
|
||||
setOpen(open)
|
||||
|
|
@ -286,10 +290,12 @@ const AccountModal = (props: Props) => {
|
|||
</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Account"
|
||||
headerref={headerRef}
|
||||
footerref={footerRef}
|
||||
onOpenAutoFocus={(event: Event) => {}}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogHeader" ref={headerRef}>
|
||||
<div className="DialogTop">
|
||||
<DialogTitle className="SubTitle">
|
||||
{t('modals.settings.title')}
|
||||
|
|
@ -310,7 +316,7 @@ const AccountModal = (props: Props) => {
|
|||
{languageField()}
|
||||
{themeField()}
|
||||
</div>
|
||||
<div className="DialogFooter">
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<Button
|
||||
contained={true}
|
||||
text={t('modals.settings.buttons.confirm')}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
|
|
@ -11,6 +11,8 @@
|
|||
}
|
||||
|
||||
.Alert {
|
||||
animation: $duration-modal-open cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none
|
||||
running openModalDesktop;
|
||||
background: var(--dialog-bg);
|
||||
border-radius: $unit;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
.Changelog.DialogContent {
|
||||
gap: 0;
|
||||
padding-bottom: $unit-4x;
|
||||
|
||||
& > div:not(.DialogHeader) {
|
||||
.updates {
|
||||
padding: 0 $unit-4x;
|
||||
}
|
||||
|
||||
|
|
@ -10,6 +9,7 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-4x;
|
||||
margin-bottom: $unit-4x;
|
||||
}
|
||||
|
||||
.version {
|
||||
|
|
@ -30,6 +30,11 @@
|
|||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
gap: $unit;
|
||||
|
||||
& > h4 {
|
||||
font-weight: $medium;
|
||||
font-size: $font-regular;
|
||||
}
|
||||
}
|
||||
|
||||
.items {
|
||||
|
|
@ -66,6 +71,7 @@
|
|||
|
||||
li {
|
||||
margin-bottom: $unit-half;
|
||||
font-size: $font-regular;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import './index.scss'
|
|||
|
||||
const ChangelogModal = () => {
|
||||
const { t } = useTranslation('common')
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
|
|
@ -26,10 +27,12 @@ const ChangelogModal = () => {
|
|||
</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Changelog"
|
||||
title={t('menu.changelog')}
|
||||
headerref={headerRef}
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onEscapeKeyDown={() => {}}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogHeader" ref={headerRef}>
|
||||
<DialogTitle className="DialogTitle">
|
||||
{t('menu.changelog')}
|
||||
</DialogTitle>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ const CharacterConflictModal = (props: Props) => {
|
|||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
// Refs
|
||||
const footerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(props.open)
|
||||
}, [setOpen, props.open])
|
||||
|
|
@ -73,42 +76,52 @@ const CharacterConflictModal = (props: Props) => {
|
|||
<Dialog open={open} onOpenChange={openChange}>
|
||||
<DialogContent
|
||||
className="Conflict"
|
||||
footerref={footerRef}
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onEscapeKeyDown={close}
|
||||
>
|
||||
<p>
|
||||
<Trans i18nKey="modals.conflict.character"></Trans>
|
||||
</p>
|
||||
<div className="CharacterDiagram Diagram">
|
||||
<ul>
|
||||
{props.conflictingCharacters?.map((character, i) => (
|
||||
<li className="character" key={`conflict-${i}`}>
|
||||
<div className="Content">
|
||||
<p>
|
||||
<Trans i18nKey="modals.conflict.character"></Trans>
|
||||
</p>
|
||||
<div className="CharacterDiagram Diagram">
|
||||
<ul>
|
||||
{props.conflictingCharacters?.map((character, i) => (
|
||||
<li className="character" key={`conflict-${i}`}>
|
||||
<img
|
||||
alt={character.object.name[locale]}
|
||||
src={imageUrl(character.object, character.uncap_level)}
|
||||
/>
|
||||
<span>{character.object.name[locale]}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<span className="arrow">→</span>
|
||||
<div className="wrapper">
|
||||
<div className="character">
|
||||
<img
|
||||
alt={character.object.name[locale]}
|
||||
src={imageUrl(character.object, character.uncap_level)}
|
||||
alt={props.incomingCharacter?.name[locale]}
|
||||
src={imageUrl(props.incomingCharacter)}
|
||||
/>
|
||||
<span>{character.object.name[locale]}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<span className="arrow">→</span>
|
||||
<div className="wrapper">
|
||||
<div className="character">
|
||||
<img
|
||||
alt={props.incomingCharacter?.name[locale]}
|
||||
src={imageUrl(props.incomingCharacter)}
|
||||
/>
|
||||
<span>{props.incomingCharacter?.name[locale]}</span>
|
||||
<span>{props.incomingCharacter?.name[locale]}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<Button onClick={close} text={t('buttons.cancel')} />
|
||||
<Button
|
||||
onClick={props.resolveConflict}
|
||||
text={t('modals.conflict.buttons.confirm')}
|
||||
/>
|
||||
</footer>
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<div className="Buttons Span">
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={close}
|
||||
text={t('buttons.cancel')}
|
||||
/>
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={props.resolveConflict}
|
||||
text={t('modals.conflict.buttons.confirm')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<Overlay open={open} visible={true} />
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -83,11 +83,17 @@ const CharacterModal = ({
|
|||
const [open, setOpen] = useState(false)
|
||||
const [formValid, setFormValid] = useState(false)
|
||||
|
||||
// Refs
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
const footerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
// Classes
|
||||
const headerClasses = classNames({
|
||||
DialogHeader: true,
|
||||
Scrolled: scrolled,
|
||||
Short: true,
|
||||
})
|
||||
|
||||
// Callbacks and Hooks
|
||||
useEffect(() => {
|
||||
setOpen(modalOpen)
|
||||
}, [modalOpen])
|
||||
|
|
@ -281,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"
|
||||
|
|
@ -311,7 +319,7 @@ const CharacterModal = ({
|
|||
{earringSelect()}
|
||||
{awakeningSelect()}
|
||||
</div>
|
||||
<div className="DialogFooter">
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={updateCharacter}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
.Dialog {
|
||||
// animation: 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal none running
|
||||
// openModalDesktop;
|
||||
position: fixed;
|
||||
background: none;
|
||||
border: 0;
|
||||
|
|
@ -16,9 +14,13 @@
|
|||
.DialogContent {
|
||||
$multiplier: 4;
|
||||
|
||||
animation: $duration-modal-open cubic-bezier(0.16, 1, 0.3, 1) 0s 1 normal
|
||||
none running openModalDesktop;
|
||||
background: var(--dialog-bg);
|
||||
border-radius: $card-corner;
|
||||
box-sizing: border-box;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit * $multiplier;
|
||||
|
|
@ -26,6 +28,7 @@
|
|||
min-width: $unit * 48;
|
||||
// min-height: $unit-12x;
|
||||
overflow-y: scroll;
|
||||
// height: 80vh;
|
||||
max-height: 80vh;
|
||||
min-width: 580px;
|
||||
max-width: 42vw;
|
||||
|
|
@ -49,8 +52,14 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.Scrollable {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.DialogHeader {
|
||||
background: var(--dialog-bg);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $unit-2x;
|
||||
|
|
@ -137,10 +146,25 @@
|
|||
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;
|
||||
position: sticky;
|
||||
|
||||
.Buttons {
|
||||
display: flex;
|
||||
gap: $unit;
|
||||
|
||||
&.Span {
|
||||
width: 100%;
|
||||
|
||||
.Button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
|
|
@ -152,16 +176,23 @@
|
|||
&.Conflict {
|
||||
$weapon-diameter: 14rem;
|
||||
|
||||
& > p {
|
||||
line-height: 1.2;
|
||||
max-width: 400px;
|
||||
.Content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-4x;
|
||||
padding: $unit-4x $unit-4x $unit-2x $unit-4x;
|
||||
|
||||
strong {
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
||||
&:lang(ja) {
|
||||
& > p {
|
||||
font-size: $font-regular;
|
||||
line-height: 1.4;
|
||||
|
||||
strong {
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
||||
&:lang(ja) {
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,7 +229,7 @@
|
|||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit * 2;
|
||||
gap: $unit-2x;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||
import classNames from 'classnames'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import './index.scss'
|
||||
import Overlay from '~components/Overlay'
|
||||
import './index.scss'
|
||||
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> {
|
||||
headerref?: React.RefObject<HTMLDivElement>
|
||||
footerref?: React.RefObject<HTMLDivElement>
|
||||
onEscapeKeyDown: (event: KeyboardEvent) => void
|
||||
onOpenAutoFocus: (event: Event) => void
|
||||
}
|
||||
|
|
@ -18,10 +21,106 @@ const DialogContent = React.forwardRef<HTMLDivElement, Props>(function dialog(
|
|||
{ children, ...props },
|
||||
forwardedRef
|
||||
) {
|
||||
// Classes
|
||||
const classes = classNames(props.className, {
|
||||
DialogContent: true,
|
||||
})
|
||||
|
||||
// Handlers
|
||||
function handleScroll(event: React.UIEvent<HTMLDivElement, UIEvent>) {
|
||||
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 (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)
|
||||
|
||||
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,
|
||||
high1: number,
|
||||
low2: number,
|
||||
high2: number
|
||||
) {
|
||||
return low2 + ((high2 - low2) * (value - low1)) / (high1 - low1)
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogPrimitive.Portal>
|
||||
<dialog className="Dialog">
|
||||
|
|
@ -32,7 +131,9 @@ const DialogContent = React.forwardRef<HTMLDivElement, Props>(function dialog(
|
|||
onEscapeKeyDown={props.onEscapeKeyDown}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
{children}
|
||||
<div className="Scrollable" onScroll={handleScroll}>
|
||||
{children}
|
||||
</div>
|
||||
</DialogPrimitive.Content>
|
||||
</dialog>
|
||||
<Overlay visible={true} open={true} />
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ const JobSection = (props: Props) => {
|
|||
) : (
|
||||
''
|
||||
)}
|
||||
<div className="Overlay" />
|
||||
<div className="Job Overlay" />
|
||||
</div>
|
||||
<div className="JobDetails">
|
||||
{props.editable ? (
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@
|
|||
gap: $unit;
|
||||
min-width: $unit * 52;
|
||||
|
||||
form {
|
||||
.Fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc($unit / 2);
|
||||
margin-bottom: $unit-3x;
|
||||
padding: 0 $unit-3x;
|
||||
gap: $unit;
|
||||
padding: 0 $unit-4x;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import setUserToken from '~utils/setUserToken'
|
|||
import { accountState } from '~utils/accountState'
|
||||
|
||||
import Button from '~components/Button'
|
||||
import Input from '~components/LabelledInput'
|
||||
import Input from '~components/Input'
|
||||
import { Dialog, DialogTrigger, DialogClose } from '~components/Dialog'
|
||||
import DialogContent from '~components/DialogContent'
|
||||
import changeLanguage from '~utils/changeLanguage'
|
||||
|
|
@ -43,6 +43,7 @@ const LoginModal = () => {
|
|||
// Set up form refs
|
||||
const emailInput: React.RefObject<HTMLInputElement> = React.createRef()
|
||||
const passwordInput: React.RefObject<HTMLInputElement> = React.createRef()
|
||||
const footerRef: React.RefObject<HTMLDivElement> = React.createRef()
|
||||
const form: React.RefObject<HTMLInputElement>[] = [emailInput, passwordInput]
|
||||
|
||||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
|
|
@ -199,6 +200,7 @@ const LoginModal = () => {
|
|||
</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Login"
|
||||
footerref={footerRef}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
|
|
@ -212,29 +214,34 @@ const LoginModal = () => {
|
|||
</div>
|
||||
|
||||
<form className="form" onSubmit={login}>
|
||||
<Input
|
||||
className="Bound"
|
||||
name="email"
|
||||
placeholder={t('modals.login.placeholders.email')}
|
||||
onChange={handleChange}
|
||||
error={errors.email}
|
||||
ref={emailInput}
|
||||
/>
|
||||
<div className="Fields">
|
||||
<Input
|
||||
className="Bound"
|
||||
name="email"
|
||||
placeholder={t('modals.login.placeholders.email')}
|
||||
onChange={handleChange}
|
||||
error={errors.email}
|
||||
ref={emailInput}
|
||||
/>
|
||||
|
||||
<Input
|
||||
className="Bound"
|
||||
name="password"
|
||||
placeholder={t('modals.login.placeholders.password')}
|
||||
type="password"
|
||||
onChange={handleChange}
|
||||
error={errors.password}
|
||||
ref={passwordInput}
|
||||
/>
|
||||
|
||||
<Button
|
||||
disabled={!formValid}
|
||||
text={t('modals.login.buttons.confirm')}
|
||||
/>
|
||||
<Input
|
||||
className="Bound"
|
||||
name="password"
|
||||
placeholder={t('modals.login.placeholders.password')}
|
||||
type="password"
|
||||
onChange={handleChange}
|
||||
error={errors.password}
|
||||
ref={passwordInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<div className="Buttons Span">
|
||||
<Button
|
||||
disabled={!formValid}
|
||||
text={t('modals.login.buttons.confirm')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(0);
|
||||
animation: 0.24s ease-in fadeInFilter;
|
||||
animation-fill-mode: forwards;
|
||||
|
||||
&.Job {
|
||||
animation: none;
|
||||
backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(1);
|
||||
}
|
||||
|
||||
&.Visible {
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import CharacterGrid from '~components/CharacterGrid'
|
|||
import api from '~utils/api'
|
||||
import { appState, initialAppState } from '~utils/appState'
|
||||
import { GridType } from '~utils/enums'
|
||||
import { retrieveCookies } from '~utils/retrieveCookies'
|
||||
import type { DetailsObject } from '~types'
|
||||
|
||||
import './index.scss'
|
||||
|
|
@ -37,6 +38,9 @@ const Party = (props: Props) => {
|
|||
const { party } = useSnapshot(appState)
|
||||
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
|
||||
|
||||
// Retrieve cookies
|
||||
const cookies = retrieveCookies()
|
||||
|
||||
// Reset state on first load
|
||||
useEffect(() => {
|
||||
const resetState = clonedeep(initialAppState)
|
||||
|
|
@ -107,13 +111,17 @@ const Party = (props: Props) => {
|
|||
}
|
||||
|
||||
// Deleting the party
|
||||
function deleteTeam(event: React.MouseEvent<HTMLButtonElement, MouseEvent>) {
|
||||
function deleteTeam() {
|
||||
if (appState.party.editable && appState.party.id) {
|
||||
api.endpoints.parties
|
||||
.destroy({ id: appState.party.id })
|
||||
.then(() => {
|
||||
// Push to route
|
||||
router.push('/')
|
||||
if (cookies && cookies.account.username) {
|
||||
router.push(`/${cookies.account.username}`)
|
||||
} else {
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
// Clean state
|
||||
const resetState = clonedeep(initialAppState)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import LiteYouTubeEmbed from 'react-lite-youtube-embed'
|
|||
import classNames from 'classnames'
|
||||
import reactStringReplace from 'react-string-replace'
|
||||
|
||||
import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
||||
import Alert from '~components/Alert'
|
||||
|
||||
import Button from '~components/Button'
|
||||
import CharLimitedFieldset from '~components/CharLimitedFieldset'
|
||||
|
|
@ -40,9 +40,7 @@ interface Props {
|
|||
new: boolean
|
||||
editable: boolean
|
||||
updateCallback: (details: DetailsObject) => void
|
||||
deleteCallback: (
|
||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
) => void
|
||||
deleteCallback: () => void
|
||||
}
|
||||
|
||||
const PartyDetails = (props: Props) => {
|
||||
|
|
@ -60,6 +58,7 @@ const PartyDetails = (props: Props) => {
|
|||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [name, setName] = useState('')
|
||||
const [alertOpen, setAlertOpen] = useState(false)
|
||||
|
||||
const [chargeAttack, setChargeAttack] = useState(true)
|
||||
const [fullAuto, setFullAuto] = useState(false)
|
||||
|
|
@ -293,6 +292,14 @@ const PartyDetails = (props: Props) => {
|
|||
toggleDetails()
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
setAlertOpen(!alertOpen)
|
||||
}
|
||||
|
||||
function deleteParty() {
|
||||
props.deleteCallback()
|
||||
}
|
||||
|
||||
function extractYoutubeVideoIds(text: string) {
|
||||
// Initialize an array to store the video IDs
|
||||
const videoIds = []
|
||||
|
|
@ -381,42 +388,18 @@ const PartyDetails = (props: Props) => {
|
|||
)
|
||||
}
|
||||
|
||||
const deleteButton = () => {
|
||||
const deleteAlert = () => {
|
||||
if (party.editable) {
|
||||
return (
|
||||
<AlertDialog.Root>
|
||||
<AlertDialog.Trigger className="Button Blended medium destructive">
|
||||
<span className="Accessory">
|
||||
<CrossIcon />
|
||||
</span>
|
||||
<span className="Text">{t('buttons.delete')}</span>
|
||||
</AlertDialog.Trigger>
|
||||
<AlertDialog.Portal>
|
||||
<AlertDialog.Overlay className="Overlay" />
|
||||
<AlertDialog.Content className="Dialog">
|
||||
<AlertDialog.Title className="DialogTitle">
|
||||
{t('modals.delete_team.title')}
|
||||
</AlertDialog.Title>
|
||||
<AlertDialog.Description className="DialogDescription">
|
||||
{t('modals.delete_team.description')}
|
||||
</AlertDialog.Description>
|
||||
<div className="actions">
|
||||
<AlertDialog.Cancel className="Button modal">
|
||||
{t('modals.delete_team.buttons.cancel')}
|
||||
</AlertDialog.Cancel>
|
||||
<AlertDialog.Action
|
||||
className="Button modal destructive"
|
||||
onClick={(e) => props.deleteCallback(e)}
|
||||
>
|
||||
{t('modals.delete_team.buttons.confirm')}
|
||||
</AlertDialog.Action>
|
||||
</div>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Portal>
|
||||
</AlertDialog.Root>
|
||||
<Alert
|
||||
open={alertOpen}
|
||||
primaryAction={deleteParty}
|
||||
primaryActionText={t('modals.delete_team.buttons.confirm')}
|
||||
cancelAction={() => setAlertOpen(false)}
|
||||
cancelActionText={t('modals.delete_team.buttons.cancel')}
|
||||
message={t('modals.delete_team.description')}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -553,7 +536,16 @@ const PartyDetails = (props: Props) => {
|
|||
|
||||
<div className="bottom">
|
||||
<div className="left">
|
||||
{router.pathname !== '/new' ? deleteButton() : ''}
|
||||
{router.pathname !== '/new' ? (
|
||||
<Button
|
||||
accessoryIcon={<CrossIcon />}
|
||||
className="Blended medium destructive"
|
||||
onClick={handleClick}
|
||||
text={t('buttons.delete')}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
<div className="right">
|
||||
<Button text={t('buttons.cancel')} onClick={toggleDetails} />
|
||||
|
|
@ -662,6 +654,7 @@ const PartyDetails = (props: Props) => {
|
|||
</div>
|
||||
{readOnly}
|
||||
{editable}
|
||||
{deleteAlert()}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
& > div:not(.DialogHeader) {
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import './index.scss'
|
|||
|
||||
const RoadmapModal = () => {
|
||||
const { t } = useTranslation('roadmap')
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
|
|
@ -27,10 +28,12 @@ const RoadmapModal = () => {
|
|||
</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Roadmap"
|
||||
title={t('title')}
|
||||
headerref={headerRef}
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onEscapeKeyDown={() => {}}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogHeader" ref={headerRef}>
|
||||
<DialogTitle className="DialogTitle">{t('title')}</DialogTitle>
|
||||
<DialogClose className="DialogClose" asChild>
|
||||
<span>
|
||||
|
|
@ -39,7 +42,7 @@ const RoadmapModal = () => {
|
|||
</DialogClose>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="content">
|
||||
<section className="notes">
|
||||
<p>{t('blurb')}</p>
|
||||
<p>{t('link.intro')}</p>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 430px;
|
||||
height: 480px;
|
||||
gap: 0;
|
||||
padding: 0;
|
||||
|
||||
@include breakpoint(phone) {
|
||||
|
|
@ -14,17 +12,16 @@
|
|||
min-height: 100vh;
|
||||
}
|
||||
|
||||
#Header {
|
||||
border-bottom: 1px solid transparent;
|
||||
.DialogHeader.Search {
|
||||
align-items: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
padding-bottom: $unit * 2;
|
||||
|
||||
&.scrolled {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
padding: 0;
|
||||
padding-bottom: $unit-2x;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
#Bar {
|
||||
align-items: center;
|
||||
|
|
@ -63,7 +60,6 @@
|
|||
|
||||
#Results {
|
||||
margin: 0;
|
||||
max-height: 356px;
|
||||
padding: 0 ($unit * 1.5);
|
||||
overflow-y: scroll;
|
||||
|
||||
|
|
|
|||
|
|
@ -42,8 +42,10 @@ const SearchModal = (props: Props) => {
|
|||
// Set up translation
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
let searchInput = React.createRef<HTMLInputElement>()
|
||||
let scrollContainer = React.createRef<HTMLDivElement>()
|
||||
// Refs
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
const searchInput = React.createRef<HTMLInputElement>()
|
||||
const scrollContainer = React.createRef<HTMLDivElement>()
|
||||
|
||||
const [firstLoad, setFirstLoad] = useState(true)
|
||||
const [filters, setFilters] = useState<{ [key: string]: any }>()
|
||||
|
|
@ -356,10 +358,11 @@ const SearchModal = (props: Props) => {
|
|||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Search"
|
||||
headerref={headerRef}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
<div id="Header">
|
||||
<div className="Search DialogHeader" ref={headerRef}>
|
||||
<div id="Bar">
|
||||
<Input
|
||||
autoComplete="off"
|
||||
|
|
|
|||
|
|
@ -2,12 +2,11 @@
|
|||
gap: $unit;
|
||||
min-width: $unit * 52;
|
||||
|
||||
form {
|
||||
.Fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc($unit / 2);
|
||||
margin-bottom: $unit-2x;
|
||||
padding: 0 $unit-3x;
|
||||
padding: 0 $unit-4x;
|
||||
|
||||
.terms {
|
||||
color: $grey-50;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import setUserToken from '~utils/setUserToken'
|
|||
import { accountState } from '~utils/accountState'
|
||||
|
||||
import Button from '~components/Button'
|
||||
import Input from '~components/LabelledInput'
|
||||
import Input from '~components/Input'
|
||||
import { Dialog, DialogTrigger, DialogClose } from '~components/Dialog'
|
||||
import DialogContent from '~components/DialogContent'
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
|
|
@ -49,6 +49,8 @@ const SignupModal = (props: Props) => {
|
|||
const emailInput = React.createRef<HTMLInputElement>()
|
||||
const passwordInput = React.createRef<HTMLInputElement>()
|
||||
const passwordConfirmationInput = React.createRef<HTMLInputElement>()
|
||||
const footerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
const form = [
|
||||
usernameInput,
|
||||
emailInput,
|
||||
|
|
@ -279,6 +281,7 @@ const SignupModal = (props: Props) => {
|
|||
</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Signup"
|
||||
footerref={footerRef}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
|
|
@ -292,48 +295,54 @@ const SignupModal = (props: Props) => {
|
|||
</div>
|
||||
|
||||
<form className="form" onSubmit={register}>
|
||||
<Input
|
||||
className="Bound"
|
||||
name="username"
|
||||
placeholder={t('modals.signup.placeholders.username')}
|
||||
onChange={handleNameChange}
|
||||
error={errors.username}
|
||||
ref={usernameInput}
|
||||
/>
|
||||
<div className="Fields">
|
||||
<Input
|
||||
className="Bound"
|
||||
name="username"
|
||||
placeholder={t('modals.signup.placeholders.username')}
|
||||
onChange={handleNameChange}
|
||||
error={errors.username}
|
||||
ref={usernameInput}
|
||||
/>
|
||||
|
||||
<Input
|
||||
className="Bound"
|
||||
name="email"
|
||||
placeholder={t('modals.signup.placeholders.email')}
|
||||
onChange={handleNameChange}
|
||||
error={errors.email}
|
||||
ref={emailInput}
|
||||
/>
|
||||
<Input
|
||||
className="Bound"
|
||||
name="email"
|
||||
placeholder={t('modals.signup.placeholders.email')}
|
||||
onChange={handleNameChange}
|
||||
error={errors.email}
|
||||
ref={emailInput}
|
||||
/>
|
||||
|
||||
<Input
|
||||
className="Bound"
|
||||
name="password"
|
||||
placeholder={t('modals.signup.placeholders.password')}
|
||||
type="password"
|
||||
onChange={handlePasswordChange}
|
||||
error={errors.password}
|
||||
ref={passwordInput}
|
||||
/>
|
||||
<Input
|
||||
className="Bound"
|
||||
name="password"
|
||||
placeholder={t('modals.signup.placeholders.password')}
|
||||
type="password"
|
||||
onChange={handlePasswordChange}
|
||||
error={errors.password}
|
||||
ref={passwordInput}
|
||||
/>
|
||||
|
||||
<Input
|
||||
className="Bound"
|
||||
name="confirm_password"
|
||||
placeholder={t('modals.signup.placeholders.password_confirm')}
|
||||
type="password"
|
||||
onChange={handlePasswordChange}
|
||||
error={errors.passwordConfirmation}
|
||||
ref={passwordConfirmationInput}
|
||||
/>
|
||||
<Input
|
||||
className="Bound"
|
||||
name="confirm_password"
|
||||
placeholder={t('modals.signup.placeholders.password_confirm')}
|
||||
type="password"
|
||||
onChange={handlePasswordChange}
|
||||
error={errors.passwordConfirmation}
|
||||
ref={passwordConfirmationInput}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
disabled={!formValid}
|
||||
text={t('modals.signup.buttons.confirm')}
|
||||
/>
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<div className="Buttons Span">
|
||||
<Button
|
||||
disabled={!formValid}
|
||||
text={t('modals.signup.buttons.confirm')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="terms">
|
||||
{/* <Trans i18nKey="modals.signup.agreement">
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ const WeaponConflictModal = (props: Props) => {
|
|||
// States
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
// Refs
|
||||
const footerRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(props.open)
|
||||
}, [setOpen, props.open])
|
||||
|
|
@ -67,40 +70,50 @@ const WeaponConflictModal = (props: Props) => {
|
|||
<Dialog open={open} onOpenChange={openChange}>
|
||||
<DialogContent
|
||||
className="Conflict"
|
||||
footerref={footerRef}
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onEscapeKeyDown={close}
|
||||
>
|
||||
<p>{infoString()}</p>
|
||||
<div className="WeaponDiagram Diagram">
|
||||
<ul>
|
||||
{props.conflictingWeapons?.map((weapon, i) => (
|
||||
<li className="weapon" key={`conflict-${i}`}>
|
||||
<div className="Content">
|
||||
<p>{infoString()}</p>
|
||||
<div className="WeaponDiagram Diagram">
|
||||
<ul>
|
||||
{props.conflictingWeapons?.map((weapon, i) => (
|
||||
<li className="weapon" key={`conflict-${i}`}>
|
||||
<img
|
||||
alt={weapon.object.name[locale]}
|
||||
src={imageUrl(weapon.object)}
|
||||
/>
|
||||
<span>{weapon.object.name[locale]}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<span className="arrow">→</span>
|
||||
<div className="wrapper">
|
||||
<div className="weapon">
|
||||
<img
|
||||
alt={weapon.object.name[locale]}
|
||||
src={imageUrl(weapon.object)}
|
||||
alt={props.incomingWeapon?.name[locale]}
|
||||
src={imageUrl(props.incomingWeapon)}
|
||||
/>
|
||||
<span>{weapon.object.name[locale]}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<span className="arrow">→</span>
|
||||
<div className="wrapper">
|
||||
<div className="weapon">
|
||||
<img
|
||||
alt={props.incomingWeapon?.name[locale]}
|
||||
src={imageUrl(props.incomingWeapon)}
|
||||
/>
|
||||
{props.incomingWeapon?.name[locale]}
|
||||
{props.incomingWeapon?.name[locale]}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<Button onClick={close} text={t('buttons.cancel')} />
|
||||
<Button
|
||||
onClick={props.resolveConflict}
|
||||
text={t('modals.conflict.buttons.confirm')}
|
||||
/>
|
||||
</footer>
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<div className="Buttons Span">
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={close}
|
||||
text={t('buttons.cancel')}
|
||||
/>
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={props.resolveConflict}
|
||||
text={t('modals.conflict.buttons.confirm')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
<Overlay open={open} visible={true} />
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -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>()
|
||||
|
||||
// Hooks
|
||||
useEffect(() => {
|
||||
setOpen(modalOpen)
|
||||
|
|
@ -352,10 +356,12 @@ const WeaponModal = ({
|
|||
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Weapon"
|
||||
headerref={headerRef}
|
||||
footerref={footerRef}
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
>
|
||||
<div className="DialogHeader Short">
|
||||
<div className="DialogHeader Short" ref={headerRef}>
|
||||
<img
|
||||
alt={gridWeapon.object.name[locale]}
|
||||
className="DialogImage"
|
||||
|
|
@ -382,7 +388,7 @@ const WeaponModal = ({
|
|||
{gridWeapon.object.ax ? axSelect() : ''}
|
||||
{gridWeapon.awakening ? awakeningSelect() : ''}
|
||||
</div>
|
||||
<div className="DialogFooter">
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={updateWeapon}
|
||||
|
|
|
|||
|
|
@ -291,12 +291,12 @@ i.tag {
|
|||
@keyframes openModalDesktop {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translate(-50%, -48%) scale(0.96);
|
||||
transform: scale(0.96);
|
||||
}
|
||||
|
||||
100% {
|
||||
// opacity: 1;
|
||||
transform: translate(-50%, -50%) scale(1);
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -311,3 +311,13 @@ i.tag {
|
|||
transform: translate(0, 30%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeInFilter {
|
||||
from {
|
||||
backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(0);
|
||||
}
|
||||
|
||||
to {
|
||||
backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -315,5 +315,6 @@ $hover-stroke: 1px solid rgba(0, 0, 0, 0.1);
|
|||
$hover-shadow: rgba(0, 0, 0, 0.08) 0px 0px 14px;
|
||||
|
||||
// Durations
|
||||
$duration-modal-open: 0.48s;
|
||||
$duration-color-fade: 0.24s;
|
||||
$duration-zoom: 0.18s;
|
||||
|
|
|
|||
Loading…
Reference in a new issue