Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ef2dab4b46 | |||
| 53c40a61ba |
2
.npmrc
2
.npmrc
@@ -1 +1 @@
|
||||
@add-ideas:registry=https://git.add-ideas.de/api/packages/zemion/npm/
|
||||
@add-ideas:registry=https://git.add-ideas.de/api/packages/lotobo/npm/
|
||||
|
||||
24
CHANGELOG.md
24
CHANGELOG.md
@@ -3,6 +3,30 @@
|
||||
All notable changes to the independently versioned Toolbox SDK packages are
|
||||
recorded here. The packages currently share one release version.
|
||||
|
||||
## 0.2.3 — 2026-07-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Moved the canonical repository, schema identifiers, examples, and source
|
||||
metadata to the `lotobo` (LocalToolBox) Gitea organization.
|
||||
- Moved publication of all three packages to the `lotobo` Gitea npm registry.
|
||||
|
||||
## 0.2.2 — 2026-07-23
|
||||
|
||||
### Added
|
||||
|
||||
- `ToolboxPersonalizePanel` for the portal-style personalization heading,
|
||||
storage warning, appearance picker, and portal extension content.
|
||||
|
||||
### Changed
|
||||
|
||||
- The invariant top row now fixes the Toolbox brand/home at the far left and
|
||||
Help, source, Apps, and Personalize at the far right in that order while the
|
||||
text identity remains geometrically centered.
|
||||
- Default app personalization now matches the central portal's panel styling;
|
||||
portal-only catalogue counts and import/export actions remain extensible
|
||||
content.
|
||||
|
||||
## 0.2.1 — 2026-07-23
|
||||
|
||||
### Added
|
||||
|
||||
20
README.md
20
README.md
@@ -5,7 +5,7 @@ tools. An app always works by itself. When it receives a trusted same-origin
|
||||
catalog URL, the same app gains a toolbox home link and an app switcher without
|
||||
becoming coupled to a portal or client-side router.
|
||||
|
||||
Version `0.2.1` contains three publish-ready packages:
|
||||
Version `0.2.3` contains three publish-ready packages:
|
||||
|
||||
- `@add-ideas/toolbox-contract` — types, strict v1 runtime parsing, context
|
||||
discovery/loading, resolved URLs, and contextual link helpers.
|
||||
@@ -36,7 +36,7 @@ Deploy `toolbox-app.json` beside the application entry. The canonical schema is
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
|
||||
"$schema": "https://git.add-ideas.de/lotobo/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
|
||||
"schemaVersion": 1,
|
||||
"id": "de.add-ideas.pdf-tools",
|
||||
"name": "PDF Workbench",
|
||||
@@ -63,7 +63,7 @@ Deploy `toolbox-app.json` beside the application entry. The canonical schema is
|
||||
"telemetry": false
|
||||
},
|
||||
"source": {
|
||||
"repository": "https://git.add-ideas.de/zemion/pdf-tools",
|
||||
"repository": "https://git.add-ideas.de/lotobo/pdf-tools",
|
||||
"license": "AGPL-3.0-only"
|
||||
}
|
||||
}
|
||||
@@ -167,11 +167,11 @@ import "@add-ideas/toolbox-shell-react/styles.css";
|
||||
</AppShell>;
|
||||
```
|
||||
|
||||
The shell's fixed top row renders Personalize, Apps, source, and Help controls
|
||||
in that order, a geometrically centered text identity, and the Toolbox
|
||||
brand/home at the far right. Personalize and Apps have labeled desktop controls
|
||||
and retain their accessible names when compact. The mutually exclusive popovers
|
||||
close on Escape, outside interaction, and link selection.
|
||||
The shell's fixed top row renders the Toolbox brand/home at the far left, a
|
||||
geometrically centered text identity, and Help, source, Apps, and Personalize
|
||||
controls at the far right in that order. Personalize and Apps have labeled
|
||||
desktop controls and retain their accessible names when compact. The mutually
|
||||
exclusive popovers close on Escape, outside interaction, and link selection.
|
||||
|
||||
`AppShell` moves version, derived privacy facts, manifest actions, and legacy
|
||||
`appActions` into its standard footer, keeping the top row stable in connected
|
||||
@@ -183,7 +183,9 @@ optional observability.
|
||||
|
||||
For a custom portal-style header, `ToolboxHeader` accepts `personalizeContent`
|
||||
for content hosted inside the shared controlled popover and `brandIconUrl` for
|
||||
the Toolbox mark. `onPersonalize` remains available for callback-only
|
||||
the Toolbox mark. `ToolboxPersonalizePanel` provides the shared portal-style
|
||||
heading, storage warning, and appearance picker, with a child slot for
|
||||
portal-specific controls. `onPersonalize` remains available for callback-only
|
||||
integrations. The deprecated `iconUrl`, `leadingActions`, and `metadata` props
|
||||
are retained for source compatibility; the centered app icon is intentionally no
|
||||
longer rendered.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
|
||||
"$schema": "https://git.add-ideas.de/lotobo/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
|
||||
"schemaVersion": 1,
|
||||
"id": "de.add-ideas.example-tool",
|
||||
"name": "Example Tool",
|
||||
@@ -26,7 +26,7 @@
|
||||
"telemetry": false
|
||||
},
|
||||
"source": {
|
||||
"repository": "https://git.add-ideas.de/zemion/toolbox-sdk",
|
||||
"repository": "https://git.add-ideas.de/lotobo/toolbox-sdk",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-catalog.v1.schema.json",
|
||||
"$schema": "https://git.add-ideas.de/lotobo/toolbox-sdk/raw/branch/main/schemas/toolbox-catalog.v1.schema.json",
|
||||
"schemaVersion": 1,
|
||||
"id": "de.add-ideas.example-toolbox",
|
||||
"name": "Example Toolbox",
|
||||
|
||||
@@ -27,7 +27,7 @@ export const toolboxApp = defineToolboxApp({
|
||||
telemetry: false,
|
||||
},
|
||||
source: {
|
||||
repository: "https://git.add-ideas.de/zemion/toolbox-sdk",
|
||||
repository: "https://git.add-ideas.de/lotobo/toolbox-sdk",
|
||||
license: "Apache-2.0",
|
||||
},
|
||||
});
|
||||
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@add-ideas/toolbox-sdk-workspace",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@add-ideas/toolbox-sdk-workspace",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"license": "Apache-2.0",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
@@ -3946,7 +3946,7 @@
|
||||
},
|
||||
"packages/contract": {
|
||||
"name": "@add-ideas/toolbox-contract",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
@@ -3954,10 +3954,10 @@
|
||||
},
|
||||
"packages/shell-react": {
|
||||
"name": "@add-ideas/toolbox-shell-react",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@add-ideas/toolbox-contract": "0.2.1"
|
||||
"@add-ideas/toolbox-contract": "0.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18 <20",
|
||||
@@ -3966,10 +3966,10 @@
|
||||
},
|
||||
"packages/testkit": {
|
||||
"name": "@add-ideas/toolbox-testkit",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@add-ideas/toolbox-contract": "0.2.1"
|
||||
"@add-ideas/toolbox-contract": "0.2.3"
|
||||
},
|
||||
"bin": {
|
||||
"toolbox-check": "dist/cli.js"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@add-ideas/toolbox-sdk-workspace",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"private": true,
|
||||
"description": "A small, framework-neutral toolbox contract with a React application shell.",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.add-ideas.de/zemion/toolbox-sdk.git"
|
||||
"url": "git+https://git.add-ideas.de/lotobo/toolbox-sdk.git"
|
||||
},
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
|
||||
@@ -17,7 +17,7 @@ The package also owns the versioned same-origin browser preference contract. Use
|
||||
`readToolboxPreferences()` and `writeToolboxPreferences()` to share pinned apps,
|
||||
ordering, visibility, and light/dark/system mode with the Toolbox portal.
|
||||
|
||||
See the workspace [README](https://git.add-ideas.de/zemion/toolbox-sdk#readme)
|
||||
See the workspace [README](https://git.add-ideas.de/lotobo/toolbox-sdk#readme)
|
||||
for the v1 document formats and full API.
|
||||
|
||||
The canonical schemas are also published at
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@add-ideas/toolbox-contract",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"description": "Runtime-validated manifests, catalogs, discovery, and URL helpers for toolbox applications.",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.add-ideas.de/zemion/toolbox-sdk.git",
|
||||
"url": "git+https://git.add-ideas.de/lotobo/toolbox-sdk.git",
|
||||
"directory": "packages/contract"
|
||||
},
|
||||
"homepage": "https://git.add-ideas.de/zemion/toolbox-sdk",
|
||||
"homepage": "https://git.add-ideas.de/lotobo/toolbox-sdk",
|
||||
"keywords": [
|
||||
"toolbox",
|
||||
"manifest",
|
||||
@@ -43,6 +43,6 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://git.add-ideas.de/api/packages/zemion/npm/"
|
||||
"registry": "https://git.add-ideas.de/api/packages/lotobo/npm/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,17 @@ import "@add-ideas/toolbox-shell-react/styles.css";
|
||||
standalone mode if context is absent, unavailable, cross-origin, or invalid. Its
|
||||
app switcher is exposed as the accessible navigation landmark
|
||||
`Toolbox applications` and all destinations use ordinary document navigation.
|
||||
The fixed top row exposes Personalize, Apps, Gitea source, and Help in that
|
||||
order, a centered title/subtitle, and a Toolbox home/brand block. Personalize
|
||||
offers light/dark/system mode by default. Version, privacy, and application
|
||||
actions render in the standard footer so connected and standalone headers keep
|
||||
the same geometry.
|
||||
The fixed top row exposes a Toolbox home/brand block at the far left, a centered
|
||||
title/subtitle, and Help, Gitea source, Apps, and Personalize at the far right
|
||||
in that order. Personalize uses the portal-style panel and offers
|
||||
light/dark/system mode by default. Version, privacy, and application actions
|
||||
render in the standard footer so connected and standalone headers keep the same
|
||||
geometry.
|
||||
|
||||
`ToolboxHeader` is exported separately for custom shells. Its
|
||||
`personalizeContent` prop puts custom preferences inside the shared controlled
|
||||
popover, while `brandIconUrl` supplies a portal favicon; an inline Toolbox cube
|
||||
is the fallback. Personalize and Apps popovers are mutually exclusive and close
|
||||
on Escape, outside interaction, or link selection.
|
||||
is the fallback. `ToolboxPersonalizePanel` shares the portal heading, optional
|
||||
storage warning, and appearance picker with custom preference content.
|
||||
Personalize and Apps popovers are mutually exclusive and close on Escape,
|
||||
outside interaction, or link selection.
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@add-ideas/toolbox-shell-react",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"description": "A lightweight React application shell for toolbox-compatible browser applications.",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.add-ideas.de/zemion/toolbox-sdk.git",
|
||||
"url": "git+https://git.add-ideas.de/lotobo/toolbox-sdk.git",
|
||||
"directory": "packages/shell-react"
|
||||
},
|
||||
"homepage": "https://git.add-ideas.de/zemion/toolbox-sdk",
|
||||
"homepage": "https://git.add-ideas.de/lotobo/toolbox-sdk",
|
||||
"keywords": [
|
||||
"toolbox",
|
||||
"react",
|
||||
@@ -38,7 +38,7 @@
|
||||
"react-dom": ">=18 <20"
|
||||
},
|
||||
"dependencies": {
|
||||
"@add-ideas/toolbox-contract": "0.2.1"
|
||||
"@add-ideas/toolbox-contract": "0.2.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json && node -e \"const fs=require('node:fs');fs.copyFileSync('src/styles.css','dist/styles.css');fs.copyFileSync('../../LICENSE','LICENSE')\"",
|
||||
@@ -48,6 +48,6 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://git.add-ideas.de/api/packages/zemion/npm/"
|
||||
"registry": "https://git.add-ideas.de/api/packages/lotobo/npm/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,13 @@ export interface ToolboxHeaderProps {
|
||||
className?: string | undefined;
|
||||
}
|
||||
|
||||
export interface ToolboxPersonalizePanelProps {
|
||||
theme: ToolboxThemeMode;
|
||||
onThemeChange: (theme: ToolboxThemeMode) => void;
|
||||
storageAvailable?: boolean | undefined;
|
||||
children?: ReactNode | undefined;
|
||||
}
|
||||
|
||||
type OpenPopover = "personalize" | "apps" | null;
|
||||
|
||||
function HelpIcon() {
|
||||
@@ -117,6 +124,57 @@ function ToolboxBrandMark({ iconUrl }: { iconUrl?: string | undefined }) {
|
||||
|
||||
const THEMES: readonly ToolboxThemeMode[] = ["system", "light", "dark"];
|
||||
|
||||
export function ToolboxPersonalizePanel({
|
||||
theme,
|
||||
onThemeChange,
|
||||
storageAvailable,
|
||||
children,
|
||||
}: ToolboxPersonalizePanelProps) {
|
||||
const headingId = useId();
|
||||
|
||||
return (
|
||||
<section
|
||||
className="toolbox-shell__preferences-content"
|
||||
aria-labelledby={headingId}
|
||||
>
|
||||
<div className="toolbox-shell__preferences-heading">
|
||||
<p className="toolbox-shell__preferences-kicker">On this device</p>
|
||||
<h2 id={headingId}>Personalize your toolbox</h2>
|
||||
</div>
|
||||
|
||||
{storageAvailable === false ? (
|
||||
<p className="toolbox-shell__preferences-warning" role="status">
|
||||
Browser storage is unavailable. Changes will last only until this page
|
||||
closes.
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<fieldset className="toolbox-shell__theme-choice">
|
||||
<legend>Appearance</legend>
|
||||
<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>
|
||||
</fieldset>
|
||||
|
||||
{children ?? (
|
||||
<p className="toolbox-shell__preferences-copy">
|
||||
The Toolbox stores your appearance choice in this browser and does not
|
||||
transmit it.
|
||||
</p>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function ToolboxHeader({
|
||||
title,
|
||||
subtitle,
|
||||
@@ -219,11 +277,89 @@ export function ToolboxHeader({
|
||||
data-toolbox-theme={theme}
|
||||
>
|
||||
<div className="toolbox-shell__bar">
|
||||
{homeHref ? (
|
||||
<a
|
||||
className="toolbox-shell__brand"
|
||||
href={homeHref}
|
||||
aria-label={homeLabel}
|
||||
onClick={() => setOpenPopover(null)}
|
||||
>
|
||||
{brandContent}
|
||||
</a>
|
||||
) : (
|
||||
<div className="toolbox-shell__brand">{brandContent}</div>
|
||||
)}
|
||||
|
||||
<div className="toolbox-shell__identity">
|
||||
<h1 className="toolbox-shell__name">{title}</h1>
|
||||
{subtitle ? (
|
||||
<span className="toolbox-shell__description">{subtitle}</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="toolbox-shell__controls"
|
||||
role="group"
|
||||
aria-label="Toolbox controls"
|
||||
>
|
||||
<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>
|
||||
|
||||
{sourceHref ? (
|
||||
<a
|
||||
className="toolbox-shell__icon-button toolbox-shell__source"
|
||||
href={sourceHref}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={sourceLabel}
|
||||
title={sourceLabel}
|
||||
onClick={() => setOpenPopover(null)}
|
||||
>
|
||||
<GiteaIcon />
|
||||
</a>
|
||||
) : (
|
||||
<button
|
||||
className="toolbox-shell__icon-button"
|
||||
type="button"
|
||||
aria-label="Source unavailable"
|
||||
title="Source unavailable"
|
||||
disabled
|
||||
>
|
||||
<GiteaIcon />
|
||||
</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>
|
||||
|
||||
<button
|
||||
ref={personalizeButtonRef}
|
||||
className="toolbox-shell__menu-button"
|
||||
@@ -260,64 +396,6 @@ export function ToolboxHeader({
|
||||
<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"
|
||||
href={sourceHref}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label={sourceLabel}
|
||||
title={sourceLabel}
|
||||
onClick={() => setOpenPopover(null)}
|
||||
>
|
||||
<GiteaIcon />
|
||||
</a>
|
||||
) : (
|
||||
<button
|
||||
className="toolbox-shell__icon-button"
|
||||
type="button"
|
||||
aria-label="Source unavailable"
|
||||
title="Source unavailable"
|
||||
disabled
|
||||
>
|
||||
<GiteaIcon />
|
||||
</button>
|
||||
)}
|
||||
|
||||
<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}
|
||||
@@ -331,22 +409,10 @@ export function ToolboxHeader({
|
||||
{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>
|
||||
</>
|
||||
<ToolboxPersonalizePanel
|
||||
theme={theme}
|
||||
onThemeChange={(option) => onThemeChange?.(option)}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
) : null}
|
||||
@@ -387,26 +453,6 @@ export function ToolboxHeader({
|
||||
</nav>
|
||||
) : 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>
|
||||
|
||||
{leadingActions || metadata ? (
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
export { AppShell } from "./AppShell.js";
|
||||
export type { AppShellProps } from "./AppShell.js";
|
||||
export { ToolboxHeader } from "./ToolboxHeader.js";
|
||||
export { ToolboxHeader, ToolboxPersonalizePanel } from "./ToolboxHeader.js";
|
||||
export type {
|
||||
ToolboxHeaderAction,
|
||||
ToolboxHeaderApp,
|
||||
ToolboxHeaderProps,
|
||||
ToolboxPersonalizePanelProps,
|
||||
} from "./ToolboxHeader.js";
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
--toolbox-accent-soft: #e9edff;
|
||||
--toolbox-accent-contrast: #ffffff;
|
||||
--toolbox-focus: #63cdbc;
|
||||
--toolbox-danger: #b4233a;
|
||||
--toolbox-radius: 0.7rem;
|
||||
--toolbox-shadow: 0 10px 34px rgb(24 34 68 / 9%);
|
||||
}
|
||||
@@ -34,6 +35,7 @@
|
||||
--toolbox-accent-soft: #252f55;
|
||||
--toolbox-accent-contrast: #11182a;
|
||||
--toolbox-focus: #66cdbd;
|
||||
--toolbox-danger: #ff8fa3;
|
||||
--toolbox-shadow: 0 14px 42px rgb(0 0 0 / 24%);
|
||||
}
|
||||
|
||||
@@ -51,6 +53,7 @@
|
||||
--toolbox-accent-soft: #252f55;
|
||||
--toolbox-accent-contrast: #11182a;
|
||||
--toolbox-focus: #66cdbd;
|
||||
--toolbox-danger: #ff8fa3;
|
||||
--toolbox-shadow: 0 14px 42px rgb(0 0 0 / 24%);
|
||||
}
|
||||
}
|
||||
@@ -106,7 +109,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
justify-self: start;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.toolbox-shell__header a {
|
||||
@@ -233,7 +236,7 @@
|
||||
min-width: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-self: end;
|
||||
justify-self: start;
|
||||
gap: 0.58rem;
|
||||
padding: 0.28rem 0.35rem;
|
||||
border-radius: var(--toolbox-radius);
|
||||
@@ -278,7 +281,8 @@ a.toolbox-shell__brand:hover {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: calc(100% + 0.7rem);
|
||||
left: 0;
|
||||
right: 0;
|
||||
left: auto;
|
||||
width: max-content;
|
||||
min-width: 13rem;
|
||||
max-width: min(22rem, calc(100vw - 1.5rem));
|
||||
@@ -292,7 +296,12 @@ a.toolbox-shell__brand:hover {
|
||||
color: var(--toolbox-text);
|
||||
}
|
||||
|
||||
.toolbox-shell__popover strong {
|
||||
.toolbox-shell__personalize-popover {
|
||||
width: min(26rem, calc(100vw - 1.5rem));
|
||||
max-width: min(26rem, calc(100vw - 1.5rem));
|
||||
}
|
||||
|
||||
.toolbox-shell__popover > strong {
|
||||
display: block;
|
||||
padding: 0.35rem 0.4rem 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
@@ -300,8 +309,8 @@ a.toolbox-shell__brand:hover {
|
||||
letter-spacing: 0.08em;
|
||||
}
|
||||
|
||||
.toolbox-shell__popover small,
|
||||
.toolbox-shell__popover p {
|
||||
.toolbox-shell__popover > small,
|
||||
.toolbox-shell__switcher > p {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0.55rem 0.4rem 0.25rem;
|
||||
@@ -310,17 +319,59 @@ a.toolbox-shell__brand:hover {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.toolbox-shell__preferences-content {
|
||||
padding: 0.35rem;
|
||||
color: var(--toolbox-text);
|
||||
}
|
||||
|
||||
.toolbox-shell__preferences-heading {
|
||||
margin-bottom: 0.9rem;
|
||||
}
|
||||
|
||||
.toolbox-shell__preferences-content h2 {
|
||||
margin: 0;
|
||||
color: var(--toolbox-text);
|
||||
font-family: var(--toolbox-font);
|
||||
font-size: 1rem;
|
||||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.toolbox-shell__preferences-content .toolbox-shell__preferences-kicker {
|
||||
margin: 0 0 0.3rem;
|
||||
padding: 0;
|
||||
color: var(--toolbox-accent);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.1em;
|
||||
line-height: 1.4;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.toolbox-shell__theme-choice {
|
||||
margin: 1rem 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.toolbox-shell__theme-choice legend {
|
||||
margin-bottom: 0.6rem;
|
||||
color: var(--toolbox-muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.toolbox-shell__theme-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.25rem;
|
||||
padding: 0.25rem;
|
||||
border-radius: 0.65rem;
|
||||
gap: 0.45rem;
|
||||
padding: 0.35rem;
|
||||
border-radius: 0.75rem;
|
||||
background: var(--toolbox-surface-soft);
|
||||
}
|
||||
|
||||
.toolbox-shell__theme-options button {
|
||||
padding: 0.5rem 0.45rem;
|
||||
padding: 0.62rem;
|
||||
border: 0;
|
||||
border-radius: 0.5rem;
|
||||
background: transparent;
|
||||
@@ -333,8 +384,30 @@ a.toolbox-shell__brand:hover {
|
||||
.toolbox-shell__theme-options button[aria-pressed="true"] {
|
||||
background: var(--toolbox-surface);
|
||||
color: var(--toolbox-accent);
|
||||
box-shadow: 0 1px 5px rgb(24 34 68 / 12%);
|
||||
font-weight: 750;
|
||||
box-shadow: 0 2px 8px rgb(20 30 60 / 9%);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.toolbox-shell__preferences-content .toolbox-shell__preferences-copy {
|
||||
margin: 0.9rem 0 0;
|
||||
padding: 0;
|
||||
color: var(--toolbox-muted);
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.toolbox-shell__preferences-content .toolbox-shell__preferences-warning {
|
||||
margin: 0 0 0.75rem;
|
||||
padding: 0.7rem 0.8rem;
|
||||
border-radius: 0.6rem;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--toolbox-danger) 8%,
|
||||
var(--toolbox-surface)
|
||||
);
|
||||
color: var(--toolbox-danger);
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.toolbox-shell__switcher ul {
|
||||
|
||||
@@ -11,7 +11,11 @@ import {
|
||||
} from "@add-ideas/toolbox-contract";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
import { AppShell, ToolboxHeader } from "../src/index.js";
|
||||
import {
|
||||
AppShell,
|
||||
ToolboxHeader,
|
||||
ToolboxPersonalizePanel,
|
||||
} from "../src/index.js";
|
||||
|
||||
const currentApp: ToolboxAppManifest = {
|
||||
schemaVersion: 1,
|
||||
@@ -124,10 +128,20 @@ describe("AppShell", () => {
|
||||
element.getAttribute("aria-label"),
|
||||
),
|
||||
).toEqual([
|
||||
"Personalize",
|
||||
"Apps",
|
||||
"Source for PDF Workbench on Gitea",
|
||||
"Help",
|
||||
"Source for PDF Workbench on Gitea",
|
||||
"Apps",
|
||||
"Personalize",
|
||||
]);
|
||||
expect(
|
||||
Array.from(
|
||||
document.querySelector(".toolbox-shell__bar")?.children ?? [],
|
||||
(element) => element.className,
|
||||
),
|
||||
).toEqual([
|
||||
"toolbox-shell__brand",
|
||||
"toolbox-shell__identity",
|
||||
"toolbox-shell__controls",
|
||||
]);
|
||||
fireEvent.click(screen.getByRole("button", { name: "Apps" }));
|
||||
expect(
|
||||
@@ -278,6 +292,21 @@ describe("AppShell", () => {
|
||||
const shell = document.querySelector(".toolbox-shell");
|
||||
expect(shell).toHaveAttribute("data-toolbox-theme", "dark");
|
||||
fireEvent.click(screen.getByRole("button", { name: "Personalize" }));
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Personalize your toolbox" }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByText("On this device")).toBeInTheDocument();
|
||||
expect(
|
||||
screen.getByRole("group", { name: "Appearance" }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("button", { name: "Dark" })).toHaveAttribute(
|
||||
"aria-pressed",
|
||||
"true",
|
||||
);
|
||||
expect(screen.queryByText("Pinned")).not.toBeInTheDocument();
|
||||
expect(
|
||||
screen.queryByRole("button", { name: "Export JSON" }),
|
||||
).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Light" }));
|
||||
expect(shell).toHaveAttribute("data-toolbox-theme", "light");
|
||||
expect(
|
||||
@@ -285,6 +314,29 @@ describe("AppShell", () => {
|
||||
).toMatchObject({ theme: "light" });
|
||||
});
|
||||
|
||||
it("shares the portal-style personalization frame and extension slot", () => {
|
||||
const onThemeChange = vi.fn();
|
||||
render(
|
||||
<ToolboxPersonalizePanel
|
||||
theme="system"
|
||||
onThemeChange={onThemeChange}
|
||||
storageAvailable={false}
|
||||
>
|
||||
<p>Portal preference actions</p>
|
||||
</ToolboxPersonalizePanel>,
|
||||
);
|
||||
|
||||
expect(
|
||||
screen.getByRole("heading", { name: "Personalize your toolbox" }),
|
||||
).toBeInTheDocument();
|
||||
expect(screen.getByRole("status")).toHaveTextContent(
|
||||
"Browser storage is unavailable",
|
||||
);
|
||||
expect(screen.getByText("Portal preference actions")).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole("button", { name: "Dark" }));
|
||||
expect(onThemeChange).toHaveBeenCalledWith("dark");
|
||||
});
|
||||
|
||||
it("resolves manifest-relative header links without rendering an app icon", async () => {
|
||||
render(
|
||||
<AppShell
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@add-ideas/toolbox-testkit",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.3",
|
||||
"description": "Manifest, asset, and nested-deployment smoke checks for built toolbox applications.",
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://git.add-ideas.de/zemion/toolbox-sdk.git",
|
||||
"url": "git+https://git.add-ideas.de/lotobo/toolbox-sdk.git",
|
||||
"directory": "packages/testkit"
|
||||
},
|
||||
"homepage": "https://git.add-ideas.de/zemion/toolbox-sdk",
|
||||
"homepage": "https://git.add-ideas.de/lotobo/toolbox-sdk",
|
||||
"keywords": [
|
||||
"toolbox",
|
||||
"manifest",
|
||||
@@ -37,7 +37,7 @@
|
||||
},
|
||||
"types": "./dist/index.d.ts",
|
||||
"dependencies": {
|
||||
"@add-ideas/toolbox-contract": "0.2.1"
|
||||
"@add-ideas/toolbox-contract": "0.2.3"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -p tsconfig.build.json && node -e \"const fs=require('node:fs');fs.chmodSync('dist/cli.js',0o755);fs.copyFileSync('../../LICENSE','LICENSE')\"",
|
||||
@@ -47,6 +47,6 @@
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://git.add-ideas.de/api/packages/zemion/npm/"
|
||||
"registry": "https://git.add-ideas.de/api/packages/lotobo/npm/"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
|
||||
"$id": "https://git.add-ideas.de/lotobo/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json",
|
||||
"title": "Toolbox application manifest v1",
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-catalog.v1.schema.json",
|
||||
"$id": "https://git.add-ideas.de/lotobo/toolbox-sdk/raw/branch/main/schemas/toolbox-catalog.v1.schema.json",
|
||||
"title": "Toolbox catalog v1",
|
||||
"type": "object",
|
||||
"required": ["schemaVersion", "id", "name", "home", "theme", "apps"],
|
||||
|
||||
Reference in New Issue
Block a user