feat: add multiple way to show collections and links

This commit is contained in:
Sonny
2025-08-21 02:27:51 +02:00
parent 18fe979069
commit 4ef2b639b6
41 changed files with 785 additions and 164 deletions

View File

@@ -0,0 +1,66 @@
import { Combobox, Input, InputBase, useCombobox } from '@mantine/core';
import { ComboListItem } from '~/components/common/combo_list/combo_list_item';
export type ValueWithIcon = {
label: string;
value: string;
icon: React.ReactNode;
};
export function ComboList({
selectedValue,
values,
setValue,
}: {
selectedValue: string;
values: ValueWithIcon[];
setValue: (value: string) => void;
}) {
const combobox = useCombobox({
onDropdownClose: () => combobox.resetSelectedOption(),
});
const selectedOption = values.find((item) => item.value === selectedValue);
const options = values.map((item) => (
<Combobox.Option value={item.value} key={item.value}>
<ComboListItem emoji={item.icon} label={item.label} />
</Combobox.Option>
));
return (
<Combobox
store={combobox}
withinPortal={false}
onOptionSubmit={(val) => {
setValue(val as string);
combobox.closeDropdown();
}}
>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
onClick={() => combobox.toggleDropdown()}
rightSectionPointerEvents="none"
multiline
>
{selectedOption ? (
<ComboListItem
emoji={selectedOption.icon}
label={selectedOption.label}
/>
) : (
<Input.Placeholder>Pick value</Input.Placeholder>
)}
</InputBase>
</Combobox.Target>
<Combobox.Dropdown>
<Combobox.Options>{options}</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
}

View File

@@ -0,0 +1,16 @@
import { Group, Text } from '@mantine/core';
export const ComboListItem = ({
emoji,
label,
}: {
emoji: React.ReactNode;
label: string;
}) => (
<Group gap="xs" align="center">
{emoji}
<Text fz="sm" fw={500}>
{label}
</Text>
</Group>
);

View File

@@ -19,6 +19,8 @@ import { useEffect } from 'react';
import { UserDropdown } from '~/components/common/floating_navbar/user_dropdown';
import { ExternalLinkUnstyled } from '~/components/common/links/external_link_unstyled';
import { InternalLink } from '~/components/common/links/internal_link';
import { LocaleSwitcher } from '~/components/common/locale_switcher';
import { ThemeSwitcher } from '~/components/common/theme_switcher';
import { useAuth } from '~/hooks/use_auth';
import classes from './floating_navbar.module.css';
@@ -74,8 +76,8 @@ export function FloatingNavbar({ width }: FloatingNavbarProps) {
</Group>
<Group>
{!isMobile && <Group>{links}</Group>}
{isMobile && <Burger opened={opened} onClick={handler.toggle} />}
{auth.isAuthenticated && <UserDropdown />}
{isMobile && <Burger opened={opened} onClick={handler.toggle} />}
{!auth.isAuthenticated && (
<Button
variant="default"
@@ -101,6 +103,10 @@ export function FloatingNavbar({ width }: FloatingNavbarProps) {
<Flex direction="column" gap="md">
{links}
</Flex>
<Group mt="md">
<ThemeSwitcher />
<LocaleSwitcher />
</Group>
</Drawer>
</Box>
</>

View File

@@ -10,10 +10,6 @@
var(--mantine-color-dark-8)
);
}
@media (max-width: 768px) {
display: none;
}
}
.userActive {

View File

@@ -1,9 +1,11 @@
import { Avatar, Group, Menu, Text, UnstyledButton } from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import { modals } from '@mantine/modals';
import cx from 'clsx';
import { useTranslation } from 'react-i18next';
import { TbChevronDown, TbLogout, TbShield } from 'react-icons/tb';
import { TbChevronDown, TbLogout, TbSettings, TbShield } from 'react-icons/tb';
import { InternalLinkUnstyled } from '~/components/common/links/internal_link_unstyled';
import { UserPreferences } from '~/components/common/user_preferences/user_preferences';
import { useAuth } from '~/hooks/use_auth';
import classes from './user_dropdown.module.css';
@@ -12,6 +14,14 @@ export function UserDropdown() {
const [userMenuOpened, { open: openUserMenu, close: closeUserMenu }] =
useDisclosure(false);
const { t } = useTranslation();
const handlePreferencesModal = () => {
modals.open({
title: t('user-preferences'),
children: <UserPreferences />,
});
};
return (
<Menu
width={260}
@@ -49,12 +59,18 @@ export function UserDropdown() {
href="/admin"
color="red"
>
{t('common:manage_users')}
{t('common:manage-users')}
</Menu.Item>
</>
)}
<Menu.Label>{t('common:settings')}</Menu.Label>
<Menu.Item
leftSection={<TbSettings size={16} />}
onClick={handlePreferencesModal}
>
{t('common:preferences')}
</Menu.Item>
<Menu.Item
leftSection={<TbLogout size={16} />}
component={InternalLinkUnstyled}

View File

@@ -0,0 +1,43 @@
import { LinkListDisplay } from '#shared/types/index';
import { Fieldset, Stack, Text } from '@mantine/core';
import { useTranslation } from 'react-i18next';
import { ComboList } from '~/components/common/combo_list/combo_list';
import { CollectionListSelector } from '~/components/dashboard/collection/collection_list_selector';
import { useDisplayPreferences } from '~/hooks/use_display_preferences';
import { useIsMobile } from '~/hooks/use_is_mobile';
import { getLinkListDisplayOptions } from '~/lib/display_preferences';
export function UserPreferences() {
const { displayPreferences, handleUpdateDisplayPreferences } =
useDisplayPreferences();
const { t } = useTranslation();
const isMobile = useIsMobile();
return (
<Fieldset legend={t('preferences')}>
{isMobile && (
<Text size="xs" c="orange" mb="sm">
{t('preferences-description')}
</Text>
)}
<Stack>
<Text size="sm" c="dimmed">
{t('display-preferences.collection-list-display')}
</Text>
<CollectionListSelector />
<Text size="sm" c="dimmed">
{t('display-preferences.link-list-display')}
</Text>
<ComboList
selectedValue={displayPreferences.linkListDisplay}
values={getLinkListDisplayOptions()}
setValue={(value) =>
handleUpdateDisplayPreferences({
linkListDisplay: value as LinkListDisplay,
})
}
/>
</Stack>
</Fieldset>
);
}