Skip to content

Commit d85c940

Browse files
authored
Feat - misc data styling in user heros (#663)
<!-- Please read https://github.com/SableClient/Sable/blob/dev/CONTRIBUTING.md before submitting your pull request --> ### Description <!-- Please include a summary of the change. Please also include relevant motivation and context. List any dependencies that are required for this change. --> This PR proposes styling the misc data (the fields from extended profile that sable does not recognize) in a more robust form, with json formatting, and tapping through the options in order to have a clear visual differentiation between it and anything else. (current view for an example from someone using a different client and thus having different fields) <img width="350" height="701" alt="image" src="https://github.com/user-attachments/assets/996bbcef-f7cf-4bbb-8667-051181d72835" /> (proposed new view, with the badge key selected) <img width="344" height="661" alt="image" src="https://github.com/user-attachments/assets/09cb347d-d287-477b-9562-5854ab09905b" /> (proposed new view when selecting an item) <img width="345" height="564" alt="image" src="https://github.com/user-attachments/assets/e52d18af-ae60-4747-9fd6-eef086373fe1" /> <img width="345" height="653" alt="image" src="https://github.com/user-attachments/assets/9252b65f-d8ea-4926-8793-1b9ddff99b0a" /> (proposed view when there is only one key, and showing it is selected) <img width="346" height="573" alt="image" src="https://github.com/user-attachments/assets/4003f601-ddf4-44e9-b05d-4c07a4015033" /> Fixes # #### Type of change - [ ] Bug fix (non-breaking change which fixes an issue) - [x] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ### Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings ### AI disclosure: - [ ] Partially AI assisted (clarify which code was AI assisted and briefly explain what it does). - [ ] Fully AI generated (explain what all the generated code does in moderate detail). <!-- Write any explanation required here, but do not generate the explanation using AI!! You must prove you understand what the code in this PR does. --> My partner's avocado gasped the implementation as its last breath before rotting away
2 parents a029580 + e3b8036 commit d85c940

6 files changed

Lines changed: 143 additions & 39 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
default: minor
3+
---
4+
5+
Change Misc. data styling in users profile pages

src/app/components/UserRoomProfileRenderer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function UserRoomProfileContextMenu({ state }: { state: UserRoomProfileState })
2323
<PopOut
2424
anchor={cords}
2525
position={position ?? 'Top'}
26-
align="Start"
26+
align={cords.y > window.innerHeight / 2 ? 'End' : 'Start'}
2727
content={
2828
<FocusTrap
2929
focusTrapOptions={{

src/app/components/user-profile/UserChips.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ export function IgnoredUserAlert() {
433433
<CutoutCard style={{ padding: config.space.S200 }} variant="Critical">
434434
<SettingTile>
435435
<Box direction="Column" gap="200">
436-
<Box gap="200" justifyContent="SpaceBetween">
436+
<Box gap="200" justifyContent="Center">
437437
<Text size="L400">Blocked User</Text>
438438
</Box>
439439
<Box direction="Column">

src/app/components/user-profile/UserModeration.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,9 @@ export function UserModeration({ userId, canKick, canBan, canInvite }: UserModer
256256
<Box direction="Column" gap="400">
257257
<Box direction="Column" gap="200">
258258
<Box grow="Yes" direction="Column" gap="100">
259-
<Text size="L400">Moderation</Text>
259+
<Text size="L400" align="Center">
260+
Moderation
261+
</Text>
260262
<Input
261263
ref={reasonInputRef}
262264
placeholder="Reason"

src/app/components/user-profile/UserRoomProfile.tsx

Lines changed: 127 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Box, Button, config, Icon, Icons, Scroll, Text } from 'folds';
1+
import { Box, Button, config, Icon, Icons, Menu, MenuItem, Scroll, Text, toRem } from 'folds';
22
import { SyntheticEvent, useCallback, useMemo, useState } from 'react';
33
import { useNavigate } from 'react-router-dom';
44
import { useAtomValue } from 'jotai';
@@ -35,6 +35,7 @@ import { getSettings, settingsAtom } from '$state/settings';
3535
import { filterPronounsByLanguage } from '$utils/pronouns';
3636
import { useSetting } from '$state/hooks/settings';
3737
import { useSettingsLinkBaseUrl } from '$features/settings/useSettingsLinkBaseUrl';
38+
import { TextViewerContent } from '$components/text-viewer';
3839
import { CreatorChip } from './CreatorChip';
3940
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
4041
import { PowerChip } from './PowerChip';
@@ -67,11 +68,8 @@ function UserExtendedSection({
6768
htmlReactParserOptions,
6869
linkifyOpts,
6970
}: Readonly<UserExtendedSectionProps>) {
70-
const clamp = (str: any, len: number) => {
71-
const stringified = String(str ?? '');
72-
return stringified.length > len ? `${stringified.slice(0, len)}...` : stringified;
73-
};
74-
const [showMore, setShowMore] = useState(false);
71+
const [showMisc, setShowMisc] = useState(false);
72+
const [miscDataIndex, setMiscDataIndex] = useState(-1);
7573

7674
const [renderAnimals] = useSetting(settingsAtom, 'renderAnimals');
7775
const isCat = profile.isCat === true;
@@ -148,6 +146,73 @@ function UserExtendedSection({
148146
([key]) => !KNOWN_KEYS.includes(key)
149147
);
150148

149+
function handleMiscSelector(index: number) {
150+
setMiscDataIndex(index);
151+
setShowMisc(false);
152+
}
153+
154+
const miscSelector = useMemo(() => {
155+
if (unknownFields.length === 1 && showMisc) {
156+
setShowMisc(false);
157+
setMiscDataIndex(miscDataIndex === -1 ? 0 : -1);
158+
return null;
159+
}
160+
return (
161+
<Menu style={{ position: 'absolute', zIndex: '100', transform: `translateY(${toRem(32)})` }}>
162+
<MenuItem
163+
size="300"
164+
radii="300"
165+
fill="None"
166+
variant="Primary"
167+
style={{ justifyContent: 'Center', textAlign: 'center' }}
168+
onClick={() => handleMiscSelector(-1)}
169+
>
170+
<Icon src={Icons.ChevronTop} size="50" />
171+
<Text>Show less</Text>
172+
</MenuItem>
173+
{unknownFields.map(([key], index) => (
174+
<MenuItem
175+
size="300"
176+
radii="300"
177+
fill="None"
178+
variant="Secondary"
179+
style={{ justifyContent: 'Center' }}
180+
onClick={() => handleMiscSelector(index)}
181+
>
182+
<Text>{key}</Text>
183+
</MenuItem>
184+
))}
185+
</Menu>
186+
);
187+
}, [miscDataIndex, showMisc, unknownFields]);
188+
const miscHeader = useMemo(
189+
() => (
190+
<Box justifyContent="Center" grow="Yes">
191+
<Button
192+
variant="Secondary"
193+
size="300"
194+
fill="None"
195+
onClick={() => setShowMisc(!showMisc)}
196+
after={miscDataIndex === -1 && <Icon size="50" src={Icons.ChevronBottom} />}
197+
style={{
198+
padding: '1rem',
199+
justifyContent: 'flex-start',
200+
width: 'fit-content',
201+
textAlign: 'center',
202+
}}
203+
>
204+
<Text size="T200" priority="400">
205+
{miscDataIndex === -1
206+
? `Show Misc. Data (${unknownFields.length} value${unknownFields.length > 1 ? 's' : ''})`
207+
: `${unknownFields[miscDataIndex][0]} ${unknownFields.length > 1 ? `(${miscDataIndex + 1}/${unknownFields.length})` : ''}`}
208+
</Text>
209+
</Button>
210+
{showMisc && miscSelector}
211+
</Box>
212+
),
213+
[miscSelector, miscDataIndex, showMisc, unknownFields]
214+
);
215+
151216
return (
152217
<Box direction="Column" gap="200" style={{ marginBottom: config.space.S100 }}>
153218
{(pronouns || localTime) && (
@@ -209,39 +274,66 @@ function UserExtendedSection({
209274

210275
{unknownFields.length > 0 && (
211276
<Box direction="Column" gap="100">
212-
<Button
213-
variant="Secondary"
214-
size="300"
215-
fill="None"
216-
onClick={() => setShowMore(!showMore)}
217-
after={<Icon size="50" src={showMore ? Icons.ChevronTop : Icons.ChevronBottom} />}
218-
style={{ padding: '1rem', justifyContent: 'flex-start', width: 'fit-content' }}
219-
>
220-
<Text size="T200" priority="400">
221-
{showMore ? 'Show less' : `+ ${unknownFields.length} more info`}
222-
</Text>
223-
</Button>
224-
225-
{showMore && (
226-
<Box
227-
direction="Column"
277+
{miscDataIndex === -1 && miscHeader}
278+
{miscDataIndex > -1 && (
279+
<div
228280
style={{
229-
padding: config.space.S200,
230-
backgroundColor: 'var(--sable-surface-container)',
281+
border: '2px solid',
282+
backgroundColor: 'var(--sable-bg-container)',
283+
borderColor: 'var(--sable-surface-container-line)',
231284
borderRadius: config.radii.R400,
232285
}}
233286
>
234-
{unknownFields.map(([key, value]) => (
235-
<Box key={key} direction="Column" style={{ marginBottom: config.space.S100 }}>
236-
<Text size="T200" priority="400" style={{ letterSpacing: '0.05em' }}>
237-
{key}
238-
</Text>
239-
<Text size="T200" priority="300">
240-
{clamp(renderValue(value), 128)}
241-
</Text>
287+
<Box
288+
direction="Row"
289+
justifyContent="Center"
290+
alignContent="Center"
291+
style={{
292+
borderRadius: config.radii.R400,
293+
}}
294+
>
295+
{unknownFields.length > 1 && (
296+
<Button
297+
variant="Secondary"
298+
size="300"
299+
fill="None"
300+
onClick={() =>
301+
setMiscDataIndex(
302+
miscDataIndex === 0 ? unknownFields.length - 1 : miscDataIndex - 1
303+
)
304+
}
305+
>
306+
<Icon src={Icons.ArrowLeft} size="50" />
307+
</Button>
308+
)}
309+
{miscHeader}
310+
{unknownFields.length > 1 && (
311+
<Button
312+
variant="Secondary"
313+
size="300"
314+
fill="None"
315+
onClick={() => setMiscDataIndex((miscDataIndex + 1) % unknownFields.length)}
316+
>
317+
<Icon src={Icons.ArrowRight} size="50" />
318+
</Button>
319+
)}
320+
</Box>
321+
<Scroll size="300" direction="Both">
322+
<Box
323+
direction="Column"
324+
style={{
325+
padding: config.space.S200,
326+
borderRadius: config.radii.R400,
327+
maxHeight: toRem(100),
328+
}}
329+
>
330+
<TextViewerContent
331+
text={renderValue(unknownFields[miscDataIndex][1])}
332+
langName="json"
333+
/>
242334
</Box>
243-
))}
244-
</Box>
335+
</Scroll>
336+
</div>
245337
)}
246338
</Box>
247339
)}
@@ -382,7 +474,7 @@ export function UserRoomProfile({ userId, initialProfile }: Readonly<UserRoomPro
382474
htmlReactParserOptions={htmlReactParserOptions}
383475
linkifyOpts={linkifyOpts}
384476
/>
385-
<Box alignItems="Center" gap="100" wrap="Wrap">
477+
<Box alignItems="Center" gap="100" wrap="Wrap" justifyContent="Center">
386478
{server && <ServerChip server={server} />}
387479
<ShareChip userId={userId} />
388480
{creator ? <CreatorChip /> : <PowerChip userId={userId} />}

src/app/features/room/MembersDrawer.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,12 @@ export function MembersDrawer({ room, members }: MembersDrawerProps) {
282282
const btn = evt.currentTarget as HTMLButtonElement;
283283
const userId = btn.getAttribute('data-user-id');
284284
if (!userId) return;
285-
openUserRoomProfile(room.roomId, space?.roomId, userId, btn.getBoundingClientRect(), 'Left');
285+
286+
const cords = btn.getBoundingClientRect();
287+
// BODGE, dependent on menuItem height staying at toRem(40)
288+
cords.y = Math.min(cords.y, window.innerHeight - 42);
289+
290+
openUserRoomProfile(room.roomId, space?.roomId, userId, cords, 'Left');
286291
};
287292

288293
return (

0 commit comments

Comments
 (0)