Update party footer
* Add segmented control to swap between remixes and description * Fix styles
This commit is contained in:
parent
05af1ac6aa
commit
5caca71931
4 changed files with 88 additions and 164 deletions
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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() : ''}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": "レベル"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue