Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions css/kit.css
Original file line number Diff line number Diff line change
Expand Up @@ -407,6 +407,113 @@ a:hover {
display: none !important;
}

/* -- Bottom bar -----------------------------------------------------------------
kit.ui.mountShell({ statusBar }) adds it after the frame, under the rail and
the sections, hidden until shown; while it shows, the frame takes the rest
of the window:

.app-frame.kit-with-statusbar the window less the bar
footer.kit-statusbar 26px, a line along its top
├── button.kit-statusbar-item an item, filled by the app
└── .kit-statusbar-panel[role=dialog] its panel, over the page above it,
├── button.kit-statusbar-close Close, at its top right
└── .kit-statusbar-panel-body filled by the app */
.app-frame.kit-with-statusbar {
height: calc(100vh - 26px);
}

.kit-statusbar {
display: flex;
align-items: center;
gap: 0.25rem;
height: 26px;
padding: 0 0.4rem;
border-top: 1px solid var(--bs-border-color);
background-color: var(--bs-tertiary-bg);
font-size: 0.75rem;
}

/* Borderless, washed in the accent when hovered and while its panel is open.
Its ring sits close, inside the bar's height. */
.kit-statusbar-item {
display: inline-flex;
align-items: center;
gap: 0.55rem;
height: 22px;
padding: 0 0.5rem;
border: 0;
border-radius: var(--radius-control);
background: none;
color: var(--bs-body-color);
font: inherit;
white-space: nowrap;
cursor: pointer;
transition: background-color var(--dur-micro) var(--ease-micro);
}

.kit-statusbar-item:hover,
.kit-statusbar-item[aria-expanded="true"] {
background-color: rgba(var(--accent-rgb), var(--wash-hover-fill));
}

.kit-statusbar-item:focus-visible {
outline-offset: 0;
}

.kit-statusbar-item .kit-card-status {
min-height: 0;
}

/* The panel: placed by the kit over its item, its width the app's (capped to
the window). It takes the focus as it opens, so the panel itself draws no
ring; its controls do. */
.kit-statusbar-panel {
position: fixed;
z-index: 1070;
padding: 0.8rem 0.9rem;
border: 1px solid var(--bs-border-color);
border-radius: var(--radius-card);
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.2);
}

.kit-statusbar-panel:focus {
outline: none;
}

.kit-statusbar-panel-body {
display: flex;
flex-direction: column;
gap: 0.65rem;
}

/* The app's first line leaves room for Close beside it. */
.kit-statusbar-panel-body > :first-child {
margin-inline-end: calc(18px + 0.5rem);
}

.kit-statusbar-close {
position: absolute;
top: 0.8rem;
right: 0.9rem;
display: inline-flex;
padding: 0;
border: 0;
border-radius: var(--radius-control);
background: none;
color: var(--bs-secondary-color);
cursor: pointer;
}

.kit-statusbar-close:hover {
color: var(--bs-body-color);
}

.kit-statusbar-close .material-icons-round {
font-size: 18px;
}

/* -- Nav rail --------------------------------------------------------------- */

.nav-rail {
Expand Down
100 changes: 94 additions & 6 deletions demo/src/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,94 @@ function fillControls() {
$("sample-steps").replaceWith(slider.element);
}

// -- The bottom bar ---------------------------------------------------------------
//
// One item, hidden until a test shows the bar (shell.statusBar.show(true)): how
// the Overview's checks went, with a panel listing each and Check Again.

/** The Overview's checks: each row's name, and the cell its result is in (the theme isn't one). */
const CHECKS = [
["Shared Bridge", "kit-api"],
["App Bridge", "electron-api"],
["Sandboxed", "sandboxed"],
["Page Library", "kit-js"],
["Kit Styles", "kit-css"],
];

/** A check that failed says so: "Missing", "No", or "Failed: …". */
const FAILED = /^(Missing|No|Failed)\b/;

/** Run the Overview's checks, each into its row. */
function runChecks() {
return Promise.all([
show("kit-api", async () => (window.kitAPI ? `App version ${await window.kitAPI.getVersion()}` : "Missing")),
show("electron-api", async () => (window.electronAPI ? `Electron ${await window.electronAPI.getElectronVersion()}` : "Missing")),
show("sandboxed", () => (window.electronAPI?.sandboxed ? "Yes" : "No")),
show("kit-js", () => (window.kit ? "Loaded" : "Missing")),
// A token only kit.css sets.
show("kit-css", () => (getComputedStyle(document.documentElement).getPropertyValue("--radius-card").trim() === "8px" ? "Loaded" : "Missing")),
]);
}

/** The bar's item and its panel, filled with the demo's own elements. */
function fillStatusBar(shell) {
const item = shell.statusBar.item("checks");
const barLine = window.kit.ui.statusLine({ text: "Checking…", tone: "running" });
const headLine = window.kit.ui.statusLine({ text: "Checking…", tone: "running" });
headLine.element.classList.add("demo-panel-head");
item.button.append(barLine.element);

const facts = document.createElement("dl");
facts.className = "demo-facts";
const again = document.createElement("button");
again.type = "button";
again.className = "btn btn-sm btn-outline-secondary";
again.textContent = "Check Again";
const overview = document.createElement("button");
overview.type = "button";
overview.className = "btn btn-sm btn-outline-secondary";
overview.textContent = "Show Overview";
const actions = document.createElement("div");
actions.className = "demo-panel-actions";
actions.append(again, overview);
item.panel.append(headLine.element, facts, actions);

/** Draw the item and its panel from the Overview's rows. */
function draw() {
const failed = CHECKS.filter(([, id]) => FAILED.test($(id).textContent)).length;
const state = failed === 0
? { text: "All Checks Passed", tone: "success", title: "Every check on the Overview passed." }
: { text: failed === 1 ? "1 Check Failed" : `${failed} Checks Failed`, tone: "danger", title: "The Overview says which." };
barLine.set(state);
headLine.set(state);
facts.replaceChildren(...CHECKS.flatMap(([name, id]) => {
const term = document.createElement("dt");
term.textContent = name;
const value = document.createElement("dd");
// A bridge that's there is "Present": its version changes with each update of the demo's Electron.
const result = $(id).textContent;
value.textContent = !FAILED.test(result) && id.endsWith("-api") ? "Present" : result;
return [term, value];
}));
}

again.addEventListener("click", () => {
const restore = window.kit.ui.busy(again, "Checking…");
barLine.set({ text: "Checking…", tone: "running" });
headLine.set({ text: "Checking…", tone: "running" });
// A moment, as a real check would take.
setTimeout(() => runChecks().then(() => {
restore();
draw();
}), 700);
});
overview.addEventListener("click", () => {
item.close();
shell.showView("overview");
});
return draw;
}

// The files the demo is opened with, kept for the tests (it shows none).
window.filesOpened = [];
window.kitAPI?.onFilesOpened((paths) => window.filesOpened.push(paths));
Expand All @@ -270,18 +358,18 @@ async function init() {
credits: { logo: "assets/logo.svg" },
// What the demo is busy with, asked before Restart Now: never anything, unless a test says so.
busy: () => window.demoBusy ?? null,
statusBar: { items: [{ id: "checks", label: "Checks", live: true, panel: { label: "Check Details", width: 360 } }] },
});
// For the tests, which show the bar.
window.demoShell = shell;
const drawChecks = fillStatusBar(shell);

await Promise.all([
shell.ready,
window.kitAPI.getSettings().then(applySettings),
show("kit-api", async () => (window.kitAPI ? `App version ${await window.kitAPI.getVersion()}` : "Missing")),
show("electron-api", async () => (window.electronAPI ? `Electron ${await window.electronAPI.getElectronVersion()}` : "Missing")),
show("sandboxed", () => (window.electronAPI?.sandboxed ? "Yes" : "No")),
show("kit-js", () => (window.kit ? "Loaded" : "Missing")),
// A token only kit.css sets.
show("kit-css", () => (getComputedStyle(document.documentElement).getPropertyValue("--radius-card").trim() === "8px" ? "Loaded" : "Missing")),
runChecks(),
]);
drawChecks();

$("open-isolated").addEventListener("click", () => window.electronAPI.openIsolatedWindow());
document.body.dataset.ready = "true";
Expand Down
37 changes: 37 additions & 0 deletions demo/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,40 @@
gap: 1rem;
max-width: 20rem;
}

/* The bar's panel: its head line, each check's result (a term and its value,
side by side), then its buttons on one line. */
.demo-panel-head {
font-size: 0.85rem;
font-weight: 500;
}

.demo-facts {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
gap: 0.35rem 0.85rem;
margin: 0;
font-size: 0.8rem;
}

.demo-facts dt {
font-weight: 400;
color: var(--bs-secondary-color);
}

.demo-facts dd {
margin: 0;
min-width: 0;
overflow-wrap: anywhere;
}

.demo-panel-actions {
display: flex;
gap: 0.4rem;
}

.demo-panel-actions .btn {
--bs-btn-padding-y: 0.15rem;
--bs-btn-padding-x: 0.55rem;
--bs-btn-font-size: 0.78rem;
}
4 changes: 2 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ and links here, so these can be corrected without a new release.
|---|---|
| [Getting Started](getting-started.md) | What the package exports, `start()` at the top of `main.js`, and the stylesheets and scripts a page loads |
| [IPC](ipc.md) | The shared channels behind `window.kitAPI`, links and the allowlist, and `kit.ipc.handle()` for an app's own, each answering the app's own page only |
| [The Shell, Settings and Credits](shell.md) | `kit.ui.mountShell()`: the nav rail, the header, and the Settings view with its Update and Credits tabs |
| [Page Components](page-components.md) | Toasts, prompts, the parts of a section of files (drop zone, progress, action bar), cards and busy buttons, the switch and the stepped slider, the key guard, `kit.format`, and the copy rules |
| [The Shell, Settings and Credits](shell.md) | `kit.ui.mountShell()`: the nav rail, the header, the bottom bar and its panels, and the Settings view with its Update and Credits tabs |
| [Page Components](page-components.md) | Toasts, prompts, the parts of a section of files (drop zone, progress, action bar), cards, the status line and busy buttons, the switch and the stepped slider, the key guard, `kit.format`, and the copy rules |
| [The Main Process](main-process.md) | The main window, isolated sessions, spell checking, one instance and the files an app is opened with, the settings and their migration, the redacted log, and the menu |
| [Updates](updates.md) | The updater: when it checks, Stable, Beta and Alpha, automatic downloads, and its state |
| [Building](building.md) | `config()`, the electron-builder config every app builds with; the files an app opens, on Linux; the update files every release carries; and naming an app's files |
Expand Down
4 changes: 3 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ package that's already on npm.
## Visual Tests

`e2e/visual.spec.js` compares the demo's window with committed images, in
`e2e/visual.spec.js-snapshots/`, using Playwright's `toHaveScreenshot()`. It takes 62 images:
`e2e/visual.spec.js-snapshots/`, using Playwright's `toHaveScreenshot()`. It takes 78 images:

| State | Accents | Themes |
|---|---|---|
Expand All @@ -56,6 +56,8 @@ package that's already on npm.
| The Queue on the collapsed rail: its bulk bar over its cards, two to a row, a paused upload's Resume focused by keyboard | the demo's and each app's | light and dark |
| The Queue with three cards selected: the bar's box half ticked, "3 selected", its target's note and the actions a selection shows | the demo's and each app's | light and dark |
| The update dot on the collapsed rail | the demo's | light and dark |
| The bottom bar shown under Controls, its item focused by keyboard | the demo's and each app's | light and dark |
| The bottom bar with its panel open, by a click | the demo's and each app's | light and dark |

The app accents are the ones in `test/fixtures/accents/`. The page is 760 × 600 at a scale factor of 1, drawn without the GPU, as on the runner, which has none.
Motion is reduced, so every transition ends at once. The caret is hidden and the mouse is parked. The
Expand Down
20 changes: 19 additions & 1 deletion docs/page-components.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Page Components

The page library's parts: toasts, prompts, the parts of a section of files, cards, the card grid and its bulk bar, busy buttons, the switch, the stepped slider, the key guard, the house's wording for numbers, and its copy rules.
The page library's parts: toasts, prompts, the parts of a section of files, cards and the status line, the card grid and its bulk bar, busy buttons, the switch, the stepped slider, the key guard, the house's wording for numbers, and its copy rules.

[All the docs](README.md)

Expand Down Expand Up @@ -204,6 +204,24 @@ onClick: (button) => {
}
```

## The Status Line

`kit.ui.statusLine({ text, tone, title })` is a card's status line on its own, for a state shown outside a card: a
Settings tab's, a bottom bar item's. A card builds its own line through it, so the two never drift.

```js
const line = kit.ui.statusLine({ text: "Connected", tone: "success", title: "Connected, through whichever adapter Windows chooses." });
pane.append(line.element);
line.set({ text: "Checking…", tone: "running" }); // the whole of it each time; the title is the text again
```

- `element` is a `span.kit-card-status.kit-tone-<tone>` holding `.kit-status-dot` and `.kit-status-text`, so it can
sit in a button. The text is one line, cut off with an ellipsis; `title` is its tooltip, the text unless given.
- **The tones** are a card's: `ready`, `running` (pulsing; still under reduced motion), `warning`, `danger`,
`success` and `muted`, as under Cards above. The words follow the copy rules below.
- A tone that isn't one of them, text that isn't text, or an option it doesn't take throws, from `set()` too,
which leaves the line as it was.

## The Card Grid

`kit.ui.cardGrid(container, { view, onSelect })` lays out a list's cards and selects them, by the rules File
Expand Down
Loading
Loading