[Glitch] Profile redesign: Design fixes
Port 079f8615fe to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
@@ -311,6 +311,7 @@ interface DropdownProps<Item extends object | null = MenuItem> {
|
|||||||
status?: ImmutableMap<string, unknown>;
|
status?: ImmutableMap<string, unknown>;
|
||||||
needsStatusRefresh?: boolean;
|
needsStatusRefresh?: boolean;
|
||||||
forceDropdown?: boolean;
|
forceDropdown?: boolean;
|
||||||
|
className?: string;
|
||||||
renderItem?: RenderItemFn<Item>;
|
renderItem?: RenderItemFn<Item>;
|
||||||
renderHeader?: RenderHeaderFn<Item>;
|
renderHeader?: RenderHeaderFn<Item>;
|
||||||
onOpen?: // Must use a union type for the full function as a union with void is not allowed.
|
onOpen?: // Must use a union type for the full function as a union with void is not allowed.
|
||||||
@@ -335,6 +336,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
|||||||
status,
|
status,
|
||||||
needsStatusRefresh,
|
needsStatusRefresh,
|
||||||
forceDropdown = false,
|
forceDropdown = false,
|
||||||
|
className,
|
||||||
renderItem,
|
renderItem,
|
||||||
renderHeader,
|
renderHeader,
|
||||||
onOpen,
|
onOpen,
|
||||||
@@ -434,6 +436,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
|||||||
modalProps: {
|
modalProps: {
|
||||||
actions: items,
|
actions: items,
|
||||||
onClick: handleItemClick,
|
onClick: handleItemClick,
|
||||||
|
className,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -462,6 +465,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
|||||||
handleClose,
|
handleClose,
|
||||||
statusId,
|
statusId,
|
||||||
needsStatusRefresh,
|
needsStatusRefresh,
|
||||||
|
className,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -515,7 +519,7 @@ export const Dropdown = <Item extends object | null = MenuItem>({
|
|||||||
popperConfig={popperConfig}
|
popperConfig={popperConfig}
|
||||||
>
|
>
|
||||||
{({ props, arrowProps, placement }) => (
|
{({ props, arrowProps, placement }) => (
|
||||||
<div {...props} id={menuId}>
|
<div {...props} className={className} id={menuId}>
|
||||||
<div className={`dropdown-animation dropdown-menu ${placement}`}>
|
<div className={`dropdown-animation dropdown-menu ${placement}`}>
|
||||||
<div
|
<div
|
||||||
className={`dropdown-menu__arrow ${placement}`}
|
className={`dropdown-menu__arrow ${placement}`}
|
||||||
|
|||||||
@@ -214,7 +214,10 @@ export const AccountHeader: React.FC<{
|
|||||||
<>
|
<>
|
||||||
<AccountBio
|
<AccountBio
|
||||||
accountId={accountId}
|
accountId={accountId}
|
||||||
className='account__header__content'
|
className={classNames(
|
||||||
|
'account__header__content',
|
||||||
|
isRedesign && redesignClasses.bio,
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<AccountHeaderFields accountId={accountId} />
|
<AccountHeaderFields accountId={accountId} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ import { FormattedDateWrapper } from '@/flavours/glitch/components/formatted_dat
|
|||||||
import { Icon } from '@/flavours/glitch/components/icon';
|
import { Icon } from '@/flavours/glitch/components/icon';
|
||||||
import { useElementHandledLink } from '@/flavours/glitch/components/status/handled_link';
|
import { useElementHandledLink } from '@/flavours/glitch/components/status/handled_link';
|
||||||
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
import { useAccount } from '@/flavours/glitch/hooks/useAccount';
|
||||||
import type { Account } from '@/flavours/glitch/models/account';
|
import type {
|
||||||
import { isValidUrl } from '@/flavours/glitch/utils/checks';
|
Account,
|
||||||
|
AccountFieldShape,
|
||||||
|
} from '@/flavours/glitch/models/account';
|
||||||
import type { OnElementHandler } from '@/flavours/glitch/utils/html';
|
import type { OnElementHandler } from '@/flavours/glitch/utils/html';
|
||||||
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
import IconVerified from '@/images/icons/icon_verified.svg?react';
|
||||||
|
|
||||||
@@ -76,8 +78,8 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
|
|||||||
[account.emojis],
|
[account.emojis],
|
||||||
);
|
);
|
||||||
const textHasCustomEmoji = useCallback(
|
const textHasCustomEmoji = useCallback(
|
||||||
(text: string) => {
|
(text?: string | null) => {
|
||||||
if (!emojis) {
|
if (!emojis || !text) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (const emoji of Object.keys(emojis)) {
|
for (const emoji of Object.keys(emojis)) {
|
||||||
@@ -92,62 +94,96 @@ const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
|
|||||||
const htmlHandlers = useElementHandledLink({
|
const htmlHandlers = useElementHandledLink({
|
||||||
hashtagAccountId: account.id,
|
hashtagAccountId: account.id,
|
||||||
});
|
});
|
||||||
const intl = useIntl();
|
|
||||||
|
if (account.fields.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CustomEmojiProvider emojis={emojis}>
|
<CustomEmojiProvider emojis={emojis}>
|
||||||
<dl className={classes.fieldList}>
|
<dl className={classes.fieldList}>
|
||||||
{account.fields.map(
|
{account.fields.map((field, key) => (
|
||||||
(
|
<FieldRow
|
||||||
{ name, name_emojified, value_emojified, value_plain, verified_at },
|
key={key}
|
||||||
key,
|
{...field.toJSON()}
|
||||||
) => (
|
htmlHandlers={htmlHandlers}
|
||||||
<div
|
textHasCustomEmoji={textHasCustomEmoji}
|
||||||
key={key}
|
/>
|
||||||
className={classNames(
|
))}
|
||||||
classes.fieldRow,
|
|
||||||
verified_at && classes.fieldVerified,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<FieldHTML
|
|
||||||
as='dt'
|
|
||||||
text={name}
|
|
||||||
textEmojified={name_emojified}
|
|
||||||
textHasCustomEmoji={textHasCustomEmoji(name)}
|
|
||||||
titleLength={50}
|
|
||||||
className='translate'
|
|
||||||
{...htmlHandlers}
|
|
||||||
/>
|
|
||||||
<FieldHTML
|
|
||||||
as='dd'
|
|
||||||
text={value_plain ?? ''}
|
|
||||||
textEmojified={value_emojified}
|
|
||||||
textHasCustomEmoji={textHasCustomEmoji(value_plain ?? '')}
|
|
||||||
titleLength={120}
|
|
||||||
{...htmlHandlers}
|
|
||||||
/>
|
|
||||||
{verified_at && (
|
|
||||||
<Icon
|
|
||||||
id='verified'
|
|
||||||
icon={IconVerified}
|
|
||||||
className={classes.fieldVerifiedIcon}
|
|
||||||
aria-label={intl.formatMessage(verifyMessage, {
|
|
||||||
date: intl.formatDate(verified_at, dateFormatOptions),
|
|
||||||
})}
|
|
||||||
noFill
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
)}
|
|
||||||
</dl>
|
</dl>
|
||||||
</CustomEmojiProvider>
|
</CustomEmojiProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FieldRow: FC<
|
||||||
|
{
|
||||||
|
textHasCustomEmoji: (text?: string | null) => boolean;
|
||||||
|
htmlHandlers: ReturnType<typeof useElementHandledLink>;
|
||||||
|
} & AccountFieldShape
|
||||||
|
> = ({
|
||||||
|
textHasCustomEmoji,
|
||||||
|
htmlHandlers,
|
||||||
|
name,
|
||||||
|
name_emojified,
|
||||||
|
value_emojified,
|
||||||
|
value_plain,
|
||||||
|
verified_at,
|
||||||
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const [showAll, setShowAll] = useState(false);
|
||||||
|
const handleClick = useCallback(() => {
|
||||||
|
setShowAll((prev) => !prev);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
/* eslint-disable -- This method of showing field contents is not very accessible, but it's what we've got for now */
|
||||||
|
<div
|
||||||
|
className={classNames(
|
||||||
|
classes.fieldRow,
|
||||||
|
verified_at && classes.fieldVerified,
|
||||||
|
showAll && classes.fieldShowAll,
|
||||||
|
)}
|
||||||
|
onClick={handleClick}
|
||||||
|
/* eslint-enable */
|
||||||
|
>
|
||||||
|
<FieldHTML
|
||||||
|
as='dt'
|
||||||
|
text={name}
|
||||||
|
textEmojified={name_emojified}
|
||||||
|
textHasCustomEmoji={textHasCustomEmoji(name)}
|
||||||
|
titleLength={50}
|
||||||
|
className='translate'
|
||||||
|
{...htmlHandlers}
|
||||||
|
/>
|
||||||
|
<dd>
|
||||||
|
<FieldHTML
|
||||||
|
as='span'
|
||||||
|
text={value_plain ?? ''}
|
||||||
|
textEmojified={value_emojified}
|
||||||
|
textHasCustomEmoji={textHasCustomEmoji(value_plain ?? '')}
|
||||||
|
titleLength={120}
|
||||||
|
{...htmlHandlers}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{verified_at && (
|
||||||
|
<Icon
|
||||||
|
id='verified'
|
||||||
|
icon={IconVerified}
|
||||||
|
className={classes.fieldVerifiedIcon}
|
||||||
|
aria-label={intl.formatMessage(verifyMessage, {
|
||||||
|
date: intl.formatDate(verified_at, dateFormatOptions),
|
||||||
|
})}
|
||||||
|
noFill
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const FieldHTML: FC<
|
const FieldHTML: FC<
|
||||||
{
|
{
|
||||||
as: 'dd' | 'dt';
|
as?: 'span' | 'dt';
|
||||||
text: string;
|
text: string;
|
||||||
textEmojified: string;
|
textEmojified: string;
|
||||||
textHasCustomEmoji: boolean;
|
textHasCustomEmoji: boolean;
|
||||||
@@ -164,11 +200,6 @@ const FieldHTML: FC<
|
|||||||
onElement,
|
onElement,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const [showAll, setShowAll] = useState(false);
|
|
||||||
const handleClick = useCallback(() => {
|
|
||||||
setShowAll((prev) => !prev);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleElement: OnElementHandler = useCallback(
|
const handleElement: OnElementHandler = useCallback(
|
||||||
(element, props, children, extra) => {
|
(element, props, children, extra) => {
|
||||||
if (element instanceof HTMLAnchorElement) {
|
if (element instanceof HTMLAnchorElement) {
|
||||||
@@ -186,17 +217,13 @@ const FieldHTML: FC<
|
|||||||
},
|
},
|
||||||
[onElement, textHasCustomEmoji],
|
[onElement, textHasCustomEmoji],
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EmojiHTML
|
<EmojiHTML
|
||||||
as={as}
|
as={as}
|
||||||
htmlString={textEmojified}
|
htmlString={textEmojified}
|
||||||
title={showTitleOnLength(text, titleLength)}
|
title={showTitleOnLength(text, titleLength)}
|
||||||
className={classNames(
|
className={className}
|
||||||
className,
|
|
||||||
text && isValidUrl(text) && classes.fieldLink,
|
|
||||||
showAll && classes.fieldShowAll,
|
|
||||||
)}
|
|
||||||
onClick={handleClick}
|
|
||||||
onElement={handleElement}
|
onElement={handleElement}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ import ShareIcon from '@/material-icons/400-24px/share.svg?react';
|
|||||||
|
|
||||||
import { isRedesignEnabled } from '../common';
|
import { isRedesignEnabled } from '../common';
|
||||||
|
|
||||||
|
import classes from './redesign.module.scss';
|
||||||
|
|
||||||
export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const { signedIn, permissions } = useIdentity();
|
const { signedIn, permissions } = useIdentity();
|
||||||
@@ -89,6 +91,7 @@ export const AccountMenu: FC<{ accountId: string }> = ({ accountId }) => {
|
|||||||
items={menuItems}
|
items={menuItems}
|
||||||
icon='ellipsis-v'
|
icon='ellipsis-v'
|
||||||
iconComponent={MoreHorizIcon}
|
iconComponent={MoreHorizIcon}
|
||||||
|
className={classes.buttonMenu}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,11 +24,10 @@
|
|||||||
|
|
||||||
.name {
|
.name {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
font-size: 22px;
|
|
||||||
white-space: initial;
|
|
||||||
line-height: normal;
|
|
||||||
|
|
||||||
> h1 {
|
> h1 {
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: normal;
|
||||||
white-space: initial;
|
white-space: initial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,11 +148,33 @@ $button-fallback-breakpoint: #{$button-breakpoint} + 55px;
|
|||||||
border-top: 1px solid var(--color-border-primary);
|
border-top: 1px solid var(--color-border-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.buttonMenu {
|
||||||
|
// Override the modal for mobile.
|
||||||
|
&:global(.actions-modal) {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li :global(.icon) {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio {
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
background-color: var(--color-bg-secondary);
|
background-color: var(--color-bg-secondary);
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
|
||||||
|
:global(.account__header__badges) > & {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
> span {
|
> span {
|
||||||
font-weight: unset;
|
font-weight: unset;
|
||||||
@@ -194,12 +215,13 @@ svg.badgeIcon {
|
|||||||
|
|
||||||
.fieldList {
|
.fieldList {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 160px 1fr min-content;
|
grid-template-columns: 160px 1fr;
|
||||||
column-gap: 12px;
|
column-gap: 12px;
|
||||||
margin: 4px 0 16px;
|
margin: 16px 0;
|
||||||
|
border-top: 0.5px solid var(--color-border-primary);
|
||||||
|
|
||||||
@container (width < 420px) {
|
@container (width < 420px) {
|
||||||
grid-template-columns: 100px 1fr min-content;
|
grid-template-columns: 100px 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,11 +230,10 @@ svg.badgeIcon {
|
|||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
grid-template-columns: subgrid;
|
grid-template-columns: subgrid;
|
||||||
padding: 0 4px;
|
padding: 8px;
|
||||||
|
border-bottom: 0.5px solid var(--color-border-primary);
|
||||||
|
|
||||||
> :is(dt, dd) {
|
> :is(dt, dd) {
|
||||||
margin: 8px 0;
|
|
||||||
|
|
||||||
&:not(.fieldShowAll) {
|
&:not(.fieldShowAll) {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
@@ -227,43 +248,34 @@ svg.badgeIcon {
|
|||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:not(.fieldVerified) > dd {
|
> dd {
|
||||||
grid-column: span 2;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
font-weight: 500;
|
color: inherit;
|
||||||
color: var(--color-text-brand);
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition: 0.2s ease-in-out;
|
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
color: var(--color-text-brand-soft);
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldVerified {
|
.fieldVerified {
|
||||||
background-color: var(--color-bg-brand-softer);
|
background-color: var(--color-bg-success-softer);
|
||||||
}
|
|
||||||
|
|
||||||
.fieldLink:is(dd, dt) {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fieldLink > a {
|
|
||||||
display: block;
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldVerifiedIcon {
|
.fieldVerifiedIcon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fieldNumbersWrapper {
|
.fieldNumbersWrapper {
|
||||||
|
font-size: 13px;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
@@ -323,10 +335,15 @@ svg.badgeIcon {
|
|||||||
border-bottom: 1px solid var(--color-border-primary);
|
border-bottom: 1px solid var(--color-border-primary);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 0 12px;
|
padding: 0 24px;
|
||||||
|
|
||||||
@container (width >= 500px) {
|
@container (width < 500px) {
|
||||||
padding: 0 24px;
|
padding: 0 12px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
flex: 1 1 0px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import {
|
|||||||
export const ActionsModal: React.FC<{
|
export const ActionsModal: React.FC<{
|
||||||
actions: MenuItem[];
|
actions: MenuItem[];
|
||||||
onClick: React.MouseEventHandler;
|
onClick: React.MouseEventHandler;
|
||||||
}> = ({ actions, onClick }) => (
|
className?: string;
|
||||||
<div className='modal-root__modal actions-modal'>
|
}> = ({ actions, onClick, className }) => (
|
||||||
|
<div className={classNames('modal-root__modal actions-modal', className)}>
|
||||||
<ul>
|
<ul>
|
||||||
{actions.map((option, i: number) => {
|
{actions.map((option, i: number) => {
|
||||||
if (option === null) {
|
if (option === null) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import { CustomEmojiFactory } from './custom_emoji';
|
|||||||
import type { CustomEmoji } from './custom_emoji';
|
import type { CustomEmoji } from './custom_emoji';
|
||||||
|
|
||||||
// AccountField
|
// AccountField
|
||||||
interface AccountFieldShape extends Required<ApiAccountFieldJSON> {
|
export interface AccountFieldShape extends Required<ApiAccountFieldJSON> {
|
||||||
name_emojified: string;
|
name_emojified: string;
|
||||||
value_emojified: string;
|
value_emojified: string;
|
||||||
value_plain: string | null;
|
value_plain: string | null;
|
||||||
|
|||||||
Reference in New Issue
Block a user