snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
82
apps/meteor/client/sidebar/Item/Condensed.stories.tsx
Normal file
82
apps/meteor/client/sidebar/Item/Condensed.stories.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Box, IconButton } from '@rocket.chat/fuselage';
|
||||
import { UserAvatar } from '@rocket.chat/ui-avatar';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { action } from 'storybook/actions';
|
||||
|
||||
import Condensed from './Condensed';
|
||||
import * as Status from '../../components/UserStatus';
|
||||
|
||||
export default {
|
||||
component: Condensed,
|
||||
args: {
|
||||
clickable: true,
|
||||
title: 'John Doe',
|
||||
},
|
||||
decorators: [
|
||||
(fn) => (
|
||||
<Box maxWidth='x300' backgroundColor='dark' borderRadius='x4'>
|
||||
{fn()}
|
||||
</Box>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof Condensed>;
|
||||
|
||||
const Template: StoryFn<typeof Condensed> = (args) => (
|
||||
<Condensed
|
||||
{...args}
|
||||
titleIcon={
|
||||
<Box marginInline={4}>
|
||||
<Status.Online />
|
||||
</Box>
|
||||
}
|
||||
avatar={<UserAvatar username='john.doe' size='x16' url='https://via.placeholder.com/16' />}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Normal = {
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Selected = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
selected: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Menu = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
menuOptions: {
|
||||
hide: {
|
||||
label: { label: 'Hide', icon: 'eye-off' },
|
||||
action: action('action'),
|
||||
},
|
||||
read: {
|
||||
label: { label: 'Mark_read', icon: 'flag' },
|
||||
action: action('action'),
|
||||
},
|
||||
favorite: {
|
||||
label: { label: 'Favorite', icon: 'star' },
|
||||
action: action('action'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Actions = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
actions: (
|
||||
<>
|
||||
<IconButton secondary success icon='phone' />
|
||||
<IconButton secondary danger icon='circle-cross' />
|
||||
<IconButton secondary info icon='trash' />
|
||||
<IconButton secondary icon='phone' />
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
46
apps/meteor/client/sidebar/Item/Condensed.tsx
Normal file
46
apps/meteor/client/sidebar/Item/Condensed.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { IconButton, SidebarItem, SidebarItemAvatarWrapper, SidebarItemMenu, SidebarItemTitle } from '@rocket.chat/fuselage';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useDeferredMenuMount } from './useDeferredMenuMount';
|
||||
|
||||
export type CondensedProps = {
|
||||
title: ReactNode;
|
||||
titleIcon?: ReactNode;
|
||||
avatar: ReactNode;
|
||||
icon?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
href?: string;
|
||||
unread?: boolean;
|
||||
menu?: () => ReactNode;
|
||||
menuOptions?: any;
|
||||
selected?: boolean;
|
||||
badges?: ReactNode;
|
||||
clickable?: boolean;
|
||||
} & Omit<HTMLAttributes<HTMLAnchorElement>, 'is'>;
|
||||
|
||||
const Condensed = ({ icon, title, titleIcon, avatar, actions, unread, menu, badges, ...props }: CondensedProps) => {
|
||||
const { mounted: menuVisibility, requestMount, mountNow } = useDeferredMenuMount();
|
||||
|
||||
return (
|
||||
<SidebarItem {...props} onFocus={mountNow} onPointerEnter={requestMount}>
|
||||
{avatar && <SidebarItemAvatarWrapper>{avatar}</SidebarItemAvatarWrapper>}
|
||||
{icon}
|
||||
<SidebarItemTitle unread={unread}>{title}</SidebarItemTitle>
|
||||
{titleIcon}
|
||||
{badges}
|
||||
{actions}
|
||||
{menu && (
|
||||
<SidebarItemMenu>
|
||||
{menuVisibility ? (
|
||||
menu()
|
||||
) : (
|
||||
<IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-item__menu icon='kebab' onPointerDown={mountNow} />
|
||||
)}
|
||||
</SidebarItemMenu>
|
||||
)}
|
||||
</SidebarItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Condensed);
|
||||
97
apps/meteor/client/sidebar/Item/Extended.stories.tsx
Normal file
97
apps/meteor/client/sidebar/Item/Extended.stories.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Box, IconButton, Badge } from '@rocket.chat/fuselage';
|
||||
import { UserAvatar } from '@rocket.chat/ui-avatar';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { action } from 'storybook/actions';
|
||||
|
||||
import Extended from './Extended';
|
||||
import * as Status from '../../components/UserStatus';
|
||||
|
||||
export default {
|
||||
component: Extended,
|
||||
decorators: [
|
||||
(fn) => (
|
||||
<Box maxWidth='x300' backgroundColor='dark' borderRadius='x4'>
|
||||
{fn()}
|
||||
</Box>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof Extended>;
|
||||
|
||||
const Template: StoryFn<typeof Extended> = (args) => (
|
||||
<Extended
|
||||
{...args}
|
||||
title='John Doe'
|
||||
subtitle={
|
||||
<Box display='flex' flexDirection='row' width='full' alignItems='center'>
|
||||
<Box flexGrow='1' withTruncatedText>
|
||||
John Doe: test 123
|
||||
</Box>
|
||||
<Badge
|
||||
{...({
|
||||
style: {
|
||||
backgroundColor: '#6c727a',
|
||||
color: 'var(--rcx-color-surface, white)',
|
||||
flexShrink: 0,
|
||||
},
|
||||
} as any)}
|
||||
>
|
||||
99
|
||||
</Badge>
|
||||
</Box>
|
||||
}
|
||||
titleIcon={
|
||||
<Box marginInline={4}>
|
||||
<Status.Online />
|
||||
</Box>
|
||||
}
|
||||
avatar={<UserAvatar username='john.doe' size='x16' url='https://via.placeholder.com/16' />}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Normal = {
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Selected = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
selected: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Menu = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
menuOptions: {
|
||||
hide: {
|
||||
label: { label: 'Hide', icon: 'eye-off' },
|
||||
action: action('action'),
|
||||
},
|
||||
read: {
|
||||
label: { label: 'Mark_read', icon: 'flag' },
|
||||
action: action('action'),
|
||||
},
|
||||
favorite: {
|
||||
label: { label: 'Favorite', icon: 'star' },
|
||||
action: action('action'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Actions = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
actions: (
|
||||
<>
|
||||
<IconButton secondary success icon='phone' />
|
||||
<IconButton secondary danger icon='circle-cross' />
|
||||
<IconButton secondary info icon='trash' />
|
||||
<IconButton secondary icon='phone' />
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
84
apps/meteor/client/sidebar/Item/Extended.tsx
Normal file
84
apps/meteor/client/sidebar/Item/Extended.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import {
|
||||
SidebarItem,
|
||||
SidebarItemAvatarWrapper,
|
||||
SidebarItemCol,
|
||||
SidebarItemRow,
|
||||
SidebarItemTitle,
|
||||
SidebarItemTimestamp,
|
||||
SidebarItemContent,
|
||||
SidebarItemMenu,
|
||||
IconButton,
|
||||
} from '@rocket.chat/fuselage';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useDeferredMenuMount } from './useDeferredMenuMount';
|
||||
import { useShortTimeAgo } from '../../hooks/useTimeAgo';
|
||||
|
||||
export type ExtendedProps = {
|
||||
icon?: ReactNode;
|
||||
title: ReactNode;
|
||||
avatar?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
href?: string;
|
||||
time?: any;
|
||||
menu?: () => ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
badges?: ReactNode;
|
||||
unread?: boolean;
|
||||
selected?: boolean;
|
||||
menuOptions?: any;
|
||||
titleIcon?: ReactNode;
|
||||
threadUnread?: boolean;
|
||||
} & Omit<HTMLAttributes<HTMLElement>, 'is'>;
|
||||
|
||||
const Extended = ({
|
||||
icon,
|
||||
title,
|
||||
avatar,
|
||||
actions,
|
||||
href,
|
||||
time,
|
||||
menu,
|
||||
menuOptions: _menuOptions,
|
||||
subtitle = '',
|
||||
titleIcon,
|
||||
badges,
|
||||
threadUnread: _threadUnread,
|
||||
unread,
|
||||
selected,
|
||||
...props
|
||||
}: ExtendedProps) => {
|
||||
const formatDate = useShortTimeAgo();
|
||||
const { mounted: menuVisibility, requestMount, mountNow } = useDeferredMenuMount();
|
||||
|
||||
return (
|
||||
<SidebarItem level={2} href={href} selected={selected} {...props} onFocus={mountNow} onPointerEnter={requestMount}>
|
||||
{avatar && <SidebarItemAvatarWrapper>{avatar}</SidebarItemAvatarWrapper>}
|
||||
<SidebarItemCol>
|
||||
<SidebarItemRow>
|
||||
{icon}
|
||||
<SidebarItemTitle unread={unread}>{title}</SidebarItemTitle>
|
||||
{time && <SidebarItemTimestamp>{formatDate(time)}</SidebarItemTimestamp>}
|
||||
</SidebarItemRow>
|
||||
<SidebarItemRow>
|
||||
<SidebarItemContent unread={unread}>{subtitle}</SidebarItemContent>
|
||||
{titleIcon}
|
||||
{badges}
|
||||
{actions}
|
||||
{menu && (
|
||||
<SidebarItemMenu>
|
||||
{menuVisibility ? (
|
||||
menu()
|
||||
) : (
|
||||
<IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-item__menu icon='kebab' onPointerDown={mountNow} />
|
||||
)}
|
||||
</SidebarItemMenu>
|
||||
)}
|
||||
</SidebarItemRow>
|
||||
</SidebarItemCol>
|
||||
</SidebarItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Extended);
|
||||
81
apps/meteor/client/sidebar/Item/Medium.stories.tsx
Normal file
81
apps/meteor/client/sidebar/Item/Medium.stories.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Box, IconButton } from '@rocket.chat/fuselage';
|
||||
import { UserAvatar } from '@rocket.chat/ui-avatar';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { action } from 'storybook/actions';
|
||||
|
||||
import Medium from './Medium';
|
||||
import * as Status from '../../components/UserStatus';
|
||||
|
||||
export default {
|
||||
component: Medium,
|
||||
args: {
|
||||
title: 'John Doe',
|
||||
},
|
||||
decorators: [
|
||||
(fn) => (
|
||||
<Box maxWidth='x300' backgroundColor='dark' borderRadius='x4'>
|
||||
{fn()}
|
||||
</Box>
|
||||
),
|
||||
],
|
||||
} satisfies Meta<typeof Medium>;
|
||||
|
||||
const Template: StoryFn<typeof Medium> = (args) => (
|
||||
<Medium
|
||||
{...args}
|
||||
titleIcon={
|
||||
<Box marginInline={4}>
|
||||
<Status.Online />
|
||||
</Box>
|
||||
}
|
||||
avatar={<UserAvatar username='john.doe' size='x16' url='https://via.placeholder.com/16' />}
|
||||
/>
|
||||
);
|
||||
|
||||
export const Normal = {
|
||||
render: Template,
|
||||
};
|
||||
|
||||
export const Selected = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
selected: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Menu = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
menuOptions: {
|
||||
hide: {
|
||||
label: { label: 'Hide', icon: 'eye-off' },
|
||||
action: action('action'),
|
||||
},
|
||||
read: {
|
||||
label: { label: 'Mark_read', icon: 'flag' },
|
||||
action: action('action'),
|
||||
},
|
||||
favorite: {
|
||||
label: { label: 'Favorite', icon: 'star' },
|
||||
action: action('action'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Actions = {
|
||||
render: Template,
|
||||
|
||||
args: {
|
||||
actions: (
|
||||
<>
|
||||
<IconButton secondary success icon='phone' />
|
||||
<IconButton secondary danger icon='circle-cross' />
|
||||
<IconButton secondary info icon='trash' />
|
||||
<IconButton secondary icon='phone' />
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
45
apps/meteor/client/sidebar/Item/Medium.tsx
Normal file
45
apps/meteor/client/sidebar/Item/Medium.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { IconButton, SidebarItem, SidebarItemAvatarWrapper, SidebarItemMenu, SidebarItemTitle } from '@rocket.chat/fuselage';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { useDeferredMenuMount } from './useDeferredMenuMount';
|
||||
|
||||
export type MediumProps = {
|
||||
title: ReactNode;
|
||||
titleIcon?: ReactNode;
|
||||
avatar: ReactNode;
|
||||
icon?: ReactNode;
|
||||
actions?: ReactNode;
|
||||
href?: string;
|
||||
unread?: boolean;
|
||||
menu?: () => ReactNode;
|
||||
badges?: ReactNode;
|
||||
selected?: boolean;
|
||||
menuOptions?: any;
|
||||
} & Omit<HTMLAttributes<HTMLElement>, 'is'>;
|
||||
|
||||
const Medium = ({ icon, title, titleIcon, avatar, actions, badges, unread, menu, ...props }: MediumProps) => {
|
||||
const { mounted: menuVisibility, requestMount, mountNow } = useDeferredMenuMount();
|
||||
|
||||
return (
|
||||
<SidebarItem {...props} onFocus={mountNow} onPointerEnter={requestMount}>
|
||||
<SidebarItemAvatarWrapper>{avatar}</SidebarItemAvatarWrapper>
|
||||
{icon}
|
||||
<SidebarItemTitle unread={unread}>{title}</SidebarItemTitle>
|
||||
{titleIcon}
|
||||
{badges}
|
||||
{actions}
|
||||
{menu && (
|
||||
<SidebarItemMenu>
|
||||
{menuVisibility ? (
|
||||
menu()
|
||||
) : (
|
||||
<IconButton tabIndex={-1} aria-hidden mini rcx-sidebar-item__menu icon='kebab' onPointerDown={mountNow} />
|
||||
)}
|
||||
</SidebarItemMenu>
|
||||
)}
|
||||
</SidebarItem>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(Medium);
|
||||
59
apps/meteor/client/sidebar/Item/useDeferredMenuMount.ts
Normal file
59
apps/meteor/client/sidebar/Item/useDeferredMenuMount.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
type IdleHandle = { type: 'idle' | 'timeout'; id: number };
|
||||
|
||||
const schedule = (fn: () => void): IdleHandle => {
|
||||
if (typeof window !== 'undefined' && typeof window.requestIdleCallback === 'function') {
|
||||
return { type: 'idle', id: window.requestIdleCallback(fn, { timeout: 200 }) };
|
||||
}
|
||||
return { type: 'timeout', id: window.setTimeout(fn, 50) };
|
||||
};
|
||||
|
||||
const cancel = (handle: IdleHandle) => {
|
||||
if (handle.type === 'idle' && typeof window.cancelIdleCallback === 'function') {
|
||||
window.cancelIdleCallback(handle.id);
|
||||
return;
|
||||
}
|
||||
window.clearTimeout(handle.id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Defers mounting the sidebar item's RoomMenu until the browser is idle. The menu's hooks
|
||||
* (useUserSubscription, usePermission, useSetting, useOmnichannelPrioritiesMenu, useUserPresence)
|
||||
* are not cheap to run synchronously inside the same pointerover/click task as a room navigation,
|
||||
* so we let the browser finish more urgent work first.
|
||||
*/
|
||||
export const useDeferredMenuMount = () => {
|
||||
const [mounted, setMounted] = useState(typeof window !== 'undefined' && !!window.DISABLE_ANIMATION);
|
||||
const handleRef = useRef<IdleHandle | undefined>(undefined);
|
||||
|
||||
const requestMount = useCallback(() => {
|
||||
if (mounted || handleRef.current !== undefined) {
|
||||
return;
|
||||
}
|
||||
handleRef.current = schedule(() => {
|
||||
handleRef.current = undefined;
|
||||
setMounted(true);
|
||||
});
|
||||
}, [mounted]);
|
||||
|
||||
const mountNow = useCallback(() => {
|
||||
if (handleRef.current !== undefined) {
|
||||
cancel(handleRef.current);
|
||||
handleRef.current = undefined;
|
||||
}
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
if (handleRef.current !== undefined) {
|
||||
cancel(handleRef.current);
|
||||
handleRef.current = undefined;
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return { mounted, requestMount, mountNow };
|
||||
};
|
||||
Reference in New Issue
Block a user