mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-09 23:15:36 +00:00
feat: create new dashboard (layout, navbar, list of links)
This commit is contained in:
@@ -26,7 +26,7 @@ export default class CollectionsController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Create DTOs
|
// TODO: Create DTOs
|
||||||
return inertia.render('dashboard', {
|
return inertia.render('mantine_dashboard', {
|
||||||
collections: collections.map((collection) => collection.serialize()),
|
collections: collections.map((collection) => collection.serialize()),
|
||||||
activeCollection:
|
activeCollection:
|
||||||
activeCollection?.serialize() || collections[0].serialize(),
|
activeCollection?.serialize() || collections[0].serialize(),
|
||||||
|
|||||||
26
inertia/components/common/external_link_styled.tsx
Normal file
26
inertia/components/common/external_link_styled.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import { Anchor } from '@mantine/core';
|
||||||
|
import { AnchorHTMLAttributes, CSSProperties, ReactNode } from 'react';
|
||||||
|
|
||||||
|
export function ExternalLinkStyled({
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
...props
|
||||||
|
}: AnchorHTMLAttributes<HTMLAnchorElement> & {
|
||||||
|
children: ReactNode;
|
||||||
|
style?: CSSProperties;
|
||||||
|
title?: string;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Anchor<'a'>
|
||||||
|
component="a"
|
||||||
|
underline="never"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
title={title}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Anchor>
|
||||||
|
);
|
||||||
|
}
|
||||||
60
inertia/components/common/mantine_user_card.tsx
Normal file
60
inertia/components/common/mantine_user_card.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import { Avatar, Group, Menu, Text, UnstyledButton } from '@mantine/core';
|
||||||
|
import { forwardRef } from 'react';
|
||||||
|
import { TbChevronRight } from 'react-icons/tb';
|
||||||
|
import useUser from '~/hooks/use_user';
|
||||||
|
|
||||||
|
interface UserButtonProps extends React.ComponentPropsWithoutRef<'button'> {
|
||||||
|
image: string;
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserButton = forwardRef<HTMLButtonElement, UserButtonProps>(
|
||||||
|
({ image, name, email, icon, ...others }: UserButtonProps, ref) => (
|
||||||
|
<UnstyledButton
|
||||||
|
ref={ref}
|
||||||
|
style={{
|
||||||
|
color: 'var(--mantine-color-text)',
|
||||||
|
borderRadius: 'var(--mantine-radius-sm)',
|
||||||
|
}}
|
||||||
|
{...others}
|
||||||
|
>
|
||||||
|
<Group>
|
||||||
|
<Avatar src={image} radius="xl" />
|
||||||
|
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{name}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text c="dimmed" size="xs">
|
||||||
|
{email}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{icon || <TbChevronRight size="1rem" />}
|
||||||
|
</Group>
|
||||||
|
</UnstyledButton>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export function MantineUserCard() {
|
||||||
|
const { user, isAuthenticated } = useUser();
|
||||||
|
return (
|
||||||
|
isAuthenticated && (
|
||||||
|
<Menu withArrow>
|
||||||
|
<Menu.Target>
|
||||||
|
<UserButton
|
||||||
|
image={user.avatarUrl}
|
||||||
|
name={user.fullname}
|
||||||
|
email={user.email}
|
||||||
|
/>
|
||||||
|
</Menu.Target>
|
||||||
|
<Menu.Dropdown>
|
||||||
|
<Menu.Item>Logout</Menu.Item>
|
||||||
|
</Menu.Dropdown>
|
||||||
|
</Menu>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
44
inertia/components/dashboard/link/link.module.css
Normal file
44
inertia/components/dashboard/link/link.module.css
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
.linkWrapper {
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 100%;
|
||||||
|
background-color: light-dark(--mantine-color-gray-1, rgb(50, 58, 71));
|
||||||
|
padding: 0.75em 1em;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkWrapper:hover {
|
||||||
|
border: 1px solid var(--mantine-color-blue-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkHeader {
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkName {
|
||||||
|
width: 100%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkDescription {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkUrl {
|
||||||
|
width: 100%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.linkUrlPathname {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
26
inertia/components/dashboard/link/link_favicon.module.css
Normal file
26
inertia/components/dashboard/link/link_favicon.module.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
.favicon {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.faviconLoader {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background-color: var(--secondary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.faviconLoader > * {
|
||||||
|
animation: rotate 1s both reverse infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import styled from '@emotion/styled';
|
import { Center, Loader } from '@mantine/core';
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { TbLoader3 } from 'react-icons/tb';
|
|
||||||
import { TfiWorld } from 'react-icons/tfi';
|
import { TfiWorld } from 'react-icons/tfi';
|
||||||
import { rotate } from '~/styles/keyframes';
|
import styles from './link_favicon.module.css';
|
||||||
|
|
||||||
const IMG_LOAD_TIMEOUT = 7_500;
|
const IMG_LOAD_TIMEOUT = 7_500;
|
||||||
|
|
||||||
@@ -11,27 +10,6 @@ interface LinkFaviconProps {
|
|||||||
size?: number;
|
size?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Favicon = styled.div({
|
|
||||||
position: 'relative',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
});
|
|
||||||
|
|
||||||
const FaviconLoader = styled.div(({ theme }) => ({
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
color: theme.colors.font,
|
|
||||||
backgroundColor: theme.colors.secondary,
|
|
||||||
|
|
||||||
'& > *': {
|
|
||||||
animation: `${rotate} 1s both reverse infinite linear`,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
// The Favicon API should always return an image, so it's not really useful to keep the loader nor placeholder icon,
|
|
||||||
// but for slow connections and other random stuff, I'll keep this
|
|
||||||
export default function LinkFavicon({ url, size = 32 }: LinkFaviconProps) {
|
export default function LinkFavicon({ url, size = 32 }: LinkFaviconProps) {
|
||||||
const imgRef = useRef<HTMLImageElement>(null);
|
const imgRef = useRef<HTMLImageElement>(null);
|
||||||
|
|
||||||
@@ -47,7 +25,6 @@ export default function LinkFavicon({ url, size = 32 }: LinkFaviconProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Ugly hack, onLoad cb not triggered on first load when SSR
|
|
||||||
if (imgRef.current?.complete) {
|
if (imgRef.current?.complete) {
|
||||||
handleStopLoading();
|
handleStopLoading();
|
||||||
return;
|
return;
|
||||||
@@ -57,7 +34,7 @@ export default function LinkFavicon({ url, size = 32 }: LinkFaviconProps) {
|
|||||||
}, [isLoading]);
|
}, [isLoading]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Favicon>
|
<div className={styles.favicon}>
|
||||||
{!isFailed ? (
|
{!isFailed ? (
|
||||||
<img
|
<img
|
||||||
src={`/favicon?url=${url}`}
|
src={`/favicon?url=${url}`}
|
||||||
@@ -73,10 +50,13 @@ export default function LinkFavicon({ url, size = 32 }: LinkFaviconProps) {
|
|||||||
<TfiWorld size={size} />
|
<TfiWorld size={size} />
|
||||||
)}
|
)}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<FaviconLoader style={{ height: `${size}px`, width: `${size}px` }}>
|
<Center
|
||||||
<TbLoader3 size={size} />
|
className={styles.faviconLoader}
|
||||||
</FaviconLoader>
|
style={{ height: `${size}px`, width: `${size}px` }}
|
||||||
|
>
|
||||||
|
<Loader size="xs" />
|
||||||
|
</Center>
|
||||||
)}
|
)}
|
||||||
</Favicon>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,76 +1,9 @@
|
|||||||
import styled from '@emotion/styled';
|
import { Card, Group, Text } from '@mantine/core'; // Import de Mantine
|
||||||
import { AiFillStar } from 'react-icons/ai';
|
import { AiFillStar } from 'react-icons/ai';
|
||||||
import ExternalLink from '~/components/common/external_link';
|
import { ExternalLinkStyled } from '~/components/common/external_link_styled';
|
||||||
import LinkControls from '~/components/dashboard/link/link_controls';
|
|
||||||
import LinkFavicon from '~/components/dashboard/link/link_favicon';
|
import LinkFavicon from '~/components/dashboard/link/link_favicon';
|
||||||
import { Link } from '~/types/app';
|
import { Link } from '~/types/app';
|
||||||
|
import styles from './link.module.css';
|
||||||
const LinkWrapper = styled.li(({ theme }) => ({
|
|
||||||
userSelect: 'none',
|
|
||||||
cursor: 'pointer',
|
|
||||||
height: 'fit-content',
|
|
||||||
width: '100%',
|
|
||||||
color: theme.colors.primary,
|
|
||||||
backgroundColor: theme.colors.secondary,
|
|
||||||
padding: '0.75em 1em',
|
|
||||||
borderRadius: theme.border.radius,
|
|
||||||
|
|
||||||
'&:hover': {
|
|
||||||
outlineWidth: '1px',
|
|
||||||
outlineStyle: 'solid',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const LinkHeader = styled.div(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
gap: '1em',
|
|
||||||
alignItems: 'center',
|
|
||||||
|
|
||||||
'& > a': {
|
|
||||||
height: '100%',
|
|
||||||
maxWidth: 'calc(100% - 75px)', // TODO: fix this, it is ugly af :(
|
|
||||||
textDecoration: 'none',
|
|
||||||
display: 'flex',
|
|
||||||
flex: 1,
|
|
||||||
flexDirection: 'column',
|
|
||||||
transition: theme.transition.delay,
|
|
||||||
|
|
||||||
'&, &:hover': {
|
|
||||||
border: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
const LinkName = styled.div({
|
|
||||||
width: '100%',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
});
|
|
||||||
|
|
||||||
const LinkDescription = styled.div(({ theme }) => ({
|
|
||||||
marginTop: '0.5em',
|
|
||||||
color: theme.colors.font,
|
|
||||||
fontSize: '0.8em',
|
|
||||||
wordWrap: 'break-word',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const LinkUrl = styled.span(({ theme }) => ({
|
|
||||||
width: '100%',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
whiteSpace: 'nowrap',
|
|
||||||
overflow: 'hidden',
|
|
||||||
color: theme.colors.grey,
|
|
||||||
fontSize: '0.8em',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const StarIcon = styled(AiFillStar)(({ theme }) => ({
|
|
||||||
color: theme.colors.yellow,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const LinkUrlPathname = styled.span({
|
|
||||||
opacity: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function LinkItem({
|
export default function LinkItem({
|
||||||
link,
|
link,
|
||||||
@@ -79,21 +12,34 @@ export default function LinkItem({
|
|||||||
link: Link;
|
link: Link;
|
||||||
showUserControls: boolean;
|
showUserControls: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { id, name, url, description, favorite } = link;
|
const { name, url, description, favorite } = link;
|
||||||
return (
|
return (
|
||||||
<LinkWrapper key={id} title={name}>
|
<Card
|
||||||
<LinkHeader>
|
className={styles.linkWrapper}
|
||||||
|
padding="xs"
|
||||||
|
pl="md"
|
||||||
|
pr="md"
|
||||||
|
radius="sm"
|
||||||
|
>
|
||||||
|
<Group className={styles.linkHeader} justify="space-between">
|
||||||
<LinkFavicon url={url} />
|
<LinkFavicon url={url} />
|
||||||
<ExternalLink href={url} className="reset">
|
<ExternalLinkStyled href={url} style={{ flex: 1 }}>
|
||||||
<LinkName>
|
<div className={styles.linkName}>
|
||||||
{name} {showUserControls && favorite && <StarIcon />}
|
<Text c="blue" lineClamp={1}>
|
||||||
</LinkName>
|
{name}{' '}
|
||||||
|
{showUserControls && favorite && <AiFillStar color="gold" />}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
<LinkItemURL url={url} />
|
<LinkItemURL url={url} />
|
||||||
</ExternalLink>
|
</ExternalLinkStyled>
|
||||||
{showUserControls && <LinkControls link={link} />}
|
{/* {showUserControls && <LinkControls link={link} />} */}
|
||||||
</LinkHeader>
|
</Group>
|
||||||
{description && <LinkDescription>{description}</LinkDescription>}
|
{description && (
|
||||||
</LinkWrapper>
|
<Text c="dimmed" size="sm">
|
||||||
|
{description}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,13 +60,17 @@ function LinkItemURL({ url }: { url: Link['url'] }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinkUrl>
|
<Text className={styles.linkUrl} c="gray" size="xs" lineClamp={1}>
|
||||||
{origin}
|
{origin}
|
||||||
<LinkUrlPathname>{text}</LinkUrlPathname>
|
<span className={styles.linkUrlPathname}>{text}</span>
|
||||||
</LinkUrl>
|
</Text>
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error', error);
|
console.error('error', error);
|
||||||
return <LinkUrl>{url}</LinkUrl>;
|
return (
|
||||||
|
<Text className={styles.linkUrl} c="gray" size="xs" lineClamp={1}>
|
||||||
|
{url}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
inertia/mantine/components/dashboard/dashboard_navbar.tsx
Normal file
83
inertia/mantine/components/dashboard/dashboard_navbar.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { Link } from '@inertiajs/react';
|
||||||
|
import { route } from '@izzyjs/route/client';
|
||||||
|
import {
|
||||||
|
AppShell,
|
||||||
|
Burger,
|
||||||
|
Divider,
|
||||||
|
Group,
|
||||||
|
NavLink,
|
||||||
|
ScrollArea,
|
||||||
|
Skeleton,
|
||||||
|
Text,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { AiOutlineFolderAdd } from 'react-icons/ai';
|
||||||
|
import { IoIosSearch } from 'react-icons/io';
|
||||||
|
import { IoAdd, IoShieldHalfSharp } from 'react-icons/io5';
|
||||||
|
import { PiGearLight } from 'react-icons/pi';
|
||||||
|
import { MantineUserCard } from '~/components/common/mantine_user_card';
|
||||||
|
|
||||||
|
interface DashboardNavbarProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
toggle: () => void;
|
||||||
|
}
|
||||||
|
export function DashboardNavbar({ isOpen, toggle }: DashboardNavbarProps) {
|
||||||
|
const { t } = useTranslation('common');
|
||||||
|
const common = {
|
||||||
|
variant: 'subtle',
|
||||||
|
color: 'blue',
|
||||||
|
active: true,
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AppShell.Navbar p="md">
|
||||||
|
<Group hiddenFrom="sm" mb="md">
|
||||||
|
<Burger opened={isOpen} onClick={toggle} size="sm" />
|
||||||
|
<Text>Menu</Text>
|
||||||
|
</Group>
|
||||||
|
<MantineUserCard />
|
||||||
|
<Divider mt="xs" mb="md" />
|
||||||
|
<NavLink
|
||||||
|
{...common}
|
||||||
|
component={Link}
|
||||||
|
href={route('admin.dashboard').url}
|
||||||
|
label={t('admin')}
|
||||||
|
leftSection={<IoShieldHalfSharp size="1.5rem" />}
|
||||||
|
color="var(--mantine-color-red-5)"
|
||||||
|
/>
|
||||||
|
<NavLink
|
||||||
|
label={t('settings')}
|
||||||
|
leftSection={<PiGearLight size="1.5rem" />}
|
||||||
|
variant="subtle"
|
||||||
|
/>
|
||||||
|
<NavLink
|
||||||
|
{...common}
|
||||||
|
label={t('search')}
|
||||||
|
leftSection={<IoIosSearch size="1.5rem" />}
|
||||||
|
/>
|
||||||
|
<NavLink
|
||||||
|
{...common}
|
||||||
|
component={Link}
|
||||||
|
href={route('collection.create-form').url}
|
||||||
|
label={t('collection.create')}
|
||||||
|
leftSection={<IoAdd size="1.5rem" />}
|
||||||
|
/>
|
||||||
|
<NavLink
|
||||||
|
{...common}
|
||||||
|
component={Link}
|
||||||
|
href={route('link.create-form').url}
|
||||||
|
label={t('link.create')}
|
||||||
|
leftSection={<AiOutlineFolderAdd size="1.5rem" />}
|
||||||
|
/>
|
||||||
|
<Text size="sm" fw={500} c="dimmed" mt="sm" ml="md">
|
||||||
|
{t('favorite')} • {0}
|
||||||
|
</Text>
|
||||||
|
<ScrollArea>
|
||||||
|
{Array(15)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => (
|
||||||
|
<Skeleton key={index} h={28} mt="sm" animate={false} />
|
||||||
|
))}
|
||||||
|
</ScrollArea>
|
||||||
|
</AppShell.Navbar>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
inertia/pages/dashboard.module.css
Normal file
3
inertia/pages/dashboard.module.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.ml_bg_color {
|
||||||
|
background-color: light-dark(var(--ml-bg-light), var(--ml-bg-dark));
|
||||||
|
}
|
||||||
100
inertia/pages/mantine_dashboard.tsx
Normal file
100
inertia/pages/mantine_dashboard.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import {
|
||||||
|
AppShell,
|
||||||
|
Burger,
|
||||||
|
Group,
|
||||||
|
ScrollArea,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
} from '@mantine/core';
|
||||||
|
import { useDisclosure } from '@mantine/hooks';
|
||||||
|
import DashboardProviders from '~/components/dashboard/dashboard_provider';
|
||||||
|
import LinkItem from '~/components/dashboard/link/link_item';
|
||||||
|
import { DashboardNavbar } from '~/mantine/components/dashboard/dashboard_navbar';
|
||||||
|
import { MantineDashboardLayout } from '~/mantine/layouts/mantine_dashboard_layout';
|
||||||
|
import { CollectionWithLinks } from '~/types/app';
|
||||||
|
import '../styles/body_overflow_hidden.css';
|
||||||
|
import classes from './dashboard.module.css';
|
||||||
|
|
||||||
|
interface DashboardPageProps {
|
||||||
|
collections: CollectionWithLinks[];
|
||||||
|
activeCollection: CollectionWithLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MantineDashboard(props: Readonly<DashboardPageProps>) {
|
||||||
|
const [openedNavbar, { toggle: toggleNavbar }] = useDisclosure();
|
||||||
|
const [openedAside, { toggle: toggleAside }] = useDisclosure();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MantineDashboardLayout>
|
||||||
|
<DashboardProviders {...props}>
|
||||||
|
<AppShell
|
||||||
|
layout="alt"
|
||||||
|
header={{ height: 60 }}
|
||||||
|
footer={{ height: 60 }}
|
||||||
|
navbar={{
|
||||||
|
width: 300,
|
||||||
|
breakpoint: 'sm',
|
||||||
|
collapsed: { mobile: !openedNavbar },
|
||||||
|
}}
|
||||||
|
aside={{
|
||||||
|
width: 300,
|
||||||
|
breakpoint: 'md',
|
||||||
|
collapsed: { mobile: !openedAside },
|
||||||
|
}}
|
||||||
|
padding={0}
|
||||||
|
classNames={{
|
||||||
|
aside: classes.ml_bg_color,
|
||||||
|
footer: classes.ml_bg_color,
|
||||||
|
navbar: classes.ml_bg_color,
|
||||||
|
header: classes.ml_bg_color,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppShell.Header style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Group justify="space-between" px="md" flex={1}>
|
||||||
|
<Group h="100%">
|
||||||
|
<Burger
|
||||||
|
opened={openedNavbar}
|
||||||
|
onClick={toggleNavbar}
|
||||||
|
hiddenFrom="sm"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
Ma super collection
|
||||||
|
</Group>
|
||||||
|
<Burger
|
||||||
|
opened={openedAside}
|
||||||
|
onClick={toggleAside}
|
||||||
|
hiddenFrom="md"
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</AppShell.Header>
|
||||||
|
<DashboardNavbar isOpen={openedNavbar} toggle={toggleNavbar} />
|
||||||
|
<AppShell.Main>
|
||||||
|
<ScrollArea
|
||||||
|
h="calc(100vh - var(--app-shell-header-height, 0px) - var(--app-shell-footer-height, 0px))"
|
||||||
|
p="md"
|
||||||
|
>
|
||||||
|
<Stack gap="xs">
|
||||||
|
{props.activeCollection.links.map((link) => (
|
||||||
|
<LinkItem key={link.id} link={link} showUserControls />
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</ScrollArea>
|
||||||
|
</AppShell.Main>
|
||||||
|
<AppShell.Aside p="md">
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text>Aside</Text>
|
||||||
|
<Burger
|
||||||
|
opened={openedAside}
|
||||||
|
onClick={toggleAside}
|
||||||
|
hiddenFrom="md"
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
</AppShell.Aside>
|
||||||
|
<AppShell.Footer p="md">Footer</AppShell.Footer>
|
||||||
|
</AppShell>
|
||||||
|
</DashboardProviders>
|
||||||
|
</MantineDashboardLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
inertia/styles/body_overflow_hidden.css
Normal file
3
inertia/styles/body_overflow_hidden.css
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -1,40 +1,41 @@
|
|||||||
|
:root {
|
||||||
|
--ml-bg-light: rgb(240, 238, 246);
|
||||||
|
--ml-bg-dark: rgb(34, 40, 49);
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
min-height: 100svh;
|
min-height: 100svh;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: light-dark(var(--mantine-color-white), rgb(34, 40, 49));
|
background-color: light-dark(var(--ml-bg-light), var(--ml-bg-dark));
|
||||||
}
|
}
|
||||||
|
|
||||||
.__transition_fadeIn {
|
.__transition_fadeIn {
|
||||||
animation: fadeIn 0.25s ease;
|
animation: fadeIn 0.15s ease both;
|
||||||
opacity: 1;
|
|
||||||
scale: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.__transition_fadeOut {
|
.__transition_fadeOut {
|
||||||
animation: fadeOut 0.25s ease;
|
animation: fadeOut 0.15s ease both;
|
||||||
opacity: 0;
|
|
||||||
scale: 0.9;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeIn {
|
@keyframes fadeIn {
|
||||||
from {
|
from {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
scale: 0.9;
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
scale: 1;
|
transform: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes fadeOut {
|
@keyframes fadeOut {
|
||||||
from {
|
from {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
scale: 1;
|
transform: none;
|
||||||
}
|
}
|
||||||
to {
|
to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
scale: 0.9;
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user