mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 21:15:32 +09:00
feat(app): redesign discovery and align skeletons with rendered UI
This commit is contained in:
1 parent
e054aa96be
commit
98c40c6979
380 files changed
+50869
-32207
No files matched your search
@@ -20,10 +20,20 @@ export const UserIdentityRow = observer(
|
||||
const displayName = DisplayNameUtils.getNickname(user, guildId, channelId);
|
||||
return (
|
||||
<>
|
||||
<Avatar user={user} size={avatarSize} guildId={guildId === null ? undefined : guildId} className={className} />
|
||||
<flx-user-identity-row-info className={flxElementClassName(styles.info)}>
|
||||
<span className={styles.name}>{displayName}</span>
|
||||
<span className={styles.tag}>{user.tag}</span>
|
||||
<Avatar
|
||||
user={user}
|
||||
size={avatarSize}
|
||||
guildId={guildId === null ? undefined : guildId}
|
||||
className={className}
|
||||
data-flx="user.user-identity-row.avatar"
|
||||
/>
|
||||
<flx-user-identity-row-info className={flxElementClassName(styles.info)} data-flx="user.user-identity-row.info">
|
||||
<span className={styles.name} data-flx="user.user-identity-row.name">
|
||||
{displayName}
|
||||
</span>
|
||||
<span className={styles.tag} data-flx="user.user-identity-row.tag">
|
||||
{user.tag}
|
||||
</span>
|
||||
</flx-user-identity-row-info>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -350,7 +350,11 @@ const LanguageTab = observer(() => {
|
||||
data-flx="user.language-tab.settings-tab-section.language-settings"
|
||||
>
|
||||
<div className={styles.languageControls} data-flx="user.language-tab.language-controls">
|
||||
<LanguageSelector value={currentLocale} onChange={handleLocaleChange} />
|
||||
<LanguageSelector
|
||||
value={currentLocale}
|
||||
onChange={handleLocaleChange}
|
||||
data-flx="user.language-tab.language-selector.locale-change"
|
||||
/>
|
||||
<div className={styles.notice} data-flx="user.language-tab.notice">
|
||||
<p className={styles.noticeText} data-flx="user.language-tab.notice-text">
|
||||
<Trans>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {SettingsTabContainer, SettingsTabContent} from '@app/features/app/compon
|
||||
import {StatusSlate} from '@app/features/app/components/dialogs/shared/StatusSlate';
|
||||
import {computeVerticalDropPosition} from '@app/features/app/components/layout/dnd/DndDropPosition';
|
||||
import type {ConnectionDragItem} from '@app/features/app/components/layout/types/DndTypes';
|
||||
import {DND_TYPES} from '@app/features/app/components/layout/types/DndTypes';
|
||||
import {DragItemType} from '@app/features/app/components/layout/types/DndTypes';
|
||||
import {BLUESKY_PROVIDER_NAME, PRODUCT_NAME} from '@app/features/app/config/I18nDisplayConstants';
|
||||
import {useMergeRefs} from '@app/features/app/hooks/useMergeRefs';
|
||||
import RuntimeConfig from '@app/features/app/state/RuntimeConfig';
|
||||
@@ -85,7 +85,7 @@ interface ConnectionCardProps {
|
||||
|
||||
const ConnectionDragAutoScroll: React.FC<{listRef: React.RefObject<HTMLDivElement | null>}> = ({listRef}) => {
|
||||
const isDraggingConnection = useDragLayer(
|
||||
(monitor) => monitor.isDragging() && monitor.getItemType() === DND_TYPES.CONNECTION,
|
||||
(monitor) => monitor.isDragging() && monitor.getItemType() === DragItemType.CONNECTION,
|
||||
);
|
||||
const getScrollElement = useCallback(() => {
|
||||
const list = listRef.current;
|
||||
@@ -103,7 +103,7 @@ const ConnectionCard: React.FC<ConnectionCardProps> = observer(
|
||||
const cardRef = useRef<HTMLDivElement>(null);
|
||||
const dragItemData = useMemo<ConnectionDragItem>(
|
||||
() => ({
|
||||
type: DND_TYPES.CONNECTION,
|
||||
type: DragItemType.CONNECTION,
|
||||
id: connection.id,
|
||||
index,
|
||||
}),
|
||||
@@ -111,7 +111,7 @@ const ConnectionCard: React.FC<ConnectionCardProps> = observer(
|
||||
);
|
||||
const [{isDragging}, dragRef, preview] = useDrag(
|
||||
() => ({
|
||||
type: DND_TYPES.CONNECTION,
|
||||
type: DragItemType.CONNECTION,
|
||||
item: () => dragItemData,
|
||||
collect: (monitor) => ({isDragging: monitor.isDragging()}),
|
||||
end: () => setDropIndicator(null),
|
||||
@@ -120,7 +120,7 @@ const ConnectionCard: React.FC<ConnectionCardProps> = observer(
|
||||
);
|
||||
const [{isOver}, dropRef] = useDrop(
|
||||
() => ({
|
||||
accept: DND_TYPES.CONNECTION,
|
||||
accept: DragItemType.CONNECTION,
|
||||
hover: (item: ConnectionDragItem, monitor) => {
|
||||
if (item.id === connection.id) {
|
||||
setDropIndicator(null);
|
||||
@@ -431,7 +431,10 @@ const LinkedAccountsTab: React.FC = observer(() => {
|
||||
</div>
|
||||
) : (
|
||||
<DndProvider backend={HTML5Backend} data-flx="user.linked-accounts-tab.dnd-provider">
|
||||
<ConnectionDragAutoScroll listRef={connectionsListRef} />
|
||||
<ConnectionDragAutoScroll
|
||||
listRef={connectionsListRef}
|
||||
data-flx="user.linked-accounts-tab.connection-drag-auto-scroll"
|
||||
/>
|
||||
<div
|
||||
ref={connectionsListRef}
|
||||
className={styles.connectionsList}
|
||||
|
||||
@@ -916,6 +916,7 @@ const MyProfileTabComponent = observer(function MyProfileTabComponent({
|
||||
composerRef={bioComposerRef}
|
||||
emojiPickerOpen={bioExpressionPickerOpen}
|
||||
onEmojiPickerOpenChange={setBioExpressionPickerOpen}
|
||||
data-flx="user.my-profile-tab.my-profile-tab-component.bio-editor.bio-change"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+5
-1
@@ -251,7 +251,11 @@ export const ThemeTabContent: React.FC = observer(() => {
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<ThemeSelector value={currentSelectedTheme} onChange={handleThemeChange} />
|
||||
<ThemeSelector
|
||||
value={currentSelectedTheme}
|
||||
onChange={handleThemeChange}
|
||||
data-flx="user.appearance-tab.theme.theme-tab-content.theme-selector.theme-change"
|
||||
/>
|
||||
<Switch
|
||||
label={i18n._(SYNC_THEME_ACROSS_DEVICES_DESCRIPTOR)}
|
||||
description={i18n._(WHEN_ENABLED_THEME_CHANGES_WILL_SYNC_TO_ALL_DESCRIPTOR)}
|
||||
|
||||
@@ -123,8 +123,9 @@ export const BioEditor = observer(
|
||||
aria-label={i18n._(OPEN_EMOJI_PICKER_DESCRIPTOR)}
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={emojiPickerOpen}
|
||||
data-flx="user.my-profile-tab.bio-editor.emoji-button.emoji-picker-open-change"
|
||||
>
|
||||
<SmileyIcon size={remFromPx(20)} weight="fill" />
|
||||
<SmileyIcon size={remFromPx(20)} weight="fill" data-flx="user.my-profile-tab.bio-editor.smiley-icon" />
|
||||
</button>
|
||||
</FocusRing>
|
||||
) : (
|
||||
@@ -147,10 +148,12 @@ export const BioEditor = observer(
|
||||
}}
|
||||
onClose={onClose}
|
||||
visibleTabs={['emojis']}
|
||||
data-flx="user.my-profile-tab.bio-editor.expression-picker-popout"
|
||||
/>
|
||||
)}
|
||||
data-flx="user.my-profile-tab.bio-editor.popout"
|
||||
>
|
||||
<FocusRing offset={-2} enabled={!disabled}>
|
||||
<FocusRing offset={-2} enabled={!disabled} data-flx="user.my-profile-tab.bio-editor.focus-ring">
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(styles.emojiButton, emojiPickerOpen && styles.emojiButtonActive)}
|
||||
@@ -158,16 +161,17 @@ export const BioEditor = observer(
|
||||
aria-label={i18n._(OPEN_EMOJI_PICKER_DESCRIPTOR)}
|
||||
aria-haspopup="dialog"
|
||||
aria-expanded={emojiPickerOpen}
|
||||
data-flx="user.my-profile-tab.bio-editor.emoji-button"
|
||||
>
|
||||
<SmileyIcon size={remFromPx(20)} weight="fill" />
|
||||
<SmileyIcon size={remFromPx(20)} weight="fill" data-flx="user.my-profile-tab.bio-editor.smiley-icon--2" />
|
||||
</button>
|
||||
</FocusRing>
|
||||
</Popout>
|
||||
);
|
||||
return (
|
||||
<div className="flx-element">
|
||||
<fieldset className={formStyles.fieldset}>
|
||||
<div className={formStyles.labelContainer}>
|
||||
<div className="flx-element" data-flx="user.my-profile-tab.bio-editor.flx-element">
|
||||
<fieldset className={formStyles.fieldset} data-flx="user.my-profile-tab.bio-editor.fieldset">
|
||||
<div className={formStyles.labelContainer} data-flx="user.my-profile-tab.bio-editor.div">
|
||||
<label
|
||||
id={labelId}
|
||||
htmlFor={editableId}
|
||||
@@ -182,12 +186,20 @@ export const BioEditor = observer(
|
||||
}
|
||||
}}
|
||||
className={formStyles.label}
|
||||
data-flx="user.my-profile-tab.bio-editor.label.prevent-default"
|
||||
>
|
||||
{i18n._(ABOUT_ME_DESCRIPTOR)}
|
||||
</label>
|
||||
</div>
|
||||
<div className={formStyles.inputGroup}>
|
||||
<FocusRing within={true} ringTarget={wrapperRef} focusTarget={wrapperRef} offset={-2} enabled={!disabled}>
|
||||
<div className={formStyles.inputGroup} data-flx="user.my-profile-tab.bio-editor.div--2">
|
||||
<FocusRing
|
||||
within={true}
|
||||
ringTarget={wrapperRef}
|
||||
focusTarget={wrapperRef}
|
||||
offset={-2}
|
||||
enabled={!disabled}
|
||||
data-flx="user.my-profile-tab.bio-editor.focus-ring--2"
|
||||
>
|
||||
<div
|
||||
ref={wrapperRef}
|
||||
className={clsx(formStyles.textareaWrapper, surfaceStyles.surface, surfaceInteractionClass)}
|
||||
@@ -205,6 +217,7 @@ export const BioEditor = observer(
|
||||
composer.focus();
|
||||
}
|
||||
}}
|
||||
data-flx="user.my-profile-tab.bio-editor.div.prevent-default"
|
||||
>
|
||||
<LexicalRichInput
|
||||
key={hydrationKey}
|
||||
@@ -231,29 +244,34 @@ export const BioEditor = observer(
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
i18n={i18n}
|
||||
data-flx="user.my-profile-tab.bio-editor.editor.change"
|
||||
/>
|
||||
<div className={formStyles.textareaActions}>
|
||||
<div className={formStyles.textareaActions} data-flx="user.my-profile-tab.bio-editor.div--3">
|
||||
{emojiButton}
|
||||
<div className={styles.characterCountContainer}>
|
||||
<div
|
||||
className={styles.characterCountContainer}
|
||||
data-flx="user.my-profile-tab.bio-editor.character-count-container"
|
||||
>
|
||||
<CharacterCounter
|
||||
currentLength={actualLength}
|
||||
maxLength={actualMaxLength}
|
||||
canUpgrade={false}
|
||||
premiumMaxLength={actualMaxLength}
|
||||
onUpgradeClick={() => undefined}
|
||||
data-flx="user.my-profile-tab.bio-editor.character-counter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</FocusRing>
|
||||
{hasError && (
|
||||
<span id={errorId} className={formStyles.errorText}>
|
||||
<span id={errorId} className={formStyles.errorText} data-flx="user.my-profile-tab.bio-editor.span">
|
||||
{errorMessage}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</fieldset>
|
||||
<div id={descriptionId} className={styles.description}>
|
||||
<div id={descriptionId} className={styles.description} data-flx="user.my-profile-tab.bio-editor.description">
|
||||
<Trans>You can use links, emoji, and markdown.</Trans>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+2
-2
@@ -11,7 +11,7 @@ import {
|
||||
import * as ContextMenuCommands from '@app/features/ui/commands/ContextMenuCommands';
|
||||
import {StatusAwareAvatar} from '@app/features/ui/components/StatusAwareAvatar';
|
||||
import FocusRing from '@app/features/ui/focus_ring/FocusRing';
|
||||
import {getAppZoomFactor} from '@app/features/ui/utils/AppZoomUtils';
|
||||
import {getAppRemScale} from '@app/features/ui/utils/AppZoomUtils';
|
||||
import userProfileModalStyles from '@app/features/user/components/modals/UserProfileModal.module.css';
|
||||
import {
|
||||
isKeyboardContextMenuTrigger,
|
||||
@@ -204,7 +204,7 @@ export const ProfileMediaHeader: React.FC<ProfileMediaHeaderProps> = observer(
|
||||
);
|
||||
const avatarContent = (
|
||||
<StatusAwareAvatar
|
||||
size={avatarSize / getAppZoomFactor()}
|
||||
size={avatarSize / getAppRemScale()}
|
||||
user={user}
|
||||
avatarUrl={avatarUrl}
|
||||
hoverAvatarUrl={hoverAvatarUrl}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
.card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 0.875rem;
|
||||
border-radius: var(--radius-md, 0.375rem);
|
||||
gap: var(--active-now-card-gap);
|
||||
padding: var(--active-now-card-padding-block) var(--active-now-card-padding-inline);
|
||||
border-radius: var(--active-now-card-radius);
|
||||
background-color: var(--background-secondary);
|
||||
border: 0.0625rem solid var(--border-color, rgba(255, 255, 255, 0.04));
|
||||
width: 100%;
|
||||
align-self: stretch;
|
||||
box-shadow: 0 0.375rem 1.125rem rgba(0, 0, 0, 0.08);
|
||||
box-shadow: var(--active-now-card-shadow);
|
||||
}
|
||||
|
||||
.headerContextGroup {
|
||||
@@ -28,7 +28,7 @@
|
||||
.headerLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
gap: var(--active-now-context-gap);
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
@@ -48,7 +48,7 @@
|
||||
.contextButton {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
gap: var(--active-now-context-gap);
|
||||
min-width: 0;
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
@@ -17,6 +17,10 @@ import {LiveBadge} from '@app/features/ui/components/LiveBadge';
|
||||
import {Spinner} from '@app/features/ui/components/Spinner';
|
||||
import {Tooltip} from '@app/features/ui/tooltip/Tooltip';
|
||||
import styles from '@app/features/user/components/profile/VoiceActivityCard.module.css';
|
||||
import {
|
||||
VOICE_ACTIVITY_AVATAR_MAX_VISIBLE,
|
||||
VOICE_ACTIVITY_AVATAR_SIZE_PX,
|
||||
} from '@app/features/user/components/profile/VoiceActivityCardMetrics';
|
||||
import type {User} from '@app/features/user/models/User';
|
||||
import Users from '@app/features/user/state/Users';
|
||||
import * as NicknameUtils from '@app/features/user/utils/NicknameUtils';
|
||||
@@ -252,8 +256,8 @@ export const VoiceActivityCard: React.FC<VoiceActivityCardProps> = observer(({ac
|
||||
data-flx="user.profile.voice-activity-card.participants-row"
|
||||
>
|
||||
<AvatarStack
|
||||
size={20}
|
||||
maxVisible={5}
|
||||
size={VOICE_ACTIVITY_AVATAR_SIZE_PX}
|
||||
maxVisible={VOICE_ACTIVITY_AVATAR_MAX_VISIBLE}
|
||||
users={avatarStackUsers}
|
||||
guildId={guildId}
|
||||
channelId={channelId}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export const VOICE_ACTIVITY_AVATAR_SIZE_PX = 20;
|
||||
export const VOICE_ACTIVITY_AVATAR_MAX_VISIBLE = 5;
|
||||
Reference in new issue
Block a user