@pckgs/ccs
v1.0.3
Published
Common Components — shared browser modules.
Readme
ccs
ccs - short for Common Components — shared browser modules, and more.
Small, framework-free, jsDelivr-friendly browser modules (button state
machines, modals, drawers, popovers, toasts, i18n, theming) plus a
handful of server-side helpers for Cloudflare Workers. Each browser
module is one self-contained IIFE / CSS file. No bundler, no module
loader; just <script> / <link> tags.
Install
Via npm (resolves through jsDelivr — no npm install needed):
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/overlay/style.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/toast/style.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/spinner/style.min.css">
<script src="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/i18n-engine/client.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/action/client.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/overlay/client.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/popover/client.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/toast/client.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/footer-brand/client.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@pckgs/[email protected]/theme/client.min.js"></script>Pin a specific version (recommended) or drop @<version> for "latest".
<script> tags are parser-blocking and run in document order — load
i18n-engine and action before overlay (overlay reads
window.tr and window.Action).
Modules
action — async-button state machine
Wraps a <button> into idle → (validate) → pending → success / error → idle
states. Use it for any button that triggers async work (form submit,
fetch, multi-step flow).
const btn = Action.create({
text: 'Save',
pendingText: 'Saving…',
successText: 'Saved',
retryText: 'Retry',
validate: () => emailIsValid() || 'Email looks wrong',
asyncFn: async () => { await fetch('/api/save', { method: 'POST' }); },
onSuccess: () => location.reload(),
});
container.appendChild(btn);
// Or wrap an existing button:
Action.wrap(document.querySelector('#submit'), {
asyncFn: async () => { /* ... */ },
});window.Action.create(opts) returns the button element (controller on
btn._ctl); Action.wrap(btn, opts) returns the controller directly.
The controller exposes getState(), setEnabled(), setText(),
reset(), trigger(), detach().
overlay — Modal, Drawer, inline overlay
A unified covering-layer primitive. Three variants cover the common cases:
box— page-fixed centered box (Modal-style)edge-right— page-fixed slide-in panel (Drawer-style)flat— container-absolute overlay (in-section loading / busy state)
Two scopes: 'page' (full-screen, with focus-return / scroll lock /
Esc / click-outside) or any HTMLElement (scoped, no page-level side
effects).
// Modal sugar — back-compat aliases on window.Modal:
Modal.confirm('Delete this item?', { onOk: () => doDelete() });
Modal.alert('Saved successfully.');
Modal.input('Project name?', { onOk: (val) => createProject(val) });
// Drawer (slide from right):
const drawer = Overlay.show({
variant: 'edge-right',
title: 'Settings',
body: settingsPanelEl,
});
// later: drawer.close()
// Loading overlay scoped to a section:
await Overlay.run({
variant: 'flat',
scope: tableContainerEl,
asyncFn: async () => { await reloadTable(); },
});Requires window.tr (a (key) => string lookup, caller-provided)
and window.Action (load action/client.min.js first). If tr
isn't set, falls back to English defaults for built-in labels.
popover — non-modal click-outside lifecycle
For caller-built popups (context menus, dropdowns) where you want click-outside / Esc to close, but don't want the modal-style focus trap or scroll lock.
triggerBtn.addEventListener('click', (e) => {
e.stopPropagation(); // prevent the click-outside detector from firing immediately
myDropdownEl.style.display = 'block';
Popover.show({
el: myDropdownEl,
closable: { escape: true, clickOutside: true },
onClose: () => { myDropdownEl.style.display = 'none'; },
});
});You manage markup, positioning, and show/hide; Popover only manages
when to close.
toast — bottom-right notifications
Top-down stacked, auto-dismissed.
Toast.ok('Saved'); // green, ~2s
Toast.err('Failed: ' + err.message); // red, ~4.5s
Toast.show('Hello', 'ok'); // explicit kindContainer <div id="toasts"></div> is auto-created if missing. Drop
<div id="toasts"></div> in your markup if you want to control its
position. RTL pages get bottom-left placement automatically.
spinner — CSS-only loading circle
<div class="spinner"></div>20×20 px ring, @keyframes spin rotation, no JS. Good as a button label,
inline-with-text indicator, or inside other components.
footer-brand — branded <footer> IIFE
Drops a small, lang-aware footer at the bottom of the page. After your i18n setup runs, call:
FooterBrand.applyLang(currentLang);Override --footer-color / --footer-border CSS vars if you want it
tinted differently.
i18n-engine — language detection + DOM translation + LangSelect
20-language detection (including RTL: ar, he) with sensible Chinese
fallback (zh-{hant,tw,hk,mo} → zh-tw, other zh* → zh-cn).
const SUPPORTED = ['en','fr','de','zh-cn','zh-tw','ja','ar', /* ... */];
const lang = detectLang(SUPPORTED); // walks navigator.languages
applyLocaleAttrs(lang); // sets <html lang> + <html dir>
const TRANSLATIONS = {
en: { hello: 'Hello', save: 'Save' },
fr: { hello: 'Bonjour', save: 'Sauvegarder' },
// ...
};
applyI18nAttrs(TRANSLATIONS[lang]); // walks [data-i18n] / [data-i18n-ph] / [data-i18n-title]
// Optional <select> for switching:
// <select id="lang-select">...</select>
LangSelect.init(lang, (newLang) => {
localStorage.setItem('lang', newLang);
location.reload();
});The engine functions (detectLang, isRTL, applyLocaleAttrs,
applyI18nAttrs) plus constants (SUPPORTED_LANGS, RTL_LANGS) are
exposed at script global scope. LangSelect is on window.LangSelect.
theme — dark/light toggle button
Drop in a button, load the IIFE, the rest is automatic — reads
localStorage('theme'), applies data-theme to <html>, wires
the click handler:
<button id="themeToggle">🌓</button>
<script src=".../theme/client.min.js"></script>Define --surface / --text / etc. with two values gated on
html[data-theme="dark"] in your CSS.
Caveat: theme reads
localStorage. In strict tracking-prevention browsers (Edge), cross-origin scripts may be blocked from accessinglocalStorage, breaking persistence. If you need theme persistence in Edge, vendor this module into your own first-party bundle instead of loading it from jsDelivr.
Required CSS variables
Modules read CSS custom properties on :root. Provide:
- All modules:
--surface,--border,--text,--text-muted,--accent,--err toast: also--okspinner: uses--borderand--accentfooter-brand: optionally--footer-color,--footer-border
License
MIT — see LICENSE.
