Skip to content

Commit 0f8e491

Browse files
fix(header): normalize streak, cores, and reputation buttons (#6097)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d545f08 commit 0f8e491

4 files changed

Lines changed: 53 additions & 42 deletions

File tree

packages/shared/src/components/profile/ProfileButton.spec.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ const renderComponent = (user = defaultUser): RenderResult => {
4949
it('should show "Profile settings" tooltip on the profile picture', () => {
5050
renderComponent();
5151

52-
const elementsWithLabel = screen.getAllByLabelText('Profile settings');
53-
// The button itself has aria-label, and the Radix Tooltip trigger also sets aria-label
54-
expect(elementsWithLabel.length).toBeGreaterThanOrEqual(2);
52+
expect(
53+
screen.getByRole('button', { name: 'Profile settings' }),
54+
).toBeInTheDocument();
5555
});
5656

5757
it('should show "Reputation" tooltip on the reputation badge', () => {

packages/shared/src/components/profile/ProfileButton.tsx

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import classNames from 'classnames';
44
import dynamic from 'next/dynamic';
55
import { useAuthContext } from '../../contexts/AuthContext';
66
import { ProfilePictureWithIndicator } from './ProfilePictureWithIndicator';
7-
import { CoreIcon, SettingsIcon } from '../icons';
7+
import { CoreIcon, ReputationIcon, SettingsIcon } from '../icons';
88
import { Button, ButtonSize, ButtonVariant } from '../buttons/Button';
99
import { useInteractivePopup } from '../../hooks/utils/useInteractivePopup';
10-
import { ReputationUserBadge } from '../ReputationUserBadge';
11-
import { IconSize } from '../Icon';
1210
import { ReadingStreakButton } from '../streak/ReadingStreakButton';
1311
import { useReadingStreak } from '../../hooks/streaks';
1412
import { walletUrl } from '../../lib/constants';
@@ -50,7 +48,7 @@ export default function ProfileButton({
5048
Partial<Record<QuestRewardType.Reputation | QuestRewardType.Cores, string>>
5149
>({});
5250
const coresCounterRef = useRef<HTMLDivElement | null>(null);
53-
const reputationCounterRef = useRef<HTMLSpanElement | null>(null);
51+
const reputationCounterRef = useRef<HTMLDivElement | null>(null);
5452
const displayedBalance =
5553
typeof animatedCores === 'number'
5654
? animatedCores
@@ -212,7 +210,6 @@ export default function ProfileButton({
212210
streak={streak}
213211
isLoading={isLoading}
214212
compact
215-
className="pl-4"
216213
/>
217214
)}
218215
{hasCoresAccess && (
@@ -236,41 +233,45 @@ export default function ProfileButton({
236233
tag="a"
237234
variant={ButtonVariant.Tertiary}
238235
size={ButtonSize.Small}
236+
className="!px-1.5"
239237
>
240238
{largeNumberFormat(displayedBalance)}
241239
</Button>
242240
</Link>
243241
</div>
244242
</Tooltip>
245243
)}
246-
<button
247-
type="button"
248-
aria-label="Profile settings"
249-
className={classNames(
250-
'focus-outline cursor-pointer items-center gap-2 border-none p-0 font-bold text-text-primary no-underline typo-subhead',
251-
className ?? 'flex',
252-
)}
253-
onClick={wrapHandler(() => onUpdate(!isOpen))}
254-
>
255-
<span
244+
<Tooltip content="Reputation">
245+
<div
256246
ref={reputationCounterRef}
257-
className="inline-flex items-center"
258-
data-reward-target={QuestRewardType.Reputation}
247+
className="flex origin-center justify-center will-change-transform"
259248
>
260-
<ReputationUserBadge
261-
className="ml-1 !typo-subhead"
262-
user={{ reputation: displayedReputation ?? 0 }}
263-
iconProps={{
264-
size: IconSize.Small,
265-
}}
266-
/>
267-
</span>
268-
<Tooltip side="bottom" content="Profile settings">
269-
<div className="flex items-center">
270-
<ProfilePictureWithIndicator user={user} />
271-
</div>
272-
</Tooltip>
273-
</button>
249+
<Button
250+
type="button"
251+
data-reward-target={QuestRewardType.Reputation}
252+
icon={<ReputationIcon className="text-accent-onion-default" />}
253+
variant={ButtonVariant.Tertiary}
254+
size={ButtonSize.Small}
255+
className="!pl-0.5 !pr-1.5"
256+
onClick={wrapHandler(() => onUpdate(!isOpen))}
257+
>
258+
{largeNumberFormat(displayedReputation ?? 0)}
259+
</Button>
260+
</div>
261+
</Tooltip>
262+
<Tooltip side="bottom" content="Profile settings">
263+
<button
264+
type="button"
265+
aria-label="Profile settings"
266+
className={classNames(
267+
'focus-outline cursor-pointer items-center border-none bg-transparent p-0',
268+
className ?? 'flex',
269+
)}
270+
onClick={wrapHandler(() => onUpdate(!isOpen))}
271+
>
272+
<ProfilePictureWithIndicator user={user} />
273+
</button>
274+
</Tooltip>
274275
</div>
275276
)}
276277
{isOpen && <ProfileMenu onClose={() => onUpdate(false)} />}

packages/shared/src/components/streak/ReadingStreakButton.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import ConditionalWrapper from '../ConditionalWrapper';
1717
import type { TooltipPosition } from '../tooltips/BaseTooltipContainer';
1818
import { useAuthContext } from '../../contexts/AuthContext';
1919
import { isSameDayInTimezone } from '../../lib/timezones';
20-
import { IconWrapper } from '../Icon';
20+
import { IconSize, IconWrapper } from '../Icon';
2121
import { useStreakTimezoneOk } from '../../hooks/streaks/useStreakTimezoneOk';
2222

2323
interface ReadingStreakButtonProps {
@@ -122,22 +122,26 @@ export function ReadingStreakButton({
122122
type="button"
123123
iconPosition={iconPosition}
124124
icon={
125-
<IconWrapper wrapperClassName="relative flex items-center gap-2">
125+
<IconWrapper
126+
size={compact ? IconSize.XSmall : undefined}
127+
wrapperClassName={classnames(
128+
'relative flex items-center gap-2',
129+
compact && 'h-6 w-6 justify-center',
130+
)}
131+
>
126132
<ReadingStreakIcon secondary={hasReadToday} />
127133
{!isTimezoneOk && (
128134
<WarningIcon className="!mr-0 text-raw-cheese-40" secondary />
129135
)}
130136
</IconWrapper>
131137
}
132138
variant={
133-
isLaptop || isMobile ? ButtonVariant.Tertiary : ButtonVariant.Float
139+
compact || isLaptop || isMobile
140+
? ButtonVariant.Tertiary
141+
: ButtonVariant.Float
134142
}
135143
onClick={handleToggle}
136-
className={classnames(
137-
'gap-1',
138-
compact && 'text-accent-bacon-default',
139-
className,
140-
)}
144+
className={classnames('gap-1', compact && '!px-1.5', className)}
141145
size={!compact && !isMobile ? ButtonSize.Medium : ButtonSize.Small}
142146
>
143147
{streak?.current}

scripts/typecheck-strict-changed.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ const strictSkipList = new Set([
107107
// unrelated to the rightCopy wiring.
108108
'packages/shared/src/contexts/WritePostContext.tsx',
109109
'packages/webapp/pages/squads/create.tsx',
110+
// Header-stat-button alignment branch — touched only to drop the
111+
// bacon-colored number and switch compact to Tertiary. Pre-existing
112+
// strict errors (optional auth user, ConditionalWrapper wrapper type,
113+
// ReactElement vs null return, Button props union) live on unrelated
114+
// lines and should be addressed in a dedicated cleanup PR.
115+
'packages/shared/src/components/streak/ReadingStreakButton.tsx',
110116
]);
111117

112118
const changedFiles = getChangedTypescriptFiles().filter(

0 commit comments

Comments
 (0)