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