Skip to content

Commit 49e8fde

Browse files
committed
refactor: reduce code dup
1 parent 124b2b5 commit 49e8fde

4 files changed

Lines changed: 132 additions & 132 deletions

File tree

src/components/popups/BackgroundCard.jsx

Lines changed: 49 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,31 +17,6 @@ const DARK_PALETTE_OVERRIDES = (() => {
1717
}, /** @type {Record<string, unknown>} */ ({ mode: 'dark' }))
1818
})()
1919

20-
/**
21-
* Generates symmetrical margin/padding to let the background artwork bleed.
22-
*
23-
* @param {{
24-
* horizontal?: number
25-
* vertical?: number
26-
* }} [options]
27-
* @returns {React.CSSProperties}
28-
*/
29-
export function createFullBleedSurfaceStyle({
30-
horizontal = 0,
31-
vertical = 0,
32-
} = {}) {
33-
const horizontalPixels = Number(horizontal) || 0
34-
const verticalPixels = Number(vertical) || 0
35-
const widthAdjustment = horizontalPixels * 2
36-
const width = widthAdjustment ? `calc(100% + ${widthAdjustment}px)` : '100%'
37-
return {
38-
margin: `${-verticalPixels}px ${-horizontalPixels}px`,
39-
padding: `${verticalPixels}px ${horizontalPixels}px`,
40-
width,
41-
boxSizing: 'border-box',
42-
}
43-
}
44-
4520
/**
4621
* Applies themed background visuals (and optional tooltip) around popup content.
4722
*
@@ -51,6 +26,8 @@ export function createFullBleedSurfaceStyle({
5126
* tooltipProps?: import('@mui/material/Tooltip').TooltipProps
5227
* wrapperProps?: React.HTMLAttributes<HTMLDivElement>
5328
* wrapWhenNoTooltip?: boolean
29+
* fullWidth?: boolean
30+
* fullBleed?: boolean | number | { horizontal?: number, vertical?: number }
5431
* surfaceStyle?: React.CSSProperties
5532
* contentProps?: import('@mui/material/Box').BoxProps
5633
* children: React.ReactNode
@@ -62,6 +39,8 @@ export function BackgroundCard({
6239
tooltipProps,
6340
wrapperProps,
6441
wrapWhenNoTooltip = false,
42+
fullWidth = false,
43+
fullBleed,
6544
surfaceStyle,
6645
contentProps,
6746
children,
@@ -79,18 +58,42 @@ export function BackgroundCard({
7958
const primaryTextShadow = visuals?.primaryTextShadow
8059
const borderColor = visuals?.borderColor
8160

61+
const normalizedFullBleed = React.useMemo(() => {
62+
if (!fullBleed) {
63+
return undefined
64+
}
65+
if (fullBleed === true) {
66+
return {}
67+
}
68+
if (typeof fullBleed === 'number') {
69+
return { horizontal: fullBleed, vertical: 0 }
70+
}
71+
return fullBleed
72+
}, [fullBleed])
73+
8274
const resolvedSurfaceStyle = React.useMemo(() => {
8375
if (!hasBackground) {
8476
return surfaceStyle
8577
}
8678
const base = {
8779
...(visuals?.styles?.surface || {}),
8880
}
81+
if (normalizedFullBleed) {
82+
const horizontalPixels = Number(normalizedFullBleed.horizontal ?? 0) || 0
83+
const verticalPixels = Number(normalizedFullBleed.vertical ?? 0) || 0
84+
const widthAdjustment = horizontalPixels * 2
85+
base.margin = `${-verticalPixels}px ${-horizontalPixels}px`
86+
base.padding = `${verticalPixels}px ${horizontalPixels}px`
87+
base.width = widthAdjustment
88+
? `calc(100% + ${widthAdjustment}px)`
89+
: '100%'
90+
base.boxSizing = 'border-box'
91+
}
8992
if (surfaceStyle) {
9093
Object.assign(base, surfaceStyle)
9194
}
9295
return base
93-
}, [hasBackground, surfaceStyle, visuals])
96+
}, [hasBackground, normalizedFullBleed, surfaceStyle, visuals])
9497

9598
const { sx: contentSx, ...restContentProps } = contentProps || {}
9699
const combinedSx = React.useMemo(() => {
@@ -126,6 +129,21 @@ export function BackgroundCard({
126129
visuals?.primaryColor,
127130
])
128131

132+
const resolvedWrapperProps = React.useMemo(() => {
133+
if (!wrapperProps && !fullWidth) {
134+
return undefined
135+
}
136+
const base = wrapperProps ? { ...wrapperProps } : {}
137+
const existingStyle = wrapperProps?.style
138+
if (existingStyle || fullWidth) {
139+
base.style = {
140+
...(existingStyle || {}),
141+
...(fullWidth ? { width: '100%' } : {}),
142+
}
143+
}
144+
return base
145+
}, [fullWidth, wrapperProps])
146+
129147
const themed = React.useMemo(() => {
130148
if (!hasBackground) {
131149
return parentTheme
@@ -158,13 +176,15 @@ export function BackgroundCard({
158176
placement="top"
159177
{...tooltipProps}
160178
>
161-
<div {...(wrapperProps || { style: { width: '100%' } })}>{content}</div>
179+
<div {...(resolvedWrapperProps || { style: { width: '100%' } })}>
180+
{content}
181+
</div>
162182
</Tooltip>
163183
)
164184
}
165185

166-
if (wrapWhenNoTooltip && wrapperProps) {
167-
return <div {...wrapperProps}>{content}</div>
186+
if (wrapWhenNoTooltip && resolvedWrapperProps) {
187+
return <div {...resolvedWrapperProps}>{content}</div>
168188
}
169189

170190
return content

src/features/gym/GymPopup.jsx

Lines changed: 74 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,7 @@ import { GenderIcon } from '@components/popups/GenderIcon'
3131
import { Navigation } from '@components/popups/Navigation'
3232
import { Coords } from '@components/popups/Coords'
3333
import { TimeStamp } from '@components/popups/TimeStamps'
34-
import {
35-
BackgroundCard,
36-
createFullBleedSurfaceStyle,
37-
} from '@components/popups/BackgroundCard'
34+
import { BackgroundCard } from '@components/popups/BackgroundCard'
3835
import { useAnalytics } from '@hooks/useAnalytics'
3936
import { getTimeUntil } from '@utils/getTimeUntil'
4037
import { formatInterval } from '@utils/formatInterval'
@@ -158,6 +155,59 @@ function DefenderRowLayout({
158155
)
159156
}
160157

158+
const mergeSxParts = (...parts) => {
159+
const flattened = parts.reduce((acc, part) => {
160+
if (!part) {
161+
return acc
162+
}
163+
if (Array.isArray(part)) {
164+
return acc.concat(part)
165+
}
166+
acc.push(part)
167+
return acc
168+
}, [])
169+
if (!flattened.length) {
170+
return undefined
171+
}
172+
return flattened.length === 1 ? flattened[0] : flattened
173+
}
174+
175+
function DefenderPrimaryText({ children, title, visualStyles, sx }) {
176+
return (
177+
<Typography
178+
variant="subtitle1"
179+
component="div"
180+
sx={mergeSxParts(
181+
{
182+
fontSize: 16,
183+
fontWeight: 700,
184+
whiteSpace: 'normal',
185+
wordBreak: 'break-word',
186+
maxWidth: '100%',
187+
lineHeight: 1.43,
188+
},
189+
visualStyles,
190+
sx,
191+
)}
192+
title={title}
193+
>
194+
{children}
195+
</Typography>
196+
)
197+
}
198+
199+
function DefenderSecondaryText({ children, visualStyles, sx }) {
200+
return (
201+
<Typography
202+
variant="body2"
203+
component="div"
204+
sx={mergeSxParts({ fontSize: 13 }, visualStyles, sx)}
205+
>
206+
{children}
207+
</Typography>
208+
)
209+
}
210+
161211
/**
162212
*
163213
* @param {{
@@ -290,7 +340,6 @@ function DefendersModal({ gym, onClose }) {
290340
// Fallback to basic gym data when detailed defender info isn't available
291341
const useFallbackData =
292342
!defenders.length && gym.team_id > 0 && gym.guarding_pokemon_id
293-
const fallbackBackgroundMeta = fallbackVisuals.backgroundMeta
294343
let fallbackRow = null
295344
if (useFallbackData) {
296345
const fallbackImageSrc =
@@ -304,58 +353,33 @@ function DefendersModal({ gym, onClose }) {
304353

305354
const fallbackHasBackground = fallbackVisuals.hasBackground
306355
const fallbackBorderColor = fallbackVisuals.borderColor
307-
const fallbackStyles = fallbackVisuals.styles || {}
308-
const fallbackPrimaryText = fallbackStyles.primaryText || {}
309-
const fallbackSecondaryText = fallbackStyles.secondaryText || {}
356+
const fallbackPrimaryText = fallbackVisuals.styles?.primaryText
357+
const fallbackSecondaryText = fallbackVisuals.styles?.secondaryText
310358
const rowContent = (
311359
<DefenderRowLayout
312360
borderColor={fallbackBorderColor}
313361
imageSrc={fallbackImageSrc}
314362
imageAlt={t(`poke_${gym.guarding_pokemon_id}`)}
315363
showBestBuddy={gym.guarding_pokemon_display?.badge === 1}
316364
>
317-
<Typography
318-
variant="subtitle1"
319-
component="div"
320-
sx={{
321-
...fallbackPrimaryText,
322-
fontSize: 16,
323-
fontWeight: 700,
324-
whiteSpace: 'normal',
325-
wordBreak: 'break-word',
326-
maxWidth: '100%',
327-
lineHeight: 1.43,
328-
}}
365+
<DefenderPrimaryText
329366
title={t(`poke_${gym.guarding_pokemon_id}`)}
367+
visualStyles={fallbackPrimaryText}
330368
>
331369
{t(`poke_${gym.guarding_pokemon_id}`)}
332-
</Typography>
370+
</DefenderPrimaryText>
333371

334-
<Typography
335-
variant="body2"
336-
component="div"
337-
sx={{
338-
...fallbackSecondaryText,
339-
fontSize: 13,
340-
}}
341-
>
372+
<DefenderSecondaryText visualStyles={fallbackSecondaryText}>
342373
{gym.total_cp &&
343374
`${t('total_cp')}: ${numFormatter.format(gym.total_cp)}`}
344-
</Typography>
375+
</DefenderSecondaryText>
345376
</DefenderRowLayout>
346377
)
347378

348379
fallbackRow = (
349380
<BackgroundCard
350381
visuals={fallbackHasBackground ? fallbackVisuals : undefined}
351-
wrapperProps={
352-
fallbackBackgroundMeta?.tooltip
353-
? { style: { width: '100%' } }
354-
: undefined
355-
}
356-
surfaceStyle={
357-
fallbackHasBackground ? createFullBleedSurfaceStyle({}) : undefined
358-
}
382+
fullBleed={fallbackHasBackground}
359383
>
360384
{rowContent}
361385
</BackgroundCard>
@@ -439,8 +463,8 @@ function DefendersModal({ gym, onClose }) {
439463
hasBackground,
440464
styles: defenderStyles = {},
441465
} = visuals
442-
const primaryTextStyles = defenderStyles.primaryText || {}
443-
const secondaryTextStyles = defenderStyles.secondaryText || {}
466+
const primaryTextStyles = defenderStyles.primaryText
467+
const secondaryTextStyles = defenderStyles.secondaryText
444468
const iconStyles = defenderStyles.icon || {}
445469
const rowKey = `${def.pokemon_id}-${def.deployed_ms}`
446470
const rowContent = (
@@ -537,32 +561,19 @@ function DefendersModal({ gym, onClose }) {
537561
}
538562
>
539563
{/* First line: Pokemon name CP{currentCP}/{fullCP} */}
540-
<Typography
541-
variant="subtitle1"
542-
component="div"
543-
sx={{
544-
...primaryTextStyles,
545-
fontSize: 16,
546-
fontWeight: 700,
547-
whiteSpace: 'normal',
548-
wordBreak: 'break-word',
549-
maxWidth: '100%',
550-
lineHeight: 1.43,
551-
}}
564+
<DefenderPrimaryText
552565
title={`${t(`poke_${def.pokemon_id}`)} CP${currentCP}/${fullCP}`}
566+
visualStyles={primaryTextStyles}
553567
>
554568
{t(`poke_${def.pokemon_id}`)}
555-
</Typography>
569+
</DefenderPrimaryText>
556570

557-
<Typography
558-
variant="body2"
559-
component="div"
571+
<DefenderSecondaryText
572+
visualStyles={secondaryTextStyles}
560573
sx={{
561-
...secondaryTextStyles,
562574
display: 'flex',
563575
alignItems: 'center',
564576
gap: '8px',
565-
fontSize: 13,
566577
}}
567578
>
568579
<Box
@@ -618,36 +629,21 @@ function DefendersModal({ gym, onClose }) {
618629
{def.times_fed || 0}
619630
</Typography>
620631
</Box>
621-
</Typography>
632+
</DefenderSecondaryText>
622633

623-
<Typography
624-
variant="body2"
625-
component="div"
626-
sx={{
627-
...secondaryTextStyles,
628-
fontSize: 13,
629-
}}
630-
>
634+
<DefenderSecondaryText visualStyles={secondaryTextStyles}>
631635
CP{currentCP}/{fullCP}{' '}
632636
{formatDeployedTime(def.deployed_ms + now - updatedMs)}
633-
</Typography>
637+
</DefenderSecondaryText>
634638
</DefenderRowLayout>
635639
)
636640

637641
return (
638642
<React.Fragment key={rowKey}>
639643
<BackgroundCard
640644
visuals={hasBackground ? visuals : undefined}
641-
wrapperProps={
642-
backgroundMeta?.tooltip
643-
? { style: { width: '100%' } }
644-
: undefined
645-
}
646-
surfaceStyle={
647-
hasBackground
648-
? createFullBleedSurfaceStyle({})
649-
: undefined
650-
}
645+
fullBleed={hasBackground}
646+
fullWidth={Boolean(backgroundMeta?.tooltip)}
651647
>
652648
{rowContent}
653649
</BackgroundCard>

0 commit comments

Comments
 (0)