mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-11 08:43:04 +00:00
feat: add multiple way to show collections and links
This commit is contained in:
29
inertia/components/dashboard/collection/collection_list.tsx
Normal file
29
inertia/components/dashboard/collection/collection_list.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { ScrollArea, Stack, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CollectionFavoriteItem } from '~/components/dashboard/collection/item/collection_favorite_item';
|
||||
import { CollectionItem } from '~/components/dashboard/collection/item/collection_item';
|
||||
import { useCollections } from '~/hooks/collections/use_collections';
|
||||
import { useIsMobile } from '~/hooks/use_is_mobile';
|
||||
import styles from './list/collection_list.module.css';
|
||||
|
||||
export function CollectionList() {
|
||||
const { t } = useTranslation();
|
||||
const collections = useCollections();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
return (
|
||||
<Stack gap="xs" h="100%" w={isMobile ? '100%' : '350px'}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<Text c="dimmed" ml="md" mb="sm">
|
||||
{t('collection.collections')} • {collections.length}
|
||||
</Text>
|
||||
<ScrollArea className={styles.collectionList}>
|
||||
<CollectionFavoriteItem />
|
||||
{collections.map((collection) => (
|
||||
<CollectionItem collection={collection} />
|
||||
))}
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import { CollectionListDisplay } from '#shared/types/index';
|
||||
import { ComboList } from '~/components/common/combo_list/combo_list';
|
||||
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
|
||||
import { getCollectionListDisplayOptions } from '~/lib/display_preferences';
|
||||
|
||||
export function CollectionListSelector() {
|
||||
const { displayPreferences, handleUpdateDisplayPreferences } =
|
||||
useDisplayPreferences();
|
||||
return (
|
||||
<ComboList
|
||||
selectedValue={displayPreferences.collectionListDisplay}
|
||||
values={getCollectionListDisplayOptions()}
|
||||
setValue={(value) =>
|
||||
handleUpdateDisplayPreferences({
|
||||
collectionListDisplay: value as CollectionListDisplay,
|
||||
})
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { router } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { Chip, Group, Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import { useCollections } from '~/hooks/collections/use_collections';
|
||||
import { appendCollectionId } from '~/lib/navigation';
|
||||
|
||||
export function InlineCollectionList() {
|
||||
const { t } = useTranslation();
|
||||
const collections = useCollections();
|
||||
const activeCollection = useActiveCollection();
|
||||
|
||||
const handleCollectionChange = (value?: string) => {
|
||||
if (value) {
|
||||
router.visit(appendCollectionId(route('dashboard').path, Number(value)));
|
||||
return;
|
||||
}
|
||||
router.visit(route('dashboard').path);
|
||||
};
|
||||
|
||||
const fields = [
|
||||
{
|
||||
label: t('common:favorite'),
|
||||
value: 'favorite',
|
||||
},
|
||||
...collections.map((c) => ({
|
||||
label: (
|
||||
<Group gap="xs" wrap="nowrap">
|
||||
<>{c.name}</>
|
||||
<Text size="xs" c="dimmed">
|
||||
{c.links.length}
|
||||
</Text>
|
||||
</Group>
|
||||
),
|
||||
value: c.id.toString(),
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<Group gap="xs" w="100%">
|
||||
{fields.map((field) => (
|
||||
<Chip
|
||||
key={field.value}
|
||||
checked={
|
||||
activeCollection?.id
|
||||
? activeCollection.id === Number(field.value)
|
||||
: field.value === 'favorite'
|
||||
}
|
||||
variant="light"
|
||||
onClick={() => handleCollectionChange(field.value)}
|
||||
>
|
||||
{field.label}
|
||||
</Chip>
|
||||
))}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Link } from '@inertiajs/react';
|
||||
import { route } from '@izzyjs/route/client';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TbStar, TbStarFilled } from 'react-icons/tb';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import classes from './collection_item.module.css';
|
||||
|
||||
export function CollectionFavoriteItem() {
|
||||
const { t } = useTranslation();
|
||||
const activeCollection = useActiveCollection();
|
||||
const isActiveCollection = !activeCollection?.id;
|
||||
const FolderIcon = isActiveCollection ? TbStarFilled : TbStar;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={classes.link}
|
||||
data-active={isActiveCollection || undefined}
|
||||
href={route('dashboard').path}
|
||||
key="favorite"
|
||||
title="Favorite"
|
||||
>
|
||||
<FolderIcon className={classes.linkIcon} />
|
||||
<Text maw={'200px'} style={{ wordBreak: 'break-all' }}>
|
||||
{t('favorite')}
|
||||
</Text>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -8,11 +8,11 @@ import { appendCollectionId } from '~/lib/navigation';
|
||||
import { CollectionWithLinks } from '~/types/app';
|
||||
import classes from './collection_item.module.css';
|
||||
|
||||
export default function CollectionItem({
|
||||
collection,
|
||||
}: {
|
||||
interface CollectionItemProps {
|
||||
collection: CollectionWithLinks;
|
||||
}) {
|
||||
}
|
||||
|
||||
export function CollectionItem({ collection }: CollectionItemProps) {
|
||||
const itemRef = useRef<HTMLAnchorElement>(null);
|
||||
const activeCollection = useActiveCollection();
|
||||
const isActiveCollection = collection.id === activeCollection?.id;
|
||||
@@ -34,7 +34,10 @@ export default function CollectionItem({
|
||||
title={collection.name}
|
||||
>
|
||||
<FolderIcon className={classes.linkIcon} />
|
||||
<Text lineClamp={1} maw={'200px'} style={{ wordBreak: 'break-all' }}>
|
||||
<Text
|
||||
lineClamp={1}
|
||||
style={{ wordBreak: 'break-all', whiteSpace: 'pre-line' }}
|
||||
>
|
||||
{collection.name}
|
||||
</Text>
|
||||
</Link>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Button, Drawer, Portal, rem, Text } from '@mantine/core';
|
||||
import { useDisclosure, useHeadroom } from '@mantine/hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TbFolder } from 'react-icons/tb';
|
||||
import { CollectionFavoriteItem } from '~/components/dashboard/collection/item/collection_favorite_item';
|
||||
import { useCollections } from '~/hooks/collections/use_collections';
|
||||
import { CollectionItem } from './item/collection_item';
|
||||
|
||||
export function MobileCollectionList() {
|
||||
const { t } = useTranslation();
|
||||
const [opened, handler] = useDisclosure();
|
||||
const collections = useCollections();
|
||||
const pinned = useHeadroom({ fixedAt: 0 });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Drawer
|
||||
opened={opened}
|
||||
onClose={handler.close}
|
||||
title={t('collection.collections', { count: collections.length })}
|
||||
>
|
||||
<CollectionFavoriteItem />
|
||||
{collections.map((collection) => (
|
||||
<CollectionItem collection={collection} />
|
||||
))}
|
||||
</Drawer>
|
||||
<Portal>
|
||||
<Button
|
||||
onClick={handler.open}
|
||||
variant="outline"
|
||||
size="xs"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
left: '50%',
|
||||
bottom: pinned ? rem(16) : rem(-100),
|
||||
width: `calc(100% - ${rem(16)} * 2)`,
|
||||
backgroundColor: 'var(--mantine-color-body)',
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
transform: 'translateX(-50%)',
|
||||
}}
|
||||
>
|
||||
<TbFolder size={18} />
|
||||
<Text ml={4}>
|
||||
{t('collection.collections', { count: collections.length })}
|
||||
</Text>
|
||||
</Button>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Badge, CopyButton } from '@mantine/core';
|
||||
import { t } from 'i18next';
|
||||
import { TbCopy } from 'react-icons/tb';
|
||||
import { useActiveCollection } from '~/hooks/collections/use_active_collection';
|
||||
import { useAppUrl } from '~/hooks/use_app_url';
|
||||
|
||||
const COPY_TIMEOUT = 3_000;
|
||||
|
||||
export function SharedCollectionCopyLink() {
|
||||
const appUrl = useAppUrl();
|
||||
const activeCollection = useActiveCollection();
|
||||
|
||||
if (!activeCollection) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const copyUrl = `${appUrl}/shared/${activeCollection.id}`;
|
||||
return (
|
||||
<CopyButton value={copyUrl} timeout={COPY_TIMEOUT}>
|
||||
{({ copied, copy }) => (
|
||||
<Badge
|
||||
variant={copied ? 'filled' : 'light'}
|
||||
onClick={copy}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{copied ? t('success-copy') : t('visibility.public')}
|
||||
{!copied && <TbCopy style={{ marginLeft: 4 }} />}
|
||||
</Badge>
|
||||
)}
|
||||
</CopyButton>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user