fix: stabilize Toolbox navigation layout
This commit is contained in:
@@ -227,7 +227,6 @@ export function AppShell({
|
||||
>
|
||||
<ToolboxHeader
|
||||
title={app.name}
|
||||
iconUrl={safeHref(app.icon, appManifestUrl)}
|
||||
subtitle={app.description}
|
||||
theme={theme}
|
||||
onThemeChange={setTheme}
|
||||
@@ -235,11 +234,19 @@ export function AppShell({
|
||||
sourceHref={sourceHref}
|
||||
sourceLabel={`Source for ${app.name} on Gitea`}
|
||||
homeHref={context?.catalog.homeUrl.href}
|
||||
homeLabel={context?.catalog.catalog.theme.brand}
|
||||
homeLabel="add·ideas Toolbox"
|
||||
brandIconUrl={
|
||||
context
|
||||
? safeHref("favicon.svg", context.catalog.homeUrl.href)
|
||||
: undefined
|
||||
}
|
||||
apps={switcherApps}
|
||||
allAppsHref={context?.catalog.homeUrl.href}
|
||||
leadingActions={
|
||||
otherActions || appActions ? (
|
||||
/>
|
||||
<main className="toolbox-shell__main">{children}</main>
|
||||
<footer className="toolbox-shell__footer">
|
||||
<div className="toolbox-shell__footer-inner">
|
||||
{otherActions?.length || appActions ? (
|
||||
<div className="toolbox-shell__actions">
|
||||
{otherActions?.map((action) => {
|
||||
const href = safeHref(action.url, appManifestUrl);
|
||||
@@ -251,10 +258,10 @@ export function AppShell({
|
||||
})}
|
||||
{appActions}
|
||||
</div>
|
||||
) : undefined
|
||||
}
|
||||
metadata={
|
||||
<>
|
||||
) : (
|
||||
<span aria-hidden="true" />
|
||||
)}
|
||||
<div className="toolbox-shell__footer-meta">
|
||||
<span aria-label={`Version ${app.version}`}>v{app.version}</span>
|
||||
<span aria-hidden="true">·</span>
|
||||
{privacyHref ? (
|
||||
@@ -262,10 +269,9 @@ export function AppShell({
|
||||
) : (
|
||||
<span>{privacyText(app)}</span>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<main className="toolbox-shell__main">{children}</main>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import type { ToolboxThemeMode } from "@add-ideas/toolbox-contract";
|
||||
import type { ReactNode } from "react";
|
||||
import {
|
||||
useEffect,
|
||||
useId,
|
||||
useRef,
|
||||
useState,
|
||||
type MouseEvent,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
|
||||
export interface ToolboxHeaderAction {
|
||||
label?: string | undefined;
|
||||
@@ -17,23 +24,30 @@ export interface ToolboxHeaderApp {
|
||||
|
||||
export interface ToolboxHeaderProps {
|
||||
title: string;
|
||||
/** @deprecated The fixed header centers text without an application icon. */
|
||||
iconUrl?: string | undefined;
|
||||
subtitle?: string | undefined;
|
||||
theme: ToolboxThemeMode;
|
||||
onThemeChange?: ((theme: ToolboxThemeMode) => void) | undefined;
|
||||
onPersonalize?: (() => void) | undefined;
|
||||
personalizeContent?: ReactNode | undefined;
|
||||
helpAction?: ToolboxHeaderAction | undefined;
|
||||
sourceHref?: string | undefined;
|
||||
sourceLabel?: string | undefined;
|
||||
homeHref?: string | undefined;
|
||||
homeLabel?: string | undefined;
|
||||
brandIconUrl?: string | undefined;
|
||||
apps?: readonly ToolboxHeaderApp[] | undefined;
|
||||
allAppsHref?: string | undefined;
|
||||
/** @deprecated Supplemental actions render outside the fixed top-bar row. */
|
||||
leadingActions?: ReactNode | undefined;
|
||||
/** @deprecated AppShell metadata now renders in its standard footer. */
|
||||
metadata?: ReactNode | undefined;
|
||||
className?: string | undefined;
|
||||
}
|
||||
|
||||
type OpenPopover = "personalize" | "apps" | null;
|
||||
|
||||
function HelpIcon() {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
@@ -79,49 +93,189 @@ function AppsIcon() {
|
||||
);
|
||||
}
|
||||
|
||||
function ToolboxMark() {
|
||||
return (
|
||||
<svg viewBox="0 0 48 48" aria-hidden="true">
|
||||
<path
|
||||
d="M7 14.5 24 5l17 9.5v19L24 43 7 33.5z"
|
||||
fill="currentColor"
|
||||
opacity=".12"
|
||||
stroke="none"
|
||||
/>
|
||||
<path d="m8 15 16 9 16-9M24 24v18" />
|
||||
<path d="m16 10.5 16 9v9L24 33l-8-4.5z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ToolboxBrandMark({ iconUrl }: { iconUrl?: string | undefined }) {
|
||||
const [failedIconUrl, setFailedIconUrl] = useState<string>();
|
||||
|
||||
if (!iconUrl || failedIconUrl === iconUrl) return <ToolboxMark />;
|
||||
return <img src={iconUrl} alt="" onError={() => setFailedIconUrl(iconUrl)} />;
|
||||
}
|
||||
|
||||
const THEMES: readonly ToolboxThemeMode[] = ["system", "light", "dark"];
|
||||
|
||||
export function ToolboxHeader({
|
||||
title,
|
||||
iconUrl,
|
||||
subtitle,
|
||||
theme,
|
||||
onThemeChange,
|
||||
onPersonalize,
|
||||
personalizeContent,
|
||||
helpAction,
|
||||
sourceHref,
|
||||
sourceLabel = "Source on Gitea",
|
||||
homeHref,
|
||||
homeLabel = "Toolbox",
|
||||
homeLabel = "add·ideas Toolbox",
|
||||
brandIconUrl,
|
||||
apps = [],
|
||||
allAppsHref,
|
||||
leadingActions,
|
||||
metadata,
|
||||
className,
|
||||
}: ToolboxHeaderProps) {
|
||||
const [openPopover, setOpenPopover] = useState<OpenPopover>(null);
|
||||
const personalizeButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const appsButtonRef = useRef<HTMLButtonElement | null>(null);
|
||||
const popoverRef = useRef<HTMLElement | null>(null);
|
||||
const personalizeId = useId();
|
||||
const appsId = useId();
|
||||
const customPersonalizeContent = personalizeContent != null;
|
||||
const hasPersonalizePopover =
|
||||
customPersonalizeContent ||
|
||||
(onPersonalize === undefined && onThemeChange !== undefined);
|
||||
const personalizeAvailable =
|
||||
hasPersonalizePopover || onPersonalize !== undefined;
|
||||
const visiblePopover =
|
||||
openPopover === "personalize" && !hasPersonalizePopover
|
||||
? null
|
||||
: openPopover;
|
||||
|
||||
useEffect(() => {
|
||||
if (visiblePopover === null) return;
|
||||
|
||||
const firstControl = popoverRef.current?.querySelector<HTMLElement>(
|
||||
'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])',
|
||||
);
|
||||
(firstControl ?? popoverRef.current)?.focus();
|
||||
|
||||
const closeFromOutside = (event: PointerEvent) => {
|
||||
const trigger =
|
||||
visiblePopover === "personalize"
|
||||
? personalizeButtonRef.current
|
||||
: appsButtonRef.current;
|
||||
if (!(event.target instanceof Node)) return;
|
||||
if (
|
||||
trigger?.contains(event.target) ||
|
||||
popoverRef.current?.contains(event.target)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
setOpenPopover(null);
|
||||
};
|
||||
const closeFromKeyboard = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape") return;
|
||||
event.preventDefault();
|
||||
const trigger =
|
||||
visiblePopover === "personalize"
|
||||
? personalizeButtonRef.current
|
||||
: appsButtonRef.current;
|
||||
setOpenPopover(null);
|
||||
trigger?.focus();
|
||||
};
|
||||
|
||||
document.addEventListener("pointerdown", closeFromOutside);
|
||||
document.addEventListener("keydown", closeFromKeyboard);
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", closeFromOutside);
|
||||
document.removeEventListener("keydown", closeFromKeyboard);
|
||||
};
|
||||
}, [visiblePopover]);
|
||||
|
||||
useEffect(() => {
|
||||
if (openPopover !== "personalize" || hasPersonalizePopover) return;
|
||||
const timeout = globalThis.setTimeout(() => setOpenPopover(null), 0);
|
||||
return () => globalThis.clearTimeout(timeout);
|
||||
}, [hasPersonalizePopover, openPopover]);
|
||||
|
||||
const closeOnLinkSelection = (event: MouseEvent<HTMLElement>) => {
|
||||
if ((event.target as Element).closest?.("a")) setOpenPopover(null);
|
||||
};
|
||||
|
||||
const brandContent = (
|
||||
<>
|
||||
<span className="toolbox-shell__brand-mark">
|
||||
<ToolboxBrandMark iconUrl={brandIconUrl} />
|
||||
</span>
|
||||
<span className="toolbox-shell__brand-label">{homeLabel}</span>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<header
|
||||
className={["toolbox-shell__header", className].filter(Boolean).join(" ")}
|
||||
data-toolbox-theme={theme}
|
||||
>
|
||||
<div className="toolbox-shell__bar">
|
||||
<div className="toolbox-shell__side toolbox-shell__side--left">
|
||||
{homeHref ? (
|
||||
<a className="toolbox-shell__home" href={homeHref}>
|
||||
{homeLabel}
|
||||
</a>
|
||||
) : null}
|
||||
{helpAction ? (
|
||||
<button
|
||||
className="toolbox-shell__icon-button"
|
||||
type="button"
|
||||
aria-label={helpAction.label ?? "Help"}
|
||||
title={helpAction.title ?? helpAction.label ?? "Help"}
|
||||
onClick={helpAction.onClick}
|
||||
>
|
||||
<HelpIcon />
|
||||
</button>
|
||||
) : null}
|
||||
<div
|
||||
className="toolbox-shell__controls"
|
||||
role="group"
|
||||
aria-label="Toolbox controls"
|
||||
>
|
||||
<button
|
||||
ref={personalizeButtonRef}
|
||||
className="toolbox-shell__menu-button"
|
||||
type="button"
|
||||
aria-label="Personalize"
|
||||
title={
|
||||
personalizeAvailable
|
||||
? "Personalize"
|
||||
: "Personalization unavailable"
|
||||
}
|
||||
aria-haspopup={hasPersonalizePopover ? "dialog" : undefined}
|
||||
aria-expanded={
|
||||
hasPersonalizePopover
|
||||
? visiblePopover === "personalize"
|
||||
: undefined
|
||||
}
|
||||
aria-controls={
|
||||
hasPersonalizePopover && visiblePopover === "personalize"
|
||||
? personalizeId
|
||||
: undefined
|
||||
}
|
||||
disabled={!personalizeAvailable}
|
||||
onClick={() => {
|
||||
if (hasPersonalizePopover) {
|
||||
setOpenPopover((current) =>
|
||||
current === "personalize" ? null : "personalize",
|
||||
);
|
||||
} else {
|
||||
onPersonalize?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<PersonalizeIcon />
|
||||
<span>Personalize</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
ref={appsButtonRef}
|
||||
className="toolbox-shell__menu-button"
|
||||
type="button"
|
||||
aria-label="Apps"
|
||||
title="Apps"
|
||||
aria-expanded={visiblePopover === "apps"}
|
||||
aria-controls={visiblePopover === "apps" ? appsId : undefined}
|
||||
onClick={() =>
|
||||
setOpenPopover((current) => (current === "apps" ? null : "apps"))
|
||||
}
|
||||
>
|
||||
<AppsIcon />
|
||||
<span>Apps</span>
|
||||
</button>
|
||||
|
||||
{sourceHref ? (
|
||||
<a
|
||||
className="toolbox-shell__icon-button toolbox-shell__source"
|
||||
@@ -130,74 +284,81 @@ export function ToolboxHeader({
|
||||
rel="noreferrer"
|
||||
aria-label={sourceLabel}
|
||||
title={sourceLabel}
|
||||
onClick={() => setOpenPopover(null)}
|
||||
>
|
||||
<GiteaIcon />
|
||||
</a>
|
||||
) : null}
|
||||
{leadingActions}
|
||||
</div>
|
||||
|
||||
<div className="toolbox-shell__identity">
|
||||
{iconUrl ? (
|
||||
<img
|
||||
className="toolbox-shell__icon"
|
||||
src={iconUrl}
|
||||
alt=""
|
||||
width="32"
|
||||
height="32"
|
||||
/>
|
||||
) : null}
|
||||
<div>
|
||||
<h1 className="toolbox-shell__name">{title}</h1>
|
||||
{subtitle ? (
|
||||
<span className="toolbox-shell__description">{subtitle}</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="toolbox-shell__side toolbox-shell__side--right">
|
||||
{onPersonalize ? (
|
||||
<button
|
||||
className="toolbox-shell__menu-button"
|
||||
type="button"
|
||||
onClick={onPersonalize}
|
||||
>
|
||||
<PersonalizeIcon />
|
||||
<span>Personalize</span>
|
||||
</button>
|
||||
) : (
|
||||
<details className="toolbox-shell__menu toolbox-shell__personalize">
|
||||
<summary className="toolbox-shell__menu-button">
|
||||
<PersonalizeIcon />
|
||||
<span>Personalize</span>
|
||||
</summary>
|
||||
<div className="toolbox-shell__popover">
|
||||
<strong>Appearance</strong>
|
||||
<div className="toolbox-shell__theme-options">
|
||||
{THEMES.map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
type="button"
|
||||
aria-pressed={theme === option}
|
||||
onClick={() => onThemeChange?.(option)}
|
||||
>
|
||||
{option.slice(0, 1).toUpperCase() + option.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<small>Saved only in this browser.</small>
|
||||
</div>
|
||||
</details>
|
||||
<button
|
||||
className="toolbox-shell__icon-button"
|
||||
type="button"
|
||||
aria-label="Source unavailable"
|
||||
title="Source unavailable"
|
||||
disabled
|
||||
>
|
||||
<GiteaIcon />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<details className="toolbox-shell__menu toolbox-shell__switcher">
|
||||
<summary className="toolbox-shell__menu-button">
|
||||
<AppsIcon />
|
||||
<span>Apps</span>
|
||||
</summary>
|
||||
<button
|
||||
className="toolbox-shell__icon-button"
|
||||
type="button"
|
||||
aria-label={helpAction?.label ?? "Help"}
|
||||
title={
|
||||
helpAction?.title ??
|
||||
helpAction?.label ??
|
||||
(helpAction ? "Help" : "Help unavailable")
|
||||
}
|
||||
disabled={!helpAction}
|
||||
onClick={() => {
|
||||
setOpenPopover(null);
|
||||
helpAction?.onClick();
|
||||
}}
|
||||
>
|
||||
<HelpIcon />
|
||||
</button>
|
||||
|
||||
{visiblePopover === "personalize" ? (
|
||||
<section
|
||||
ref={popoverRef}
|
||||
id={personalizeId}
|
||||
className="toolbox-shell__popover toolbox-shell__personalize-popover"
|
||||
role="dialog"
|
||||
aria-label="Personalize Toolbox"
|
||||
tabIndex={-1}
|
||||
onClick={closeOnLinkSelection}
|
||||
>
|
||||
{customPersonalizeContent ? (
|
||||
personalizeContent
|
||||
) : (
|
||||
<>
|
||||
<strong>Appearance</strong>
|
||||
<div className="toolbox-shell__theme-options">
|
||||
{THEMES.map((option) => (
|
||||
<button
|
||||
key={option}
|
||||
type="button"
|
||||
aria-pressed={theme === option}
|
||||
onClick={() => onThemeChange?.(option)}
|
||||
>
|
||||
{option.slice(0, 1).toUpperCase() + option.slice(1)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<small>Saved only in this browser.</small>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
|
||||
{visiblePopover === "apps" ? (
|
||||
<nav
|
||||
className="toolbox-shell__popover"
|
||||
ref={popoverRef}
|
||||
id={appsId}
|
||||
className="toolbox-shell__popover toolbox-shell__switcher"
|
||||
aria-label="Toolbox applications"
|
||||
tabIndex={-1}
|
||||
onClick={closeOnLinkSelection}
|
||||
>
|
||||
{allAppsHref ? (
|
||||
<a className="toolbox-shell__all-apps" href={allAppsHref}>
|
||||
@@ -224,10 +385,38 @@ export function ToolboxHeader({
|
||||
<p>Open this app from Toolbox to switch tools here.</p>
|
||||
)}
|
||||
</nav>
|
||||
</details>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="toolbox-shell__identity">
|
||||
<h1 className="toolbox-shell__name">{title}</h1>
|
||||
{subtitle ? (
|
||||
<span className="toolbox-shell__description">{subtitle}</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{homeHref ? (
|
||||
<a
|
||||
className="toolbox-shell__brand"
|
||||
href={homeHref}
|
||||
aria-label={homeLabel}
|
||||
onClick={() => setOpenPopover(null)}
|
||||
>
|
||||
{brandContent}
|
||||
</a>
|
||||
) : (
|
||||
<div className="toolbox-shell__brand">{brandContent}</div>
|
||||
)}
|
||||
</div>
|
||||
{metadata ? <div className="toolbox-shell__meta">{metadata}</div> : null}
|
||||
|
||||
{leadingActions || metadata ? (
|
||||
<div className="toolbox-shell__supplemental">
|
||||
{leadingActions}
|
||||
{metadata ? (
|
||||
<div className="toolbox-shell__supplemental-meta">{metadata}</div>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@
|
||||
|
||||
.toolbox-shell {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--toolbox-text);
|
||||
background: var(--toolbox-background);
|
||||
font-family: var(--toolbox-font);
|
||||
@@ -86,24 +88,25 @@
|
||||
|
||||
.toolbox-shell__bar {
|
||||
width: min(100%, 90rem);
|
||||
min-height: 4.35rem;
|
||||
height: 4.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
||||
grid-template-columns: minmax(18.75rem, 1fr) minmax(0, 31rem) minmax(
|
||||
18.75rem,
|
||||
1fr
|
||||
);
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin: 0 auto;
|
||||
padding: 0.65rem 1rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__side {
|
||||
.toolbox-shell__controls {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__side--right {
|
||||
justify-content: flex-end;
|
||||
gap: 0.4rem;
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.toolbox-shell__header a {
|
||||
@@ -115,70 +118,15 @@
|
||||
color: var(--toolbox-accent-hover);
|
||||
}
|
||||
|
||||
.toolbox-shell__home {
|
||||
max-width: 9rem;
|
||||
margin-right: 0.15rem;
|
||||
overflow: hidden;
|
||||
font-weight: 750;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbox-shell__identity {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.65rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.toolbox-shell__identity > div {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
gap: 0.08rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
flex: none;
|
||||
border-radius: 0.45rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.toolbox-shell__name {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
color: var(--toolbox-text);
|
||||
font-family: var(--toolbox-font);
|
||||
font-size: 1rem;
|
||||
font-weight: 750;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbox-shell__description {
|
||||
display: block;
|
||||
max-width: 27rem;
|
||||
overflow: hidden;
|
||||
color: var(--toolbox-muted);
|
||||
font-size: 0.73rem;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbox-shell__icon-button,
|
||||
.toolbox-shell__menu-button,
|
||||
.toolbox-shell__actions a {
|
||||
min-height: 2.35rem;
|
||||
.toolbox-shell__supplemental a {
|
||||
min-height: 2.5rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.45rem 0.65rem;
|
||||
padding: 0.45rem 0.68rem;
|
||||
border: 1px solid var(--toolbox-border);
|
||||
border-radius: var(--toolbox-radius);
|
||||
background: var(--toolbox-surface);
|
||||
@@ -192,14 +140,15 @@
|
||||
}
|
||||
|
||||
.toolbox-shell__icon-button {
|
||||
width: 2.35rem;
|
||||
width: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
padding: 0.45rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.toolbox-shell__icon-button:hover,
|
||||
.toolbox-shell__menu-button:hover,
|
||||
.toolbox-shell__actions a:hover {
|
||||
.toolbox-shell__supplemental a:hover {
|
||||
border-color: color-mix(
|
||||
in srgb,
|
||||
var(--toolbox-accent) 45%,
|
||||
@@ -210,10 +159,17 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.toolbox-shell__icon-button:disabled,
|
||||
.toolbox-shell__menu-button:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.48;
|
||||
}
|
||||
|
||||
.toolbox-shell__icon-button:focus-visible,
|
||||
.toolbox-shell__menu-button:focus-visible,
|
||||
.toolbox-shell__popover a:focus-visible,
|
||||
.toolbox-shell__popover button:focus-visible {
|
||||
.toolbox-shell__popover button:focus-visible,
|
||||
.toolbox-shell__brand:focus-visible {
|
||||
outline: 3px solid
|
||||
color-mix(in srgb, var(--toolbox-focus) 72%, var(--toolbox-accent));
|
||||
outline-offset: 2px;
|
||||
@@ -242,32 +198,92 @@
|
||||
color: #609926 !important;
|
||||
}
|
||||
|
||||
.toolbox-shell__actions {
|
||||
.toolbox-shell__identity {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
display: grid;
|
||||
gap: 0.12rem;
|
||||
justify-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.toolbox-shell__name {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
color: var(--toolbox-text);
|
||||
font-family: var(--toolbox-font);
|
||||
font-size: 1rem;
|
||||
font-weight: 750;
|
||||
line-height: 1.2;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbox-shell__description {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
color: var(--toolbox-muted);
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbox-shell__brand {
|
||||
min-width: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
justify-self: end;
|
||||
gap: 0.58rem;
|
||||
padding: 0.28rem 0.35rem;
|
||||
border-radius: var(--toolbox-radius);
|
||||
color: var(--toolbox-text) !important;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 750;
|
||||
line-height: 1.1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.toolbox-shell__menu {
|
||||
position: relative;
|
||||
a.toolbox-shell__brand:hover {
|
||||
background: var(--toolbox-accent-soft);
|
||||
color: var(--toolbox-accent) !important;
|
||||
}
|
||||
|
||||
.toolbox-shell__menu > summary {
|
||||
list-style: none;
|
||||
.toolbox-shell__brand-mark {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
display: inline-grid;
|
||||
flex: none;
|
||||
place-items: center;
|
||||
color: var(--toolbox-accent);
|
||||
}
|
||||
|
||||
.toolbox-shell__menu > summary::-webkit-details-marker {
|
||||
display: none;
|
||||
.toolbox-shell__brand-mark img,
|
||||
.toolbox-shell__brand-mark svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.toolbox-shell__brand-mark svg {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.toolbox-shell__popover {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.55rem);
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
top: calc(100% + 0.7rem);
|
||||
left: 0;
|
||||
width: max-content;
|
||||
min-width: 13rem;
|
||||
max-width: min(22rem, calc(100vw - 1.5rem));
|
||||
max-height: calc(100vh - 5.5rem);
|
||||
overflow: auto;
|
||||
padding: 0.55rem;
|
||||
border: 1px solid var(--toolbox-border);
|
||||
border-radius: 0.85rem;
|
||||
@@ -352,76 +368,157 @@
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.toolbox-shell__meta {
|
||||
min-height: 1.65rem;
|
||||
.toolbox-shell__supplemental {
|
||||
min-height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.45rem;
|
||||
padding: 0.25rem 1rem 0.45rem;
|
||||
gap: 0.65rem;
|
||||
padding: 0.3rem 1rem 0.45rem;
|
||||
color: var(--toolbox-muted);
|
||||
font-size: 0.7rem;
|
||||
text-align: center;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__supplemental-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__main {
|
||||
width: min(100%, 90rem);
|
||||
display: block;
|
||||
flex: 1 0 auto;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.toolbox-shell__bar {
|
||||
min-height: 3.8rem;
|
||||
gap: 0.5rem;
|
||||
padding-inline: 0.7rem;
|
||||
}
|
||||
.toolbox-shell__footer {
|
||||
width: min(100%, 90rem);
|
||||
margin: auto auto 0;
|
||||
padding: 0.35rem 1rem 1.25rem;
|
||||
color: var(--toolbox-muted);
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__home,
|
||||
.toolbox-shell__description,
|
||||
.toolbox-shell__menu-button > span {
|
||||
display: none;
|
||||
.toolbox-shell__footer-inner {
|
||||
min-height: 2.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem 1.25rem;
|
||||
padding-top: 0.7rem;
|
||||
border-top: 1px solid var(--toolbox-border);
|
||||
}
|
||||
|
||||
.toolbox-shell__footer-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 0.45rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.toolbox-shell__footer a {
|
||||
color: var(--toolbox-accent);
|
||||
}
|
||||
|
||||
@media (max-width: 72rem) {
|
||||
.toolbox-shell__bar {
|
||||
grid-template-columns:
|
||||
minmax(0, 1fr) minmax(0, min(31rem, calc(100% - 24rem)))
|
||||
minmax(0, 1fr);
|
||||
gap: 0.7rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__menu-button {
|
||||
width: 2.35rem;
|
||||
width: 2.5rem;
|
||||
min-width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
padding: 0.45rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.toolbox-shell__side {
|
||||
gap: 0.3rem;
|
||||
.toolbox-shell__menu-button > span,
|
||||
.toolbox-shell__description {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 40rem) {
|
||||
.toolbox-shell__bar {
|
||||
gap: 0.45rem;
|
||||
padding-inline: 0.65rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__identity {
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__icon {
|
||||
width: 1.7rem;
|
||||
height: 1.7rem;
|
||||
.toolbox-shell__controls {
|
||||
gap: 0.28rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__name {
|
||||
max-width: 36vw;
|
||||
font-size: 0.92rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__meta {
|
||||
display: none;
|
||||
.toolbox-shell__footer-inner {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.toolbox-shell__actions {
|
||||
.toolbox-shell__footer-meta {
|
||||
justify-content: flex-start;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 32rem) {
|
||||
.toolbox-shell__brand-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 27rem) {
|
||||
.toolbox-shell__source {
|
||||
display: none;
|
||||
.toolbox-shell__bar {
|
||||
height: 4.1rem;
|
||||
grid-template-columns:
|
||||
minmax(0, 1fr) minmax(0, calc(100vw - 17.9rem))
|
||||
minmax(0, 1fr);
|
||||
gap: 0.2rem;
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__controls {
|
||||
gap: 0.1rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__icon-button,
|
||||
.toolbox-shell__menu-button {
|
||||
width: 2rem;
|
||||
min-width: 2rem;
|
||||
height: 2rem;
|
||||
min-height: 2rem;
|
||||
padding: 0.3rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__brand {
|
||||
padding-inline: 0.1rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__brand-mark {
|
||||
width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__name {
|
||||
max-width: 30vw;
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__popover {
|
||||
max-width: calc(100vw - 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user