Update party footer

* Add segmented control to swap between remixes and description
* Fix styles
This commit is contained in:
Justin Edmund 2023-06-30 12:30:35 -07:00
parent 05af1ac6aa
commit 5caca71931
4 changed files with 88 additions and 164 deletions

View file

@ -1,4 +1,4 @@
.FooterWrapper { .wrapper {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $unit-2x; gap: $unit-2x;
@ -6,7 +6,7 @@
max-width: $grid-width; max-width: $grid-width;
@include breakpoint(phone) { @include breakpoint(phone) {
.Button:not(.IconButton) { .Button:not(.icon) {
justify-content: center; justify-content: center;
width: 100%; width: 100%;
@ -16,7 +16,8 @@
} }
} }
.PartyFooter { .description,
.remixes {
box-sizing: border-box; box-sizing: border-box;
line-height: 1.4; line-height: 1.4;
white-space: pre-wrap; white-space: pre-wrap;
@ -30,150 +31,20 @@
@include breakpoint(phone) { @include breakpoint(phone) {
padding: 0 $unit; padding: 0 $unit;
} }
&.Editable {
gap: $unit;
&.Visible { .noRemixes {
display: grid; align-items: center;
display: flex;
flex-direction: column;
gap: $unit-2x;
margin: 0 auto;
padding: $unit-4x 0;
h3 {
font-size: $font-small;
font-weight: $medium;
text-align: center;
} }
fieldset {
display: block;
width: 100%;
textarea {
min-height: $unit * 22;
width: 100%;
}
}
.SelectTrigger {
padding: $unit-2x;
width: 100%;
}
.DetailToggleGroup {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: $unit;
@include breakpoint(phone) {
grid-template-columns: 1fr;
}
.ToggleSection,
.InputSection {
align-items: center;
display: flex;
background: var(--card-bg);
border-radius: $input-corner;
& > label {
align-items: center;
display: flex;
font-size: $font-regular;
gap: $unit;
grid-template-columns: 2fr 1fr;
justify-content: space-between;
width: 100%;
& > span {
flex-grow: 1;
}
}
}
.ToggleSection {
padding: ($unit * 1.5) $unit-2x;
}
.InputSection {
padding: $unit-half $unit-2x;
padding-right: $unit-half;
.Input {
border-radius: 7px;
}
div.Input {
align-items: center;
border: 2px solid transparent;
box-sizing: border-box;
display: flex;
padding: $unit;
&:has(> input:focus) {
border: 2px solid $blue;
outline: none;
}
& > input {
background: transparent;
border: none;
padding: $unit 0;
text-align: right;
width: 2rem;
&:focus {
outline: none;
border: none;
}
}
}
label {
display: flex;
justify-content: space-between;
span {
flex-grow: 1;
}
.Input {
border-radius: 7px;
max-width: 10rem;
}
div {
display: flex;
flex-direction: row;
gap: $unit-half;
justify-content: right;
}
}
}
}
.bottom {
display: flex;
flex-direction: row;
gap: $unit;
@include breakpoint(phone) {
flex-direction: column;
width: 100%;
}
.left {
flex-grow: 1;
}
.right {
display: flex;
flex-direction: row;
gap: $unit;
@include breakpoint(phone) {
.Button {
flex-grow: 1;
}
}
}
}
}
&.Visible {
display: block;
} }
a:hover { a:hover {

View file

@ -2,13 +2,16 @@ import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio' import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import classNames from 'classnames'
import clonedeep from 'lodash.clonedeep' import clonedeep from 'lodash.clonedeep'
import Linkify from 'react-linkify' import Linkify from 'react-linkify'
import LiteYouTubeEmbed from 'react-lite-youtube-embed' import LiteYouTubeEmbed from 'react-lite-youtube-embed'
import classNames from 'classnames'
import reactStringReplace from 'react-string-replace' import reactStringReplace from 'react-string-replace'
import Button from '~components/common/Button'
import SegmentedControl from '~components/common/SegmentedControl'
import Segment from '~components/common/Segment'
import GridRepCollection from '~components/GridRepCollection' import GridRepCollection from '~components/GridRepCollection'
import GridRep from '~components/GridRep' import GridRep from '~components/GridRep'
@ -18,6 +21,7 @@ import { youtube } from '~utils/youtube'
import type { DetailsObject } from 'types' import type { DetailsObject } from 'types'
import RemixIcon from '~public/icons/Remix.svg'
import styles from './index.module.scss' import styles from './index.module.scss'
// Props // Props
@ -38,6 +42,7 @@ const PartyFooter = (props: Props) => {
/(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g /(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [currentSegment, setCurrentSegment] = useState(0)
const [remixes, setRemixes] = useState<Party[]>([]) const [remixes, setRemixes] = useState<Party[]>([])
const [embeddedDescription, setEmbeddedDescription] = const [embeddedDescription, setEmbeddedDescription] =
@ -147,6 +152,47 @@ const PartyFooter = (props: Props) => {
}) })
} }
const segmentedControl = (
<SegmentedControl className="background">
<Segment
name="description"
groupName="footer"
selected={currentSegment === 0}
onClick={() => setCurrentSegment(0)}
>
{t('footer.description.label')}
</Segment>
<Segment
name="remixes"
groupName="footer"
selected={currentSegment === 1}
onClick={() => setCurrentSegment(1)}
>
{t('footer.remixes.label')}
</Segment>
</SegmentedControl>
)
const descriptionSection = (
<section className={styles.description}>
<Linkify>{embeddedDescription}</Linkify>
</section>
)
const remixesSection = (
<section className={styles.remixes}>
{party?.remixes?.length > 0 && (
<GridRepCollection>{renderRemixes()}</GridRepCollection>
)}
{party?.remixes?.length === 0 && (
<div className={styles.noRemixes}>
<h3>{t('footer.remixes.empty')}</h3>
<Button leftAccessoryIcon={<RemixIcon />} text={t('buttons.remix')} />
</div>
)}
</section>
)
function renderRemixes() { function renderRemixes() {
return remixes.map((party, i) => { return remixes.map((party, i) => {
return ( return (
@ -162,7 +208,6 @@ const PartyFooter = (props: Props) => {
fullAuto={party.full_auto} fullAuto={party.full_auto}
autoGuard={party.auto_guard} autoGuard={party.auto_guard}
key={`party-${i}`} key={`party-${i}`}
displayUser={true}
onClick={goTo} onClick={goTo}
onSave={toggleFavorite} onSave={toggleFavorite}
/> />
@ -170,23 +215,13 @@ const PartyFooter = (props: Props) => {
}) })
} }
const remixSection = () => {
return (
<section className="Remixes">
<h3>{t('remixes')}</h3>
{<GridRepCollection>{renderRemixes()}</GridRepCollection>}
</section>
)
}
return ( return (
<> <>
<section className="FooterWrapper"> <div className={styles.wrapper}>
<section className="PartyFooter"> {segmentedControl}
<Linkify>{embeddedDescription}</Linkify> {currentSegment === 0 && descriptionSection}
</section> {currentSegment === 1 && remixesSection}
</section> </div>
{remixes && remixes.length > 0 ? remixSection() : ''}
</> </>
) )
} }

View file

@ -106,6 +106,16 @@
"rarity": "Rarity" "rarity": "Rarity"
} }
}, },
"footer": {
"description": {
"label": "Description",
"empty": "This team doesn't have a description"
},
"remixes": {
"label": "Remixes",
"empty": "No one has remixed this team yet"
}
},
"header": { "header": {
"anonymous": "Anonymous", "anonymous": "Anonymous",
"untitled_team": "Untitled team by {{username}}", "untitled_team": "Untitled team by {{username}}",
@ -540,6 +550,5 @@
"no_user": "Anonymous", "no_user": "Anonymous",
"no_job": "No class", "no_job": "No class",
"no_value": "No value", "no_value": "No value",
"remixes": "Remixes",
"level": "Level" "level": "Level"
} }

View file

@ -106,6 +106,16 @@
"rarity": "レアリティ" "rarity": "レアリティ"
} }
}, },
"footer": {
"description": {
"label": "説明",
"empty": "この編成には説明がありません"
},
"remixes": {
"label": "リミックス",
"empty": "この編成はリミックスされていません"
}
},
"header": { "header": {
"anonymous": "無名", "anonymous": "無名",
"untitled_team": "{{username}}さんからの無題編成", "untitled_team": "{{username}}さんからの無題編成",
@ -537,6 +547,5 @@
"no_user": "無名", "no_user": "無名",
"no_job": "ジョブなし", "no_job": "ジョブなし",
"no_value": "値なし", "no_value": "値なし",
"remixes": "リミックスされた編成",
"level": "レベル" "level": "レベル"
} }