nicolium: Add a timeline picker to timeline column header
Signed-off-by: nicole mikołajczyk <git@mkljczk.pl>
This commit is contained in:
213
packages/nicolium/src/components/timeline-picker.tsx
Normal file
213
packages/nicolium/src/components/timeline-picker.tsx
Normal file
@ -0,0 +1,213 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useLoggedIn } from '@/hooks/use-logged-in';
|
||||
import { useAntennas } from '@/queries/accounts/use-antennas';
|
||||
import { useCircles } from '@/queries/accounts/use-circles';
|
||||
import { useLists } from '@/queries/accounts/use-lists';
|
||||
import { useInstance } from '@/stores/instance';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
import DropdownMenu, { type Menu } from './dropdown-menu';
|
||||
import Icon from './ui/icon';
|
||||
|
||||
const messages = defineMessages({
|
||||
homeTimeline: { id: 'column.home', defaultMessage: 'Home' },
|
||||
localTimeline: { id: 'column.community', defaultMessage: 'Local timeline' },
|
||||
bubbleTimeline: { id: 'column.bubble', defaultMessage: 'Bubble timeline' },
|
||||
federatedTimeline: { id: 'column.public', defaultMessage: 'Fediverse timeline' },
|
||||
wrenchedTimeline: { id: 'column.wrenched', defaultMessage: 'Wrenched timeline' },
|
||||
lists: { id: 'column.lists', defaultMessage: 'Lists' },
|
||||
circles: { id: 'column.circles', defaultMessage: 'Circles' },
|
||||
antennas: { id: 'column.antennas', defaultMessage: 'Antennas' },
|
||||
pinnedInstances: { id: 'timeline_picker.pinned_instances', defaultMessage: 'Pinned instances' },
|
||||
});
|
||||
|
||||
interface ITimelinePicker {
|
||||
active:
|
||||
| 'home'
|
||||
| 'local'
|
||||
| 'bubble'
|
||||
| 'federated'
|
||||
| 'wrenched'
|
||||
| `list:${string}`
|
||||
| `circle:${string}`
|
||||
| `antenna:${string}`
|
||||
| `instance:${string}`;
|
||||
}
|
||||
|
||||
const TimelinePicker: React.FC<ITimelinePicker> = ({ active }) => {
|
||||
const intl = useIntl();
|
||||
const features = useFeatures();
|
||||
const { isLoggedIn } = useLoggedIn();
|
||||
const timelineAccess = useInstance().configuration.timelines_access;
|
||||
const pinnedHosts = useSettings().remote_timeline.pinnedHosts;
|
||||
|
||||
const { data: lists } = useLists();
|
||||
const { data: circles } = useCircles();
|
||||
const { data: antennas } = useAntennas();
|
||||
|
||||
const heading = useMemo(() => {
|
||||
switch (active) {
|
||||
case 'home':
|
||||
return intl.formatMessage(messages.homeTimeline);
|
||||
case 'local':
|
||||
return intl.formatMessage(messages.localTimeline);
|
||||
case 'bubble':
|
||||
return intl.formatMessage(messages.bubbleTimeline);
|
||||
case 'federated':
|
||||
return intl.formatMessage(messages.federatedTimeline);
|
||||
case 'wrenched':
|
||||
return intl.formatMessage(messages.wrenchedTimeline);
|
||||
default:
|
||||
if (active.startsWith('list:')) {
|
||||
const list = lists?.find((list) => `list:${list.id}` === active);
|
||||
return list?.title ?? '';
|
||||
}
|
||||
if (active.startsWith('circle:')) {
|
||||
const circle = circles?.find((circle) => `circle:${circle.id}` === active);
|
||||
return circle?.title ?? '';
|
||||
}
|
||||
if (active.startsWith('antenna:')) {
|
||||
const antenna = antennas?.find((antenna) => `antenna:${antenna.id}` === active);
|
||||
return antenna?.title ?? '';
|
||||
}
|
||||
if (active.startsWith('instance:')) {
|
||||
return active.replace('instance:', '');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}, [active]);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const items: Menu = [];
|
||||
|
||||
if (isLoggedIn) {
|
||||
items.push({
|
||||
to: '/',
|
||||
text: intl.formatMessage(messages.homeTimeline),
|
||||
icon: require('@phosphor-icons/core/regular/house.svg'),
|
||||
active: active === 'home',
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
isLoggedIn
|
||||
? timelineAccess.live_feeds.local !== 'disabled'
|
||||
: timelineAccess.live_feeds.local === 'public'
|
||||
) {
|
||||
items.push({
|
||||
to: '/timeline/local',
|
||||
text: intl.formatMessage(messages.localTimeline),
|
||||
icon: require('@phosphor-icons/core/regular/planet.svg'),
|
||||
active: active === 'local',
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
features.bubbleTimeline && isLoggedIn
|
||||
? timelineAccess.live_feeds.bubble !== 'disabled'
|
||||
: timelineAccess.live_feeds.bubble === 'public'
|
||||
) {
|
||||
items.push({
|
||||
to: '/timeline/bubble',
|
||||
text: intl.formatMessage(messages.bubbleTimeline),
|
||||
icon: require('@phosphor-icons/core/regular/graph.svg'),
|
||||
active: active === 'bubble',
|
||||
});
|
||||
}
|
||||
if (
|
||||
features.bubbleTimeline && isLoggedIn
|
||||
? timelineAccess.live_feeds.bubble !== 'disabled'
|
||||
: timelineAccess.live_feeds.bubble === 'public'
|
||||
) {
|
||||
items.push({
|
||||
to: '/timeline/fediverse',
|
||||
text: intl.formatMessage(messages.federatedTimeline),
|
||||
icon: require('@phosphor-icons/core/regular/fediverse-logo.svg'),
|
||||
active: active === 'federated',
|
||||
});
|
||||
}
|
||||
if (
|
||||
features.wrenchedTimeline && isLoggedIn
|
||||
? timelineAccess.live_feeds.wrenched !== 'disabled'
|
||||
: timelineAccess.live_feeds.wrenched === 'public'
|
||||
) {
|
||||
items.push({
|
||||
to: '/timeline/wrenched',
|
||||
text: intl.formatMessage(messages.wrenchedTimeline),
|
||||
icon: require('@phosphor-icons/core/regular/wrench.svg'),
|
||||
active: active === 'wrenched',
|
||||
});
|
||||
}
|
||||
if (lists?.length) {
|
||||
items.push({
|
||||
text: intl.formatMessage(messages.lists),
|
||||
active: active.startsWith('list:'),
|
||||
icon: require('@phosphor-icons/core/regular/list-dashes.svg'),
|
||||
items: lists.map((list) => ({
|
||||
to: '/list/$listId',
|
||||
params: { listId: list.id },
|
||||
text: list.title,
|
||||
icon: require('@phosphor-icons/core/regular/list-dashes.svg'),
|
||||
active: active === `list:${list.id}`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
if (circles?.length) {
|
||||
items.push({
|
||||
text: intl.formatMessage(messages.circles),
|
||||
active: active.startsWith('circle:'),
|
||||
icon: require('@phosphor-icons/core/regular/circles-three.svg'),
|
||||
items: circles.map((circle) => ({
|
||||
to: '/circles/$circleId',
|
||||
params: { circleId: circle.id },
|
||||
text: circle.title,
|
||||
icon: require('@phosphor-icons/core/regular/list-dashes.svg'),
|
||||
active: active === `circle:${circle.id}`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
if (antennas?.length) {
|
||||
items.push({
|
||||
text: intl.formatMessage(messages.antennas),
|
||||
active: active.startsWith('antenna:'),
|
||||
icon: require('@phosphor-icons/core/regular/broadcast.svg'),
|
||||
items: antennas.map((antenna) => ({
|
||||
to: '/antennas/$antennaId',
|
||||
params: { antennaId: antenna.id },
|
||||
text: antenna.title,
|
||||
icon: require('@phosphor-icons/core/regular/list-dashes.svg'),
|
||||
active: active === `antenna:${antenna.id}`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
if (pinnedHosts.length) {
|
||||
items.push({
|
||||
text: intl.formatMessage(messages.pinnedInstances),
|
||||
active: active.startsWith('instance:'),
|
||||
icon: require('@phosphor-icons/core/regular/globe-simple.svg'),
|
||||
items: pinnedHosts.map((instance) => ({
|
||||
to: '/timeline/$instance',
|
||||
params: { instance },
|
||||
text: instance,
|
||||
icon: require('@phosphor-icons/core/regular/globe-simple.svg'),
|
||||
active: active === `instance:${instance}`,
|
||||
})),
|
||||
});
|
||||
}
|
||||
return items;
|
||||
}, [active, lists, circles, antennas, features, isLoggedIn]);
|
||||
|
||||
return (
|
||||
<DropdownMenu items={items} width='16rem' placement='bottom-start'>
|
||||
<div className='⁂-timeline-picker'>
|
||||
{heading}
|
||||
<Icon src={require('@phosphor-icons/core/regular/caret-down.svg')} aria-hidden />
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
||||
|
||||
export { TimelinePicker };
|
||||
@ -8,17 +8,28 @@ import { useFrontendConfig } from '@/hooks/use-frontend-config';
|
||||
|
||||
import { Card, CardBody, CardHeader, CardTitle, type CardSizes } from './card';
|
||||
|
||||
interface IColumnHeader extends Pick<IColumn, 'backHref' | 'backParams' | 'className' | 'action'> {
|
||||
label?: React.ReactNode;
|
||||
}
|
||||
type IColumnHeader = Pick<
|
||||
IColumn,
|
||||
| 'label'
|
||||
| 'title'
|
||||
| 'withBack'
|
||||
| 'backHref'
|
||||
| 'backParams'
|
||||
| 'className'
|
||||
| 'action'
|
||||
| 'truncateTitle'
|
||||
>;
|
||||
|
||||
/** Contains the column title with optional back button. */
|
||||
const ColumnHeader: React.FC<IColumnHeader> = ({
|
||||
label,
|
||||
title,
|
||||
withBack,
|
||||
backHref,
|
||||
backParams,
|
||||
className,
|
||||
action,
|
||||
truncateTitle,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const { history } = useRouter();
|
||||
@ -37,8 +48,8 @@ const ColumnHeader: React.FC<IColumnHeader> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<CardHeader className={className} onBackClick={handleBackClick}>
|
||||
<CardTitle title={label} />
|
||||
<CardHeader className={className} onBackClick={withBack ? handleBackClick : undefined}>
|
||||
<CardTitle title={title || label} truncate={truncateTitle} />
|
||||
|
||||
{action && <div className='⁂-column__header__action'>{action}</div>}
|
||||
</CardHeader>
|
||||
@ -47,11 +58,13 @@ const ColumnHeader: React.FC<IColumnHeader> = ({
|
||||
|
||||
interface IColumn {
|
||||
/** Route the back button goes to. */
|
||||
withBack?: boolean;
|
||||
backHref?: LinkOptions['to'];
|
||||
backParams?: LinkOptions['params'];
|
||||
backSearch?: LinkOptions['search'];
|
||||
/** Column title text. */
|
||||
label?: string;
|
||||
title?: React.ReactNode;
|
||||
/** Whether this column should have a transparent background. */
|
||||
transparent?: boolean;
|
||||
/** Whether this column should have a title and back button. */
|
||||
@ -66,20 +79,24 @@ interface IColumn {
|
||||
action?: React.ReactNode;
|
||||
/** Column size, inherited from Card. */
|
||||
size?: CardSizes;
|
||||
truncateTitle?: boolean;
|
||||
}
|
||||
|
||||
/** A backdrop for the main section of the UI. */
|
||||
const Column: React.FC<IColumn> = (props): React.JSX.Element => {
|
||||
const {
|
||||
withBack = true,
|
||||
backHref,
|
||||
children,
|
||||
label,
|
||||
title,
|
||||
transparent = false,
|
||||
withHeader = true,
|
||||
className,
|
||||
bodyClassName,
|
||||
action,
|
||||
size,
|
||||
truncateTitle,
|
||||
} = props;
|
||||
const frontendConfig = useFrontendConfig();
|
||||
const [isScrolled, setIsScrolled] = useState(false);
|
||||
@ -120,11 +137,14 @@ const Column: React.FC<IColumn> = (props): React.JSX.Element => {
|
||||
{withHeader && (
|
||||
<ColumnHeader
|
||||
label={label}
|
||||
title={title}
|
||||
withBack={withBack}
|
||||
backHref={backHref}
|
||||
className={clsx('⁂-column__header', {
|
||||
'⁂-column__header--scrolled': isScrolled,
|
||||
})}
|
||||
action={action}
|
||||
truncateTitle={truncateTitle}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import Button from '@/components/ui/button';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
interface IPinnedHostsPicker {
|
||||
/** The active host among pinned hosts. */
|
||||
host?: string;
|
||||
}
|
||||
|
||||
const PinnedHostsPicker: React.FC<IPinnedHostsPicker> = ({ host: activeHost }) => {
|
||||
const settings = useSettings();
|
||||
const pinnedHosts = settings.remote_timeline.pinnedHosts;
|
||||
|
||||
if (!pinnedHosts.length) return null;
|
||||
|
||||
return (
|
||||
<div className='mb-4 flex gap-2 black:mx-2'>
|
||||
{pinnedHosts.map((host) => (
|
||||
<Button
|
||||
key={host}
|
||||
to='/timeline/$instance'
|
||||
params={{ instance: host }}
|
||||
size='sm'
|
||||
theme={host === activeHost ? 'accent' : 'secondary'}
|
||||
>
|
||||
{host}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { PinnedHostsPicker as default };
|
||||
@ -2024,6 +2024,7 @@
|
||||
"timeline.gap.load_newer": "Load newer posts",
|
||||
"timeline.gap.load_older": "Load older posts",
|
||||
"timeline.gap.load_recent": "Load recent posts",
|
||||
"timeline_picker.pinned_instances": "Pinned instances",
|
||||
"toast.view": "View",
|
||||
"trends.count_by_accounts": "{count} {rawCount, plural, one {person} other {people}} talking",
|
||||
"trends.no_accounts": "Try entering a search query or browsing the profile directory to find accounts to follow.",
|
||||
|
||||
@ -5,6 +5,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
import { AntennaTimelineColumn } from '@/columns/timeline';
|
||||
import DropdownMenu from '@/components/dropdown-menu';
|
||||
import MissingIndicator from '@/components/missing-indicator';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
// import Button from '@/components/ui/button';
|
||||
import Column from '@/components/ui/column';
|
||||
import Spinner from '@/components/ui/spinner';
|
||||
@ -90,6 +91,8 @@ const AntennaTimelinePage: React.FC = () => {
|
||||
src={require('@phosphor-icons/core/regular/dots-three-vertical.svg')}
|
||||
/>
|
||||
}
|
||||
title={<TimelinePicker active={`antenna:${antennaId}`} />}
|
||||
truncateTitle={false}
|
||||
>
|
||||
<AntennaTimelineColumn
|
||||
antennaId={antennaId}
|
||||
|
||||
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { BubbleTimelineColumn } from '@/columns/timeline';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
import Column from '@/components/ui/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -12,7 +13,12 @@ const BubbleTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)}>
|
||||
<Column
|
||||
className='-mt-3 sm:mt-0'
|
||||
label={intl.formatMessage(messages.title)}
|
||||
title={<TimelinePicker active='bubble' />}
|
||||
truncateTitle={false}
|
||||
>
|
||||
<BubbleTimelineColumn
|
||||
emptyMessageText={
|
||||
<FormattedMessage
|
||||
|
||||
@ -5,6 +5,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
import { CircleTimelineColumn } from '@/columns/timeline';
|
||||
import DropdownMenu from '@/components/dropdown-menu';
|
||||
import MissingIndicator from '@/components/missing-indicator';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
import Button from '@/components/ui/button';
|
||||
import Column from '@/components/ui/column';
|
||||
import Spinner from '@/components/ui/spinner';
|
||||
@ -90,6 +91,8 @@ const CircleTimelinePage: React.FC = () => {
|
||||
src={require('@phosphor-icons/core/regular/dots-three-vertical.svg')}
|
||||
/>
|
||||
}
|
||||
title={<TimelinePicker active={`cirlce:${circleId}`} />}
|
||||
truncateTitle={false}
|
||||
>
|
||||
<CircleTimelineColumn
|
||||
circleId={circleId}
|
||||
|
||||
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { PublicTimelineColumn } from '@/columns/timeline';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
import Column from '@/components/ui/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -12,7 +13,12 @@ const CommunityTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)}>
|
||||
<Column
|
||||
className='-mt-3 sm:mt-0'
|
||||
label={intl.formatMessage(messages.title)}
|
||||
title={<TimelinePicker active='local' />}
|
||||
truncateTitle={false}
|
||||
>
|
||||
<PublicTimelineColumn
|
||||
local
|
||||
emptyMessageText={
|
||||
|
||||
@ -3,6 +3,7 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { HomeTimelineColumn } from '@/columns/timeline';
|
||||
import { Link } from '@/components/link';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
import Column from '@/components/ui/column';
|
||||
import Text from '@/components/ui/text';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
@ -46,7 +47,13 @@ const HomeTimelinePage: React.FC = () => {
|
||||
if (isSledzikRemoved) return null;
|
||||
|
||||
return (
|
||||
<Column className='py-0' label={intl.formatMessage(messages.title)} withHeader={false}>
|
||||
<Column
|
||||
className='py-0'
|
||||
label={intl.formatMessage(messages.title)}
|
||||
title={<TimelinePicker active='home' />}
|
||||
withBack={false}
|
||||
truncateTitle={false}
|
||||
>
|
||||
<HomeTimelineColumn
|
||||
emptyMessageText={
|
||||
<div className='flex flex-col gap-1'>
|
||||
|
||||
@ -5,6 +5,7 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
||||
import { ListTimelineColumn } from '@/columns/timeline';
|
||||
import DropdownMenu from '@/components/dropdown-menu';
|
||||
import { EmptyMessage } from '@/components/empty-message';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
import Button from '@/components/ui/button';
|
||||
import Column from '@/components/ui/column';
|
||||
import Spinner from '@/components/ui/spinner';
|
||||
@ -67,7 +68,11 @@ const ListTimelinePage: React.FC = () => {
|
||||
);
|
||||
} else if (!list) {
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.notFound)}>
|
||||
<Column
|
||||
label={intl.formatMessage(messages.notFound)}
|
||||
title={<TimelinePicker active={`list:${listId}`} />}
|
||||
truncateTitle={false}
|
||||
>
|
||||
<EmptyMessage
|
||||
heading={<FormattedMessage id='list.not_found_heading' defaultMessage='List not found' />}
|
||||
text={
|
||||
|
||||
@ -4,9 +4,9 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { changeSetting } from '@/actions/settings';
|
||||
import { PublicTimelineColumn } from '@/columns/timeline';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
import Accordion from '@/components/ui/accordion';
|
||||
import Column from '@/components/ui/column';
|
||||
import PinnedHostsPicker from '@/features/remote-timeline/components/pinned-hosts-picker';
|
||||
import { useInstance } from '@/stores/instance';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
@ -33,9 +33,12 @@ const PublicTimelinePage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)}>
|
||||
<PinnedHostsPicker />
|
||||
|
||||
<Column
|
||||
className='-mt-3 sm:mt-0'
|
||||
label={intl.formatMessage(messages.title)}
|
||||
title={<TimelinePicker active='federated' />}
|
||||
truncateTitle={false}
|
||||
>
|
||||
{showExplanationBox && (
|
||||
<Accordion
|
||||
headline={
|
||||
|
||||
@ -3,10 +3,10 @@ import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { PublicTimelineColumn } from '@/columns/timeline';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
import Column from '@/components/ui/column';
|
||||
import IconButton from '@/components/ui/icon-button';
|
||||
import Text from '@/components/ui/text';
|
||||
import PinnedHostsPicker from '@/features/remote-timeline/components/pinned-hosts-picker';
|
||||
import { remoteTimelineRoute } from '@/features/ui/router';
|
||||
import { useSettings } from '@/stores/settings';
|
||||
|
||||
@ -30,9 +30,11 @@ const RemoteTimelinePage: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Column label={instance}>
|
||||
{instance && <PinnedHostsPicker host={instance} />}
|
||||
|
||||
<Column
|
||||
label={instance}
|
||||
title={<TimelinePicker active={`instance:${instance}`} />}
|
||||
truncateTitle={false}
|
||||
>
|
||||
{!pinned && (
|
||||
<div className='mb-4 flex gap-2 px-2'>
|
||||
<IconButton
|
||||
|
||||
@ -2,6 +2,7 @@ import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { WrenchedTimelineColumn } from '@/columns/timeline';
|
||||
import { TimelinePicker } from '@/components/timeline-picker';
|
||||
import Column from '@/components/ui/column';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -12,7 +13,12 @@ const WrenchedTimelinePage = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Column className='-mt-3 sm:mt-0' label={intl.formatMessage(messages.title)}>
|
||||
<Column
|
||||
className='-mt-3 sm:mt-0'
|
||||
label={intl.formatMessage(messages.title)}
|
||||
title={<TimelinePicker active='wrenched' />}
|
||||
truncateTitle={false}
|
||||
>
|
||||
<WrenchedTimelineColumn
|
||||
emptyMessageText={
|
||||
<FormattedMessage
|
||||
|
||||
@ -2,6 +2,7 @@ import { useMutation, useQuery, type UseQueryResult } from '@tanstack/react-quer
|
||||
|
||||
import { useClient } from '@/hooks/use-client';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useLoggedIn } from '@/hooks/use-logged-in';
|
||||
import { queryKeys } from '@/queries/keys';
|
||||
|
||||
import { queryClient } from '../client';
|
||||
@ -16,11 +17,12 @@ function useAntennas(): UseQueryResult<Array<Antenna>, Error>;
|
||||
function useAntennas<T = Array<Antenna>>(select?: (data: Array<Antenna>) => T) {
|
||||
const client = useClient();
|
||||
const features = useFeatures();
|
||||
const { isLoggedIn } = useLoggedIn();
|
||||
|
||||
return useQuery({
|
||||
queryKey: queryKeys.antennas.all,
|
||||
queryFn: () => client.antennas.fetchAntennas(),
|
||||
enabled: features.antennas,
|
||||
enabled: isLoggedIn && features.antennas,
|
||||
select,
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { type UseQueryResult, useMutation, useQuery } from '@tanstack/react-quer
|
||||
|
||||
import { useClient } from '@/hooks/use-client';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useLoggedIn } from '@/hooks/use-logged-in';
|
||||
import { queryKeys } from '@/queries/keys';
|
||||
|
||||
import { queryClient } from '../client';
|
||||
@ -16,11 +17,12 @@ function useCircles(): UseQueryResult<Array<Circle>, Error>;
|
||||
function useCircles<T = Array<Circle>>(select?: (data: Array<Circle>) => T) {
|
||||
const client = useClient();
|
||||
const features = useFeatures();
|
||||
const { isLoggedIn } = useLoggedIn();
|
||||
|
||||
return useQuery({
|
||||
queryKey: queryKeys.circles.all,
|
||||
queryFn: () => client.circles.fetchCircles(),
|
||||
enabled: features.circles,
|
||||
enabled: isLoggedIn && features.circles,
|
||||
select,
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { useMutation, useQuery, type UseQueryResult } from '@tanstack/react-quer
|
||||
|
||||
import { useClient } from '@/hooks/use-client';
|
||||
import { useFeatures } from '@/hooks/use-features';
|
||||
import { useLoggedIn } from '@/hooks/use-logged-in';
|
||||
import { queryKeys } from '@/queries/keys';
|
||||
|
||||
import { queryClient } from '../client';
|
||||
@ -16,11 +17,12 @@ function useLists(): UseQueryResult<Array<List>, Error>;
|
||||
function useLists<T = Array<List>>(select?: (data: Array<List>) => T) {
|
||||
const client = useClient();
|
||||
const features = useFeatures();
|
||||
const { isLoggedIn } = useLoggedIn();
|
||||
|
||||
return useQuery({
|
||||
queryKey: queryKeys.lists.all,
|
||||
queryFn: () => client.lists.getLists(),
|
||||
enabled: features.lists,
|
||||
enabled: isLoggedIn && features.lists,
|
||||
select,
|
||||
});
|
||||
}
|
||||
|
||||
@ -158,3 +158,29 @@
|
||||
.⁂-load-more {
|
||||
@include mixins.button($theme: primary, $block: true);
|
||||
}
|
||||
|
||||
.⁂-timeline-picker {
|
||||
cursor: pointer;
|
||||
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
|
||||
margin: -0.25rem -0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
transition: background 150ms ease-in-out;
|
||||
|
||||
&:hover, &:focus-within, &[aria-expanded="true"] {
|
||||
background: rgb(var(--color-primary-100));
|
||||
|
||||
.dark & {
|
||||
background: rgb(var(--color-primary-800));
|
||||
}
|
||||
|
||||
.dark.black & {
|
||||
background: rgb(var(--color-gray-900));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user