This commit is contained in:
2025-11-13 12:40:38 +01:00
parent f7251b8ce3
commit 98165f71b0
8 changed files with 177 additions and 26 deletions
+26 -9
View File
@@ -1,5 +1,7 @@
import React, { useEffect, useState } from 'react';
const loginLogoSrc = new URL('../assets/logo.webp', import.meta.url).toString();
interface StatusBannerProps {
status?: { message: string; variant: string } | null;
}
@@ -80,13 +82,26 @@ const LoginView: React.FC<LoginViewProps> = ({
return (
<div className="login-screen">
<div className="login-card">
<h1>Papercrate</h1>
{hasTenantSelection ? (
<div className="login-card__selection">
<p>Select a tenant to finish signing in.</p>
<div className="login-card__tenant-list">
{tenantSelection?.tenants?.map((tenant) => (
<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>
</div>
<div className="login-card">
{hasTenantSelection ? (
<div className="login-card__selection">
<p>Select a tenant to finish signing in.</p>
<div className="login-card__tenant-list">
{tenantSelection?.tenants?.map((tenant) => (
<button
key={tenant.id}
type="button"
@@ -149,10 +164,12 @@ const LoginView: React.FC<LoginViewProps> = ({
</button>
) : null}
</>
)}
<StatusBanner status={status} />
)}
<StatusBanner status={status} />
</div>
</div>
</div>
</div>
);
};
+35
View File
@@ -19,6 +19,9 @@ import {
DesktopIcon,
UploadIcon,
LogoIcon,
GithubIcon,
MatrixIcon,
WorldIcon,
} from '../ui/icons';
import PanelHeader from '../ui/PanelHeader';
import useFloatingMenu from '../ui/useFloatingMenu';
@@ -980,6 +983,38 @@ const Sidebar: React.FC<SidebarProps> = ({
</ul>
</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>
);
};
+1 -1
View File
@@ -32,7 +32,7 @@ const SIDEBAR_COLLAPSE_STORAGE_KEY = 'papercrate_sidebar_collapsed';
const SidebarContext = createContext<SidebarContextValue | null>(null);
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_CONTRAST = 0.3;
const DEFAULT_THEME_MODE = 'system';
+46 -9
View File
@@ -1,14 +1,57 @@
.login-screen {
min-height: 100vh;
min-height: 100dvh;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: var(--bg);
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 {
width: min(340px, 100%);
background: var(--surface);
@@ -20,12 +63,6 @@
gap: 1rem;
}
.login-card h1 {
margin: 0;
font-size: 1.6rem;
font-weight: 600;
}
.login-card p {
margin: 0;
color: var(--muted);
+1 -1
View File
@@ -1,7 +1,7 @@
:root {
color-scheme: light dark;
--neutral-hue: 145deg;
--neutral-hue: 35deg;
--surface-hue-shift: 6deg;
--foreground-hue-offset: 20deg;
--accent-hue-offset: 0deg;
+1 -1
View File
@@ -7,7 +7,7 @@
min-height: 100dvh;
height: 100%;
height: 100%;
background: var(--bg);
background: var(--surface);
box-shadow: 0 0 24px var(--shadow-soft);
border-left: 1px solid var(--border);
display: flex;
+37 -5
View File
@@ -189,13 +189,45 @@
}
.sidebar__footer {
margin-top: auto;
padding: 0.75rem 1rem 1rem;
border-top: 1px solid var(--border);
border-top: 1px solid var(--border-muted, var(--border));
background: var(--surface-subtle);
padding: 0.6rem 0.85rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
gap: 0.35rem;
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 {
+30
View File
@@ -48,6 +48,9 @@ import {
IconSortDescendingLetters,
IconFileInfo,
IconAlertTriangle,
IconBrandGithub,
IconBrandMatrix,
IconWorld,
} from '@tabler/icons-react';
import FolderSvg from '../assets/folder.svg';
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 }) => (
<IconLayoutList
className={composeClassName('icon', className)}