links
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
const loginLogoSrc = new URL('../assets/logo.webp', import.meta.url).toString();
|
||||||
|
|
||||||
interface StatusBannerProps {
|
interface StatusBannerProps {
|
||||||
status?: { message: string; variant: string } | null;
|
status?: { message: string; variant: string } | null;
|
||||||
}
|
}
|
||||||
@@ -80,8 +82,21 @@ const LoginView: React.FC<LoginViewProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="login-screen">
|
<div className="login-screen">
|
||||||
<div className="login-card">
|
<div className="login-screen__inner">
|
||||||
|
<div className="login-screen__content">
|
||||||
|
<div className="login-screen__brand">
|
||||||
|
<img
|
||||||
|
src={loginLogoSrc}
|
||||||
|
alt="Papercrate logo"
|
||||||
|
width={72}
|
||||||
|
height={72}
|
||||||
|
decoding="async"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
<h1>Papercrate</h1>
|
<h1>Papercrate</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="login-card">
|
||||||
{hasTenantSelection ? (
|
{hasTenantSelection ? (
|
||||||
<div className="login-card__selection">
|
<div className="login-card__selection">
|
||||||
<p>Select a tenant to finish signing in.</p>
|
<p>Select a tenant to finish signing in.</p>
|
||||||
@@ -153,6 +168,8 @@ const LoginView: React.FC<LoginViewProps> = ({
|
|||||||
<StatusBanner status={status} />
|
<StatusBanner status={status} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import {
|
|||||||
DesktopIcon,
|
DesktopIcon,
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
LogoIcon,
|
LogoIcon,
|
||||||
|
GithubIcon,
|
||||||
|
MatrixIcon,
|
||||||
|
WorldIcon,
|
||||||
} from '../ui/icons';
|
} from '../ui/icons';
|
||||||
import PanelHeader from '../ui/PanelHeader';
|
import PanelHeader from '../ui/PanelHeader';
|
||||||
import useFloatingMenu from '../ui/useFloatingMenu';
|
import useFloatingMenu from '../ui/useFloatingMenu';
|
||||||
@@ -980,6 +983,38 @@ const Sidebar: React.FC<SidebarProps> = ({
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="sidebar__footer">
|
||||||
|
<a
|
||||||
|
className="sidebar__footer-link"
|
||||||
|
href="https://github.com/papercrate-dms/papercrate"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
title="Open Papercrate on GitHub"
|
||||||
|
>
|
||||||
|
<GithubIcon size={16} stroke={1.5} />
|
||||||
|
<span>GitHub</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="sidebar__footer-link"
|
||||||
|
href="https://matrix.to/#/#papercrate:matrix.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
title="Join the Papercrate Matrix room"
|
||||||
|
>
|
||||||
|
<MatrixIcon size={16} stroke={1.5} />
|
||||||
|
<span>Matrix</span>
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
className="sidebar__footer-link"
|
||||||
|
href="https://papercrate.org"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
title="Visit papercrate.org"
|
||||||
|
>
|
||||||
|
<WorldIcon size={16} stroke={1.5} />
|
||||||
|
<span>Website</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const SIDEBAR_COLLAPSE_STORAGE_KEY = 'papercrate_sidebar_collapsed';
|
|||||||
const SidebarContext = createContext<SidebarContextValue | null>(null);
|
const SidebarContext = createContext<SidebarContextValue | null>(null);
|
||||||
|
|
||||||
const THEME_STORAGE_KEY = 'papercrate_theme_settings';
|
const THEME_STORAGE_KEY = 'papercrate_theme_settings';
|
||||||
const DEFAULT_NEUTRAL_HUE = 145;
|
const DEFAULT_NEUTRAL_HUE = 35;
|
||||||
const DEFAULT_NEUTRAL_CHROMA = 0.1;
|
const DEFAULT_NEUTRAL_CHROMA = 0.1;
|
||||||
const DEFAULT_NEUTRAL_CONTRAST = 0.3;
|
const DEFAULT_NEUTRAL_CONTRAST = 0.3;
|
||||||
const DEFAULT_THEME_MODE = 'system';
|
const DEFAULT_THEME_MODE = 'system';
|
||||||
|
|||||||
@@ -1,14 +1,57 @@
|
|||||||
|
|
||||||
.login-screen {
|
.login-screen {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-screen__inner {
|
||||||
|
min-height: calc(100vh - 4rem);
|
||||||
|
min-height: calc(100dvh - 4rem);
|
||||||
|
max-width: 24rem;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-screen__content {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-screen__brand {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.65rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-screen__brand img {
|
||||||
|
width: 5.5rem;
|
||||||
|
height: 5.5rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-screen__brand h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 2.35rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
.login-card {
|
.login-card {
|
||||||
width: min(340px, 100%);
|
width: min(340px, 100%);
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
@@ -20,12 +63,6 @@
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card h1 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.6rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-card p {
|
.login-card p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: var(--muted);
|
color: var(--muted);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
:root {
|
:root {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
|
|
||||||
--neutral-hue: 145deg;
|
--neutral-hue: 35deg;
|
||||||
--surface-hue-shift: 6deg;
|
--surface-hue-shift: 6deg;
|
||||||
--foreground-hue-offset: 20deg;
|
--foreground-hue-offset: 20deg;
|
||||||
--accent-hue-offset: 0deg;
|
--accent-hue-offset: 0deg;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: var(--bg);
|
background: var(--surface);
|
||||||
box-shadow: 0 0 24px var(--shadow-soft);
|
box-shadow: 0 0 24px var(--shadow-soft);
|
||||||
border-left: 1px solid var(--border);
|
border-left: 1px solid var(--border);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -189,13 +189,45 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__footer {
|
.sidebar__footer {
|
||||||
margin-top: auto;
|
border-top: 1px solid var(--border-muted, var(--border));
|
||||||
padding: 0.75rem 1rem 1rem;
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
background: var(--surface-subtle);
|
background: var(--surface-subtle);
|
||||||
|
padding: 0.6rem 0.85rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
gap: 0.35rem;
|
||||||
gap: 0.75rem;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__footer-link {
|
||||||
|
display: inline-flex;
|
||||||
|
flex: 1 1 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.83rem;
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 0.2rem 0.35rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
transition:
|
||||||
|
color 0.12s ease,
|
||||||
|
background-color 0.12s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__footer-link:visited {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__footer-link:hover,
|
||||||
|
.sidebar__footer-link:focus-visible {
|
||||||
|
color: var(--sidebar-fg, var(--fg));
|
||||||
|
background-color: color-mix(in srgb, var(--surface-overlay) 20%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar__footer-link .icon {
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar__title {
|
.sidebar__title {
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ import {
|
|||||||
IconSortDescendingLetters,
|
IconSortDescendingLetters,
|
||||||
IconFileInfo,
|
IconFileInfo,
|
||||||
IconAlertTriangle,
|
IconAlertTriangle,
|
||||||
|
IconBrandGithub,
|
||||||
|
IconBrandMatrix,
|
||||||
|
IconWorld,
|
||||||
} from '@tabler/icons-react';
|
} from '@tabler/icons-react';
|
||||||
import FolderSvg from '../assets/folder.svg';
|
import FolderSvg from '../assets/folder.svg';
|
||||||
const logoWebp = new URL('../assets/logo.webp', import.meta.url).toString();
|
const logoWebp = new URL('../assets/logo.webp', import.meta.url).toString();
|
||||||
@@ -146,6 +149,33 @@ export const IconZoomInArea: TablerIconComponent = ({ className, size = '1em', s
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const GithubIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconBrandGithub
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const MatrixIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconBrandMatrix
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const WorldIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
|
<IconWorld
|
||||||
|
className={composeClassName('icon', className)}
|
||||||
|
size={size}
|
||||||
|
stroke={stroke}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
export const ViewListIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
export const ViewListIcon: TablerIconComponent = ({ className, size = '1em', stroke = 1.6, ...rest }) => (
|
||||||
<IconLayoutList
|
<IconLayoutList
|
||||||
className={composeClassName('icon', className)}
|
className={composeClassName('icon', className)}
|
||||||
|
|||||||
Reference in New Issue
Block a user