@jolly-pixel/ui
v3.2.0
Published
Common and System's UI for JollyPixel's editors
Readme
📌 About
Browser-based Lit components for JollyPixel editor interfaces: controlled fields, actions, icons, theming and collaboration-aware field state.
💡 Features
- Form controls: text, number, slider, range, checkbox, select, flags, color and button groups
- Containers and chrome: panes, folders, tabs, docks, floating panes, dialogs, toolbars and rails
- Actions and layout: buttons, separators and property rows
- Controlled fields: shared values, events, drafts, validation, mixed values and defaults
- Collaboration state: peer presence, field locking and peer colors
- Feedback: determinate and indeterminate progress plus the runtime loading screen
- Theming: light/dark themes, density presets and semantic custom-property tokens
- Icons: built-in glyphs and an open registry for custom icons
💃 Getting Started
This package is available in the Node Package Repository and can be easily installed with npm or yarn.
$ npm i @jolly-pixel/ui
# or
$ yarn add @jolly-pixel/ui[!IMPORTANT]
litis a peer dependency. Use one compatible copy in the application.
👀 Usage Example
Import the package to register its custom elements. Apply themeStyles to a shadow-root scope
host, then bind field values and change events:
import { LitElement, html, css } from "lit";
import { customElement } from "lit/decorators.js";
import { themeStyles } from "@jolly-pixel/ui";
@customElement("settings-pane")
export class SettingsPane extends LitElement {
static override styles = [themeStyles, css`:host { display: block; }`];
#opacity = 1;
override render() {
return html`
<jolly-text
label="Name"
.value=${"Player"}
></jolly-text>
<jolly-number
label="Opacity"
.step=${0.01}
.min=${0}
.max=${1}
.value=${this.#opacity}
.default=${1}
@jolly-change=${this.#setOpacity}
></jolly-number>
<jolly-button variant="accent" @click=${this.#resetOpacity}>
Reset opacity
</jolly-button>
`;
}
#setOpacity = (event: CustomEvent<{ value: number }>) => {
this.#opacity = event.detail.value;
this.requestUpdate();
};
#resetOpacity = () => {
this.#opacity = 1;
this.requestUpdate();
};
}jolly-text, jolly-number and jolly-button are components provided by this package. Fields are
controlled: they render the supplied value and emit changes, so the handler writes the new value
back into the component's state.
Set density on the scope host when needed:
<settings-pane density="compact"></settings-pane>📚 API
The installation and first component example above are the starting point. These guides cover cross-component behavior:
- Choosing controls
- Controlled fields
- Composing containers
- Docking and persistence
- Theming and density
- Using the facade
The API reference follows the implementation folders. Each registered custom element has one page with its usage, properties, events, methods, slots, and styling surface where applicable.
- Controls
- Containers
- Data
- Feedback
- Icons
- Math
- Monitors
- Peer presence
- Stats
- Theme components
- Facade
- Storage
- Interaction helpers
- Color utilities
🖼️ Examples Gallery
Every component has a gallery entry, which is also its only end to end fixture.
pnpm run devThe gallery exercises the shared field states. Deep-link a control with or without the surrounding shell:
/?example=controls/number
/?example=controls/number&chrome=offAdding an example:
- Put the module in
examples/scripts/examples/<group>/, and give it the id<group>/<name>. The folder, the id prefix, and the navigation group are the same word, andgroups.tsmaps it to a label. An id outside that list fails to compile. - Register it in that folder's
index.ts, which is the only order the navigation and the manifest sweep read.manifest.tsitself never changes. - Return a teardown from
renderonly for state living outsidehost: timers, subscriptions, listeners onwindowordocument, and panes mounting themselves ondocument.body. The gallery clearshoston its own. - Keep DOM access inside
render. The manifest sweep imports every example in Node, whereHTMLElementdoes not exist at module scope. - Give a component one page, and declare its variants as
optionsrather than as sibling pages. The shell renders one checkbox per option, remounts the example on a toggle, and keeps the state in the URL (/?example=containers/tabs&closable=1), so a variant stays deep-linkable and survives a reload. End to end tests pass them throughopenExample(page, id, { options }).
Contributors Guide
Read the contributing guide before making changes.
Run the package checks with:
pnpm run test
pnpm run test:e2e
pnpm run lintUnit tests use node:test; end-to-end tests use Playwright against the gallery.
End-to-end tests follow the production ownership model under test/e2e/:
controls/,containers/, andfield/hold component and field contracts.gallery/verifies the gallery harness and manifest.scenarios/holds multi-component workflows that do not belong to one source module.support/owns navigation, event capture, dock and computed-style helpers. Pointer and locator helpers come from@jolly-pixel/e2e. Do not duplicate these helpers in a spec.
Keep a test with the component that owns the observed contract. Put only
deliberately cross-component user workflows in scenarios/.
[!IMPORTANT] Keep unit-test assertions in plain modules. Component decorators are not erasable syntax and cannot be imported directly by
node --testwith type stripping.
[!CAUTION] Include tests for new features and bug fixes.
License
MIT
This package embeds Roboto Mono (weight 400, latin subset), licensed under the
Apache License 2.0. See NOTICE for the full attribution. The face is
registered against the document on first import of themeStyles; call ensureFontFace() yourself
if you declare theme tokens by hand. Without it, --jolly-font-family falls back to the system
mono stack.
