staffa 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -16
- package/dist/components/autocomplete.js +12 -9
- package/dist/components/box.js +15 -10
- package/dist/components/button.d.ts +3 -1
- package/dist/components/button.js +21 -28
- package/dist/components/buttonChooser.js +1 -1
- package/dist/components/buttonGroup.d.ts +3 -3
- package/dist/components/buttonGroup.js +3 -3
- package/dist/components/dialog.d.ts +3 -1
- package/dist/components/dialog.js +20 -10
- package/dist/components/field.js +7 -4
- package/dist/components/main.js +52 -15
- package/dist/components/menu.d.ts +23 -2
- package/dist/components/menu.js +54 -29
- package/dist/components/select.js +1 -1
- package/dist/components/tabs.js +3 -3
- package/dist/components/toast.js +8 -8
- package/dist/components/tooltip.js +4 -3
- package/dist/core.d.ts +14 -2
- package/dist/core.js +23 -0
- package/dist/staffa.esm.js +1 -1
- package/dist/theme.js +138 -224
- package/package.json +3 -2
- package/skill/Attributes.md +10 -0
- package/skill/AutocompleteOptions.md +37 -0
- package/skill/BoxOptions.md +33 -0
- package/skill/ButtonChooserOptions.md +37 -0
- package/skill/ButtonGroupOptions.md +23 -0
- package/skill/ButtonOptions.md +58 -0
- package/skill/CheckboxOptions.md +27 -0
- package/skill/ContentOptions.md +16 -0
- package/skill/DialogOptions.md +70 -0
- package/skill/FieldOptions.md +63 -0
- package/skill/FloatingMenuOptions.md +21 -0
- package/skill/FormOptions.md +31 -0
- package/skill/MainOptions.md +91 -0
- package/skill/MenuItem.md +52 -0
- package/skill/MenuOptions.md +25 -0
- package/skill/SKILL.md +609 -0
- package/skill/SelectOptions.md +21 -0
- package/skill/Slot.md +13 -0
- package/skill/Tab.md +33 -0
- package/skill/TabsOptions.md +28 -0
- package/skill/TextareaOptions.md +51 -0
- package/skill/TextlineOptions.md +45 -0
- package/skill/TextlineType.md +18 -0
- package/skill/ToastOptions.md +40 -0
- package/skill/TooltipOptions.md +22 -0
- package/skill/addContextMenu.md +28 -0
- package/skill/addTooltip.md +29 -0
- package/skill/alert.md +17 -0
- package/skill/autocomplete.md +29 -0
- package/skill/box.md +26 -0
- package/skill/button.md +31 -0
- package/skill/buttonChooser.md +23 -0
- package/skill/buttonGroup.md +22 -0
- package/skill/checkbox.md +18 -0
- package/skill/confirm.md +17 -0
- package/skill/dialog.md +28 -0
- package/skill/form.md +29 -0
- package/skill/getDarkMode.md +12 -0
- package/skill/main.md +37 -0
- package/skill/menuButton.md +27 -0
- package/skill/prompt.md +19 -0
- package/skill/select.md +18 -0
- package/skill/showFloatingMenu.md +21 -0
- package/skill/tabs.md +19 -0
- package/skill/textarea.md +17 -0
- package/skill/textline.md +20 -0
- package/skill/toast.md +19 -0
- package/src/components/autocomplete.ts +12 -9
- package/src/components/box.ts +15 -10
- package/src/components/button.ts +23 -32
- package/src/components/buttonChooser.ts +1 -1
- package/src/components/buttonGroup.ts +3 -3
- package/src/components/dialog.ts +22 -11
- package/src/components/field.ts +7 -4
- package/src/components/main.ts +47 -16
- package/src/components/menu.ts +73 -40
- package/src/components/select.ts +1 -1
- package/src/components/tabs.ts +3 -3
- package/src/components/toast.ts +8 -8
- package/src/components/tooltip.ts +4 -3
- package/src/core.ts +30 -2
- package/src/theme.ts +152 -234
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: staffa
|
|
3
|
+
description: Documentation for the Staffa component library for Aberdeen. Covers *surfaces*, CSS variables for colors etc, how to the various `s-` prefixed css classes, customizing, overriding style, adding own components. Access this if you're doing front-end work on an Aberdeen project that already uses Staffa, or may benefit from it.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Staffa
|
|
7
|
+
|
|
8
|
+
A small, opinionated TypeScript component library for the [Aberdeen](https://aberdeenjs.org) reactive UI library.
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
import A from "aberdeen";
|
|
12
|
+
import * as S from "staffa";
|
|
13
|
+
|
|
14
|
+
const $user = A.proxy({ name: "", email: "" });
|
|
15
|
+
|
|
16
|
+
S.main({
|
|
17
|
+
title: "Sign up",
|
|
18
|
+
maxWidth: "40rem",
|
|
19
|
+
content: () => {
|
|
20
|
+
S.form({
|
|
21
|
+
submit: () => S.dialog({
|
|
22
|
+
header: "Submitted",
|
|
23
|
+
content: () => A.dump($user)
|
|
24
|
+
}),
|
|
25
|
+
content: () => {
|
|
26
|
+
S.textline({ label: "Name", required: true, bind: A.ref($user, "name") });
|
|
27
|
+
S.textline({ label: "Email", type: "email", bind: A.ref($user, "email") });
|
|
28
|
+
},
|
|
29
|
+
actions: () => S.button({ content: "Create account", type: "submit" }),
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Staffa is made to look decent out of the box, but easily customizable at runtime.
|
|
36
|
+
|
|
37
|
+
## Screenshot
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
npm install staffa aberdeen
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Aberdeen is a peer dependency. Staffa is published as ESM with TypeScript types.
|
|
48
|
+
|
|
49
|
+
## How it works
|
|
50
|
+
|
|
51
|
+
### Components are functions
|
|
52
|
+
|
|
53
|
+
Every component takes a single typed options object and draws DOM via Aberdeen. No classes, no web components. The `S` object collects all component functions:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
S.button({ content: "Save", disabled: false });
|
|
57
|
+
S.box({ header: "Settings", content: () => { ... } });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Options objects are typed and can be reactive
|
|
61
|
+
|
|
62
|
+
All components get their options in a typed object. The object may be an Aberdeen proxy, if you want to update the component in-place.
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
const $btn = A.proxy({ content: "Save", disabled: false });
|
|
66
|
+
S.button($btn);
|
|
67
|
+
setTimeout(() => // Later..
|
|
68
|
+
$btn.disabled = true; // button updates instantly
|
|
69
|
+
}, 3000);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Rich text slots
|
|
73
|
+
|
|
74
|
+
Anywhere a component takes content, a `label`, `header`, button `text`, dialog body, etc, you can pass either a string or a `() => void` draw function. Strings render as **rich text**: `*italic*`, `**bold**`, `` `code` ``, `[link](/path)`. All text is safely escaped.
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
S.button({ content: "Save **now**" });
|
|
78
|
+
S.box({ header: "See the [docs](/docs)", content: () => { ... } });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Surfaces
|
|
82
|
+
|
|
83
|
+
Staffa builds on **surfaces**: elements marked with `.s-s` that have their own background and derived text/border tokens. There are two families:
|
|
84
|
+
|
|
85
|
+
- **Nesting surfaces** — `.nest` (and the implicit page at `:root`). A calm neutral whose shade steps automatically with nesting depth (page → panel → raised, capped). Use them for cards, bars, popovers — anything that just holds content. No variants.
|
|
86
|
+
- **Solid surfaces** — `.primary`, `.danger`, `.success`, `.warning`, `.link` (a bare `.s-s` defaults to primary). A bright fill with white ink, painted as a subtle single-colour gradient. They take a **variant**: `.filled` (default), `.tonal`, or `.outlined`. A surface nested *inside* a solid surface is always rendered filled, so it can't bleed into the vivid parent.
|
|
87
|
+
|
|
88
|
+
Components are built from these (`S.button` is a `.s-s.primary`, `S.box` a `.s-s.nest`, etc.). Because component options include an optional `attrs` string, which has Aberdeen `A()` string semantics, you can easily override it:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
S.button({ content: "Delete", attrs: ".danger" });
|
|
92
|
+
S.button({ content: "Cancel", attrs: ".nest" }); // neutral button
|
|
93
|
+
S.box({ attrs: ".primary", content: () => { ... } });
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Inside any surface (including `:root`), CSS variables are defined for the background and a set of safe foreground colors: `$s-bg`, `$s-text` (also applied as `color`), `$s-muted`, `$s-accent` (the surface's "pop" — the brand primary on nesting surfaces, the ink on solid surfaces), and `$s-faint`. By using these, components adapt to wherever they're nested.
|
|
97
|
+
|
|
98
|
+
The colour tokens are mode-independent and settable: `$s-primary` (the one brand colour — it tints the neutrals and defines `.s-s.primary`), `$s-danger`, `$s-success`, `$s-warning`, and `$s-link` (the link colour, which is also the fill of the `.s-s.link` surface). Links render in `$s-link` on nesting surfaces and in the ink on solid surfaces.
|
|
99
|
+
|
|
100
|
+
**Borders & shadows.** Nesting surfaces carry a subtle hairline border on their own (so a card looks like a card without any component help). Any surface can be lifted with `.shadow` or `.extra-shadow`: on a nesting surface that's a neutral drop shadow, on a solid surface it's a self-coloured glow (a lit button is just a `.primary` surface with `.shadow`), and on `.tonal`/`.outlined` it's ignored. `.no-shadow` removes a component's built-in shadow:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
S.box({ attrs: ".extra-shadow", content: () => { ... } }); // a more raised card
|
|
104
|
+
S.button({ content: "Quiet", attrs: ".no-shadow" }); // drop the button glow
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Dark and light modes
|
|
108
|
+
|
|
109
|
+
Dark/light mode is detected from OS preference by default. If you want to override this (based on user preferences), use:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
S.setDarkMode(true); // force dark
|
|
113
|
+
S.setDarkMode(false); // force light
|
|
114
|
+
S.setDarkMode(undefined); // follow OS
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
*Hint:* A `buttonChooser` is probably the right component for a color scheme selector.
|
|
118
|
+
|
|
119
|
+
### CSS reset
|
|
120
|
+
|
|
121
|
+
Staffa includes a lightweight CSS reset that makes bare semantic HTML look a bit better but unsurprising without additional styling.
|
|
122
|
+
|
|
123
|
+
### Theming
|
|
124
|
+
|
|
125
|
+
The first step in theming is just setting some CSS variables. Everything derives from a single brand colour, `s-primary` (the neutral surface shades are tinted toward it too), so often that's all you need. This can be done through CSS directly, or using Aberdeen:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
A.cssVars["s-primary"] = "#fdda58";
|
|
129
|
+
A.cssVars["s-danger"] = "#ee4422";
|
|
130
|
+
A.cssVars["s-radius"] = "4px";
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
See `src/theme.ts` for what other CSS variables are being used.
|
|
134
|
+
|
|
135
|
+
If you need further customization, just add some CSS to override the default styling. For instance, to add your own solid surface, set its background (and, if needed, its ink) — the subtle gradient and the rest of the tokens follow automatically:
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
A.insertGlobalCss({".s-s.my-surface": "--s-bg:#ef6b00 --s-text:#fff"});
|
|
139
|
+
|
|
140
|
+
S.button({
|
|
141
|
+
content: "You'll want to click me",
|
|
142
|
+
attrs: ".my-surface",
|
|
143
|
+
click: () => S.alert("Good work!", {attrs: ".my-surface"})
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Custom surface class names may be anything (other than the built-in modifiers `.tonal`, `.outlined`, `.small`, `.large`). The `.tonal` and `.outlined` variants work on your surface for free.
|
|
148
|
+
|
|
149
|
+
Note that when changing CSS like this, things *may* break if you upgrade Staffa. The recommended update strategy is therefore: don't!
|
|
150
|
+
|
|
151
|
+
If you want to make changes that are dependent upon the current light/dark mode setting, rely on Aberdeen reactivity:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
A(() => {
|
|
155
|
+
if (S.getDarkMode()) {
|
|
156
|
+
A.cssVars["s-primary"] = "#aa9944";
|
|
157
|
+
A.insertGlobalCss({".s-s.my-surface": "--s-bg:#444444 --s-text:#fff"});
|
|
158
|
+
} else {
|
|
159
|
+
A.cssVars["s-primary"] = "#fdda58";
|
|
160
|
+
A.insertGlobalCss({".s-s.my-surface": "--s-bg:#cccccc --s-text:#000"});
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Components
|
|
166
|
+
|
|
167
|
+
Components share naming conventions for options: `attrs` (outermost element), `contentAttrs` (children-holding element), `inputAttrs` (form control element), and `<region>Attrs` (sub-regions like `headerAttrs`/`footerAttrs`). Form components consistently support `label`, `help`, `error`, `disabled`, `required`, `name` through the `drawField()` helper.
|
|
168
|
+
|
|
169
|
+
### Layout & containers
|
|
170
|
+
|
|
171
|
+
- **`S.main(opts)`**: app shell, a sticky header with `icon`, `title`, `subtitle`, `menu`; scrollable content area; footer. Set `maxWidth` to center the content.
|
|
172
|
+
- **`S.box(opts | content)`**: surface with optional `header`/`footer` and padded body. Pass a function for shorthand `{ content }`.
|
|
173
|
+
- **`S.tabs(opts)`**: tablist with live panels and keyboard navigation.
|
|
174
|
+
- **`S.form(opts | content)`**: form aligning fields in a column or responsive grid, with an `actions` bar. Prevents the default page reload.
|
|
175
|
+
|
|
176
|
+
### Form fields
|
|
177
|
+
|
|
178
|
+
- **`S.textline(opts)`**: single-line input (`text`, `password`, `email`, `number`, `tel`, `url`, `search`, dates, ...).
|
|
179
|
+
- **`S.textarea(opts)`**: multi-line input.
|
|
180
|
+
- **`S.checkbox(opts)`**: labelled checkbox.
|
|
181
|
+
- **`S.select(opts)`**: single-select dropdown backed by native `<select>` (styled control, OS dropdown).
|
|
182
|
+
- **`S.autocomplete(opts)`**: type-ahead combobox with `multi` (chips), `allowCustom` (free text), `required`, and dynamic `options`.
|
|
183
|
+
|
|
184
|
+
### Dialogs
|
|
185
|
+
|
|
186
|
+
- **`S.dialog(opts)`**: modal dialog with backdrop and fade transition. The `content` slot receives a `close()` function. Lifecycle is tied to the calling scope (disappears when cleaned up). Nesting stacks correctly.
|
|
187
|
+
- **`S.alert(msg)` / `S.confirm(msg)` / `S.prompt(msg, initial?)`**: promise-returning shortcuts.
|
|
188
|
+
|
|
189
|
+
### Actions
|
|
190
|
+
|
|
191
|
+
- **`S.button(opts | text)`**: button surface; restyle via `attrs` (e.g. `.danger`, `.outlined`), plus `size`, `disabled`, `icon`, `href` (renders `<a role=button>`). Defaults to filled `.primary`.
|
|
192
|
+
- **`S.buttonGroup(opts)`**: groups buttons, `attached` (segmented) or `spaced`.
|
|
193
|
+
- **`S.buttonChooser(opts)`**: single-select segmented control bound to a value.
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
### Icons
|
|
197
|
+
|
|
198
|
+
Staffa ships the full [Lucide icon set](https://lucide.dev/icons/) as named exports. Import only the ones you use, so a bundler tree-shakes the rest (the whole set is ~82 kB gzipped):
|
|
199
|
+
|
|
200
|
+
Each icon is a draw function usable anywhere a slot is accepted (e.g. a button `icon`), or called directly. Customize per call, or globally via `setDefaults()`:
|
|
201
|
+
|
|
202
|
+
```ts
|
|
203
|
+
import * as S from "staffa";
|
|
204
|
+
import { sparkles, bell } from "staffa/icons";
|
|
205
|
+
S.button({ content: "Save", icon: bell });
|
|
206
|
+
sparkles({ size: "1.5em", color: "var(--s-primary)", strokeWidth: 1.5 });
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Options: `size`, `color` (defaults to `currentColor`), `strokeWidth`, `cap`, `join`, `attrs`.
|
|
210
|
+
|
|
211
|
+
### Other
|
|
212
|
+
|
|
213
|
+
- **`S.menuButton(opts)` / `S.addContextMenu(opts)` / `S.showFloatingMenu(opts)`**: dropdown menus from a button, right-click/long-press context menus, and the underlying floating menu primitive — with keyboard navigation.
|
|
214
|
+
- **`S.toast(opts)`**: transient notification at the bottom of the viewport.
|
|
215
|
+
- **`S.addTooltip(el, opts)`**: tooltip on hover, attached to an existing element.
|
|
216
|
+
|
|
217
|
+
Two-way binding uses Aberdeen proxies: pass `bind: A.ref($obj, "key")` to form fields.
|
|
218
|
+
|
|
219
|
+
## Browser (no bundler)
|
|
220
|
+
|
|
221
|
+
`staffa/all.js` is a pre-built ESM bundle. Use an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap):
|
|
222
|
+
|
|
223
|
+
```html
|
|
224
|
+
<script type="importmap">
|
|
225
|
+
{
|
|
226
|
+
"imports": {
|
|
227
|
+
"aberdeen": "https://cdn.jsdelivr.net/npm/aberdeen/dist/src/aberdeen.js",
|
|
228
|
+
"staffa/all.js": "https://cdn.jsdelivr.net/npm/staffa/dist/staffa.esm.js"
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
</script>
|
|
232
|
+
<script type="module">
|
|
233
|
+
import A from "aberdeen";
|
|
234
|
+
import * as S from "staffa/all.js";
|
|
235
|
+
// ...
|
|
236
|
+
</script>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
It includes all components, but not the icons.
|
|
240
|
+
|
|
241
|
+
## Extending Staffa
|
|
242
|
+
|
|
243
|
+
Staffa is designed for extension. A component is simply a plain function taking a typed options object and drawing Aberdeen DOM. This section explains the philosophy so extensions follow the same patterns.
|
|
244
|
+
|
|
245
|
+
### Design principles
|
|
246
|
+
|
|
247
|
+
1. **Components are functions**. They take one typed options object, emit Aberdeen DOM, and *usually* return nothing.
|
|
248
|
+
|
|
249
|
+
2. **Reuse option types.** Define options by extending `ContentOptions` (for layout components) or `FieldOptions` (for form controls) from `src/core.ts` and `src/components/field.ts`. Don't reinvent fields like `attrs`, `label`, `help`, etc.
|
|
250
|
+
|
|
251
|
+
3. **Reach for reactivity deliberately.** Pass option strings straight to `A` as positional args (the caller's scope). Only wrap a dedicated `A(() => ...)` scope where it matters: input elements (recreation loses focus), or large subtrees you don't want to redraw. Use `A.peek(() => ...)` when you need a value but must not subscribe.
|
|
252
|
+
|
|
253
|
+
4. **Build on surfaces.** Mark elements `.s-s` and add `.nest` (neutral) or a solid role (`.primary`, `.danger`, …) plus an optional variant. Inside them, use the contextual CSS variables (`$s-text`, `$s-bg`, `$s-muted`, `$s-accent`, `$s-faint`, ...) so components adapt to wherever they're nested. Hard-coding colors in components shouldn't be needed, but if you must, make sure you set *both* foreground and background.
|
|
254
|
+
|
|
255
|
+
5. **No outer margins.** Components don't margin themselves; spacing is the parent's job. Content components set default `padding` on the content element; `contentAttrs` overrides it.
|
|
256
|
+
|
|
257
|
+
6. **Make everything styleable.** Provide `attrs`, `contentAttrs`, `inputAttrs`, and `<region>Attrs` hooks so callers can customize. Apply `attrs` last so it can override component classes.
|
|
258
|
+
|
|
259
|
+
7. **Use semantic HTML and ARIA.** Prefer native elements (`<button>`, `<label>`, `<form>`, `<section>`) and native behaviour. Add ARIA only where semantics fall short (e.g. tabs, combobox).
|
|
260
|
+
|
|
261
|
+
8. **Use CSS.** Use `A.insertGlobalCss({...})` at module top level to provide (nested) CSS styling for your component. Give your top-level element the `s-<component-name>` class. Avoid inventing further classes; lean on nesting (`&` for the element, bare key for descendants) and element/structural selectors.
|
|
262
|
+
|
|
263
|
+
9. **Reuse form controls.** Use `drawField()` and call `applyControlAttrs()`.
|
|
264
|
+
|
|
265
|
+
10. **Function over form.** Provide enough contrast. Stick to UI conventions to help users; buttons have a rounded border, links are underlined, text input background is white, etc.
|
|
266
|
+
|
|
267
|
+
### Adding a component to Staffa
|
|
268
|
+
|
|
269
|
+
The previous section is good advice for any project-specific custom, but should definitely be followed for any new components to be included in Staffa. In addition, you'd want to:
|
|
270
|
+
|
|
271
|
+
1. Create `src/components/<name>.ts`.
|
|
272
|
+
2. Define `<Name>Options` extending `ContentOptions`, `FieldOptions`, or a plain interface. Add TSDoc on every option.
|
|
273
|
+
3. Add a TSDoc `@example` on the function.
|
|
274
|
+
4. Register in `src/index.ts` (the `S` object + type re-export).
|
|
275
|
+
5. Extend `smoke.mjs` to render it. Run `npm run smoke` and `npm run build`.
|
|
276
|
+
|
|
277
|
+
See `src/components/button.ts` and `src/components/dialog.ts` for examples.
|
|
278
|
+
|
|
279
|
+
## Commands
|
|
280
|
+
|
|
281
|
+
```sh
|
|
282
|
+
npm run build # compile TypeScript to dist/
|
|
283
|
+
npm run typecheck # check types
|
|
284
|
+
npm run smoke # render every component in jsdom
|
|
285
|
+
npx http-server # allows demo to be viewed at http://localhost:8080/demo
|
|
286
|
+
npx shotest test # visual tests: click through the demo, screenshotting every step
|
|
287
|
+
npx shotest review # review/accept the visual changes against the baseline
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
The visual tests (`tests/*.spec.ts`) need a build first (`npm run build`); they serve the repo root themselves and click through every demo page. Accepted baselines live in `test-accepted/`.
|
|
291
|
+
|
|
292
|
+
## AI skill
|
|
293
|
+
|
|
294
|
+
If you use Claude Code, GitHub Copilot or another AI agents that supports Skills, Staffa includes a `skill/` directory that provides specialized knowledge to the AI about how to use the library effectively.
|
|
295
|
+
|
|
296
|
+
To use this, it is recommended to symlink the skill into your project's `.claude/skills` directory:
|
|
297
|
+
|
|
298
|
+
```sh
|
|
299
|
+
mkdir -p .claude/skills
|
|
300
|
+
ln -s ../../node_modules/staffa/skill .claude/skills/staffa
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Breaking changes
|
|
304
|
+
|
|
305
|
+
- **0.7** — the surface model was simplified to two families: **nesting** (`.nest`) and **solid** (`.primary`/`.danger`/`.success`/`.warning`/`.link`).
|
|
306
|
+
- Surface levels `.base`/`.panel`/`.raised`/`.neutral` are replaced by a single `.nest` class whose shade steps with nesting depth. Replace them all with `.nest` (a neutral *button* is `.nest` too, in place of `.neutral .outlined`).
|
|
307
|
+
- `.secondary` and `.gradient` are gone: there is no more `s-secondary` colour, and the default button is `.primary` (which now carries a subtle auto-gradient). Drop `s-secondary` overrides.
|
|
308
|
+
- Solid surfaces now use **white** ink (was near-black), and the brand/semantic colours (`s-primary`/`s-danger`/`s-success`/`s-warning`) are now a single mode-independent value each.
|
|
309
|
+
- New: a `s-link` colour and a matching `.s-s.link` surface. Links use `s-link` on nesting surfaces, the ink on solid ones.
|
|
310
|
+
- Nesting surfaces now carry their own hairline border, and `.shadow`/`.extra-shadow`/`.no-shadow` utilities work on any surface. `S.box` no longer draws its own border/shadow (they come from the surface); pass `.no-shadow` to `S.box`/`S.button` to drop the default elevation.
|
|
311
|
+
- Contextual tokens were renamed/trimmed: `--s-fg`→`--s-text`, `--s-fg-muted`→`--s-muted`, `--s-border`→`--s-faint`; `--s-strong`, `--s-fg-faint`, `--s-border-strong`, `--s-on-accent`, `--s-ink`, `--s-page`/`--s-panel`/`--s-raised`, `--s-tint`, `--s-glow`, `--s-shadow`, `--s-gradient-surface`, and the `--s-a`/`--s-b` anchors were removed. Custom solid surfaces now set `--s-bg`/`--s-text`.
|
|
312
|
+
|
|
313
|
+
- **0.6**: None.
|
|
314
|
+
|
|
315
|
+
- **0.5**
|
|
316
|
+
- Surfaces (`.s-s`) now apply `border-radius` and — for `.tonal` and `.outlined` variants — `border` automatically. Custom surfaces or components that previously set these manually may see doubled or conflicting styles; remove the manual declarations.
|
|
317
|
+
- `border:0` is now applied to `.s-btn` by default (overriding the browser's 2px button border). Custom button-like components built on `.s-btn` that relied on the browser default border should add an explicit border.
|
|
318
|
+
|
|
319
|
+
- **0.4**
|
|
320
|
+
- There is no default export anymore: replace `import S from "staffa"` with `import * as S from "staffa"`.
|
|
321
|
+
- `S.button` no longer has a `text` option: use `content` instead (it accepts a string or a draw function).
|
|
322
|
+
- The `Content` type is gone: use `Slot` instead. The `Styling` type alias is now exported as `Attributes`.
|
|
323
|
+
- `S.buttonChooser` uses `undefined` instead of `null` for "nothing selected" (in `bind` and with `allowDeselect`).
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# API Reference
|
|
327
|
+
|
|
328
|
+
## setDarkMode · function
|
|
329
|
+
|
|
330
|
+
Force dark mode (`true`), light mode (`false`), or follow the OS preference
|
|
331
|
+
(`undefined`). Takes effect immediately and is persisted to localStorage.
|
|
332
|
+
|
|
333
|
+
**Signature:** `(value: boolean) => void`
|
|
334
|
+
|
|
335
|
+
**Parameters:**
|
|
336
|
+
|
|
337
|
+
- `value: boolean | undefined`
|
|
338
|
+
|
|
339
|
+
## [getDarkMode](getDarkMode.md) · function
|
|
340
|
+
|
|
341
|
+
Whether dark mode is currently active. Reactive — read it inside a scope to
|
|
342
|
+
re-run on changes.
|
|
343
|
+
|
|
344
|
+
## [autocomplete](autocomplete.md) · function
|
|
345
|
+
|
|
346
|
+
A combobox with type-ahead filtering. Supports single or multi-select (chips),
|
|
347
|
+
optional free-text entry, and full keyboard control (arrows, enter, escape,
|
|
348
|
+
backspace-to-remove). Implements the ARIA combobox/listbox pattern.
|
|
349
|
+
|
|
350
|
+
## [AutocompleteOptions](AutocompleteOptions.md) · interface
|
|
351
|
+
|
|
352
|
+
Options for `autocomplete`.
|
|
353
|
+
|
|
354
|
+
## AutocompleteOptionInput · type
|
|
355
|
+
|
|
356
|
+
A selectable option: a bare string, or a `{ value, label }` pair.
|
|
357
|
+
|
|
358
|
+
**Type:** `string | { value: string; label?: string }`
|
|
359
|
+
|
|
360
|
+
## [box](box.md) · function
|
|
361
|
+
|
|
362
|
+
A surface container — the workhorse layout primitive. Has an optional styled
|
|
363
|
+
header and footer, and a padded body that holds `ContentOptions.content`.
|
|
364
|
+
|
|
365
|
+
## [BoxOptions](BoxOptions.md) · interface
|
|
366
|
+
|
|
367
|
+
Options for `box`.
|
|
368
|
+
|
|
369
|
+
## [button](button.md) · function
|
|
370
|
+
|
|
371
|
+
A button. Tonal and outlined variants show a border; filled variants rely on
|
|
372
|
+
their solid background for affordance.
|
|
373
|
+
|
|
374
|
+
## [ButtonOptions](ButtonOptions.md) · interface
|
|
375
|
+
|
|
376
|
+
Options for `button`.
|
|
377
|
+
|
|
378
|
+
## [buttonChooser](buttonChooser.md) · function
|
|
379
|
+
|
|
380
|
+
A single-selection segmented control: an attached button group where exactly
|
|
381
|
+
one button is active at a time. Optionally allows deselecting back to `undefined`.
|
|
382
|
+
|
|
383
|
+
## [ButtonChooserOptions](ButtonChooserOptions.md) · interface
|
|
384
|
+
|
|
385
|
+
Options for `buttonChooser`.
|
|
386
|
+
|
|
387
|
+
## [buttonGroup](buttonGroup.md) · function
|
|
388
|
+
|
|
389
|
+
Groups related buttons, either as a joined segmented control (`attached`) or
|
|
390
|
+
spaced out. A `role=group` is applied for assistive tech.
|
|
391
|
+
|
|
392
|
+
## [ButtonGroupOptions](ButtonGroupOptions.md) · interface
|
|
393
|
+
|
|
394
|
+
Options for `buttonGroup`.
|
|
395
|
+
|
|
396
|
+
## [checkbox](checkbox.md) · function
|
|
397
|
+
|
|
398
|
+
A checkbox with an associated, clickable label. Uses the native `<input
|
|
399
|
+
type=checkbox>` (styled with `accent-color`) for full keyboard and screen
|
|
400
|
+
reader support.
|
|
401
|
+
|
|
402
|
+
## [CheckboxOptions](CheckboxOptions.md) · interface
|
|
403
|
+
|
|
404
|
+
Options for `checkbox`.
|
|
405
|
+
|
|
406
|
+
## [form](form.md) · function
|
|
407
|
+
|
|
408
|
+
An opinionated `<form>` wrapper that lays its fields out consistently — a clean
|
|
409
|
+
single column by default, or a responsive grid — and provides a standard
|
|
410
|
+
action bar.
|
|
411
|
+
|
|
412
|
+
## [FormOptions](FormOptions.md) · interface
|
|
413
|
+
|
|
414
|
+
Options for `form`.
|
|
415
|
+
|
|
416
|
+
## [main](main.md) · function
|
|
417
|
+
|
|
418
|
+
An application shell that wires up the things almost every app needs: a sticky
|
|
419
|
+
top bar (icon, title, subtitle, action menu), a scrollable content area, and a
|
|
420
|
+
footer. With `MainOptions.maxWidth` the content area is centred and its
|
|
421
|
+
width capped. Add a `nav` to get a responsive sidebar (auto-collapses to a
|
|
422
|
+
menu button below 640 px, or always a button with `navPosition: "button"`).
|
|
423
|
+
|
|
424
|
+
## [MainOptions](MainOptions.md) · interface
|
|
425
|
+
|
|
426
|
+
Options for `main`.
|
|
427
|
+
|
|
428
|
+
## [menuButton](menuButton.md) · function
|
|
429
|
+
|
|
430
|
+
A button that opens a | floating dropdown menu on
|
|
431
|
+
click. Keyboard navigation: Arrow Up/Down, Home, End; Escape/Tab to close;
|
|
432
|
+
Enter/Space activate the focused item natively.
|
|
433
|
+
|
|
434
|
+
## [showFloatingMenu](showFloatingMenu.md) · function
|
|
435
|
+
|
|
436
|
+
Open a floating dropdown menu anchored to an element. Portals to
|
|
437
|
+
`document.body` (never clipped), positions itself (flipping up when there's
|
|
438
|
+
no room below), and closes on Escape, Tab, item selection, or any click
|
|
439
|
+
outside the panel and anchor. Returns a `close()` function.
|
|
440
|
+
|
|
441
|
+
## [addContextMenu](addContextMenu.md) · function
|
|
442
|
+
|
|
443
|
+
Attaches a context menu to the current element: adds a `contextmenu` handler
|
|
444
|
+
via `A` so a | floating menu opens (instead of
|
|
445
|
+
the browser's own menu) on right-click or long-press. The menu is anchored to
|
|
446
|
+
the element and closes on Escape, Tab, item selection, or any click outside.
|
|
447
|
+
|
|
448
|
+
## [MenuOptions](MenuOptions.md) · interface
|
|
449
|
+
|
|
450
|
+
Options for `menuButton` and `MainOptions.nav`.
|
|
451
|
+
|
|
452
|
+
## MenuEntry · type
|
|
453
|
+
|
|
454
|
+
An entry in a menu or sidebar nav list. Three forms:
|
|
455
|
+
- `MenuItem` — a clickable/linkable row with label and optional icon.
|
|
456
|
+
- `MenuSeparator` — a visual divider (`{ separator: true }`).
|
|
457
|
+
- A slot (string or draw function) — renders custom content (section header,
|
|
458
|
+
avatar, search box, …). Skipped by keyboard navigation.
|
|
459
|
+
|
|
460
|
+
**Type:** `MenuItem | MenuSeparator | Slot`
|
|
461
|
+
|
|
462
|
+
## [MenuItem](MenuItem.md) · interface
|
|
463
|
+
|
|
464
|
+
A clickable item in a menu or sidebar nav.
|
|
465
|
+
|
|
466
|
+
## MenuSeparator · interface
|
|
467
|
+
|
|
468
|
+
A visual divider between groups of items.
|
|
469
|
+
|
|
470
|
+
### menuSeparator.separator · member
|
|
471
|
+
|
|
472
|
+
**Type:** `true`
|
|
473
|
+
|
|
474
|
+
## [FloatingMenuOptions](FloatingMenuOptions.md) · interface
|
|
475
|
+
|
|
476
|
+
Options for `showFloatingMenu`.
|
|
477
|
+
|
|
478
|
+
## ContextMenuOptions · type
|
|
479
|
+
|
|
480
|
+
Options for `addContextMenu` — like `FloatingMenuOptions`, but
|
|
481
|
+
the anchor is the element the handler is attached to.
|
|
482
|
+
|
|
483
|
+
**Type:** `Omit<FloatingMenuOptions, "anchor">`
|
|
484
|
+
|
|
485
|
+
## [dialog](dialog.md) · function
|
|
486
|
+
|
|
487
|
+
A dialog rendered into `document.body` via `A.mount`, with a dimming backdrop
|
|
488
|
+
that fades in and out. Returns a `Promise<void>` that resolves when the dialog
|
|
489
|
+
closes. Lifecycle is also tied to the parent reactive scope — when that scope
|
|
490
|
+
is cleaned up the dialog disappears and the promise resolves.
|
|
491
|
+
|
|
492
|
+
## [alert](alert.md) · function
|
|
493
|
+
|
|
494
|
+
Shows a message dialog with a single OK button. Returns a `Promise<void>`
|
|
495
|
+
that resolves when the user dismisses it.
|
|
496
|
+
|
|
497
|
+
## [confirm](confirm.md) · function
|
|
498
|
+
|
|
499
|
+
Shows a confirmation dialog with Cancel and OK buttons. Returns a
|
|
500
|
+
`Promise<boolean>` — `true` if the user clicked OK, `false` otherwise.
|
|
501
|
+
|
|
502
|
+
## [prompt](prompt.md) · function
|
|
503
|
+
|
|
504
|
+
Shows a prompt dialog with a text input. Returns a `Promise<string | null>` —
|
|
505
|
+
the entered string if the user confirmed, or `null` if cancelled.
|
|
506
|
+
|
|
507
|
+
## [DialogOptions](DialogOptions.md) · interface
|
|
508
|
+
|
|
509
|
+
Options for `dialog`.
|
|
510
|
+
|
|
511
|
+
## [select](select.md) · function
|
|
512
|
+
|
|
513
|
+
A single-select dropdown backed by a native `<select>` element. Looks like the
|
|
514
|
+
other Staffa inputs but delegates all focus management, keyboard navigation, and
|
|
515
|
+
mobile-native picker behaviour to the browser.
|
|
516
|
+
|
|
517
|
+
## [SelectOptions](SelectOptions.md) · interface
|
|
518
|
+
|
|
519
|
+
Options for `select`.
|
|
520
|
+
|
|
521
|
+
## SelectOptionInput · type
|
|
522
|
+
|
|
523
|
+
A selectable option: a bare string, or a `{ value, label }` pair.
|
|
524
|
+
|
|
525
|
+
**Type:** `string | { value: string; label?: string }`
|
|
526
|
+
|
|
527
|
+
## [tabs](tabs.md) · function
|
|
528
|
+
|
|
529
|
+
A tabbed view. Renders an ARIA `tablist` of buttons and a single live panel
|
|
530
|
+
for the selected tab. Supports keyboard navigation (left/right/home/end).
|
|
531
|
+
|
|
532
|
+
## [Tab](Tab.md) · interface
|
|
533
|
+
|
|
534
|
+
A single tab definition.
|
|
535
|
+
|
|
536
|
+
## [TabsOptions](TabsOptions.md) · interface
|
|
537
|
+
|
|
538
|
+
Options for `tabs`.
|
|
539
|
+
|
|
540
|
+
## [textarea](textarea.md) · function
|
|
541
|
+
|
|
542
|
+
A multi-line text input. Shares the field chrome and styling of
|
|
543
|
+
`textline`.
|
|
544
|
+
|
|
545
|
+
## [TextareaOptions](TextareaOptions.md) · interface
|
|
546
|
+
|
|
547
|
+
Options for `textarea`.
|
|
548
|
+
|
|
549
|
+
## [textline](textline.md) · function
|
|
550
|
+
|
|
551
|
+
A single-line text input — covering text, passwords, numbers, email, dates and
|
|
552
|
+
the other line-oriented `<input>` types.
|
|
553
|
+
|
|
554
|
+
## [TextlineOptions](TextlineOptions.md) · interface
|
|
555
|
+
|
|
556
|
+
Options for `textline`.
|
|
557
|
+
|
|
558
|
+
## [TextlineType](TextlineType.md) · type
|
|
559
|
+
|
|
560
|
+
The `<input>` types `textline` supports. Deliberately excludes types
|
|
561
|
+
that need their own widget (`checkbox`, `radio`, `color`, `range`, `file`,
|
|
562
|
+
`button`, ...) — use the dedicated components for those.
|
|
563
|
+
|
|
564
|
+
## [toast](toast.md) · function
|
|
565
|
+
|
|
566
|
+
Show a toast notification. Returns a `dismiss()` function to remove it
|
|
567
|
+
programmatically. Auto-dismisses after `duration` ms (default 6 000).
|
|
568
|
+
|
|
569
|
+
## [ToastOptions](ToastOptions.md) · interface
|
|
570
|
+
|
|
571
|
+
Options for `toast`.
|
|
572
|
+
|
|
573
|
+
## [addTooltip](addTooltip.md) · function
|
|
574
|
+
|
|
575
|
+
Attaches a tooltip to the current element: adds hover/focus handlers via
|
|
576
|
+
`A` so the tip appears when the element is hovered or keyboard-focused.
|
|
577
|
+
The tip panel is rendered into `document.body` via a portal, so it is never
|
|
578
|
+
clipped by `overflow:hidden` ancestors. Position is computed from the
|
|
579
|
+
element's bounding rect and automatically flips when near the viewport edge.
|
|
580
|
+
|
|
581
|
+
## [TooltipOptions](TooltipOptions.md) · interface
|
|
582
|
+
|
|
583
|
+
Options for `addTooltip`.
|
|
584
|
+
|
|
585
|
+
## [FieldOptions](FieldOptions.md) · interface
|
|
586
|
+
|
|
587
|
+
Options shared by all *form field* components (textline, textarea, checkbox,
|
|
588
|
+
autocomplete, ...).
|
|
589
|
+
|
|
590
|
+
## [ContentOptions](ContentOptions.md) · interface
|
|
591
|
+
|
|
592
|
+
Options for components that wrap a single block of caller-provided content,
|
|
593
|
+
with an `attrs` escape hatch on the outermost element.
|
|
594
|
+
|
|
595
|
+
## Bindable · type
|
|
596
|
+
|
|
597
|
+
A reactive "value box", such as the result of `A.proxy(x)` or `A.ref(obj, key)`.
|
|
598
|
+
|
|
599
|
+
**Type:** `{ value: T }`
|
|
600
|
+
|
|
601
|
+
## [Slot](Slot.md) · type
|
|
602
|
+
|
|
603
|
+
Something that renders a small piece of content: either a plain string or a
|
|
604
|
+
draw function (for icons, badges, custom markup, ...).
|
|
605
|
+
|
|
606
|
+
## [Attributes](Attributes.md) · type
|
|
607
|
+
|
|
608
|
+
Shared building blocks for the Staffa component library.
|
|
609
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
## SelectOptions · interface
|
|
2
|
+
|
|
3
|
+
Options for `select`.
|
|
4
|
+
|
|
5
|
+
### selectOptions.options · member
|
|
6
|
+
|
|
7
|
+
The list of selectable options.
|
|
8
|
+
|
|
9
|
+
**Type:** `SelectOptionInput[] | (() => SelectOptionInput[])`
|
|
10
|
+
|
|
11
|
+
### selectOptions.bind · member
|
|
12
|
+
|
|
13
|
+
Two-way binding for the selected value string (`""` when nothing is selected).
|
|
14
|
+
|
|
15
|
+
**Type:** `Bindable<string>`
|
|
16
|
+
|
|
17
|
+
### selectOptions.placeholder · member
|
|
18
|
+
|
|
19
|
+
Placeholder option shown when nothing is selected yet.
|
|
20
|
+
|
|
21
|
+
**Type:** `string`
|
package/skill/Slot.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
## Slot · type
|
|
2
|
+
|
|
3
|
+
Something that renders a small piece of content: either a plain string or a
|
|
4
|
+
draw function (for icons, badges, custom markup, ...).
|
|
5
|
+
|
|
6
|
+
A string is drawn as **rich text** (see `drawSlot`): Aberdeen's `rich`
|
|
7
|
+
markup is applied, so `*italic*`, `**bold**`, `` `code` `` and
|
|
8
|
+
`[links](/path)` render as inline elements (text is safely escaped).
|
|
9
|
+
|
|
10
|
+
The optional `Args` type parameter lets a slot's draw-function receive
|
|
11
|
+
arguments — e.g. a dialog body is a `Slot<[close: () => void]>`.
|
|
12
|
+
|
|
13
|
+
**Type:** `string | ((...args: Args) => void)`
|
package/skill/Tab.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
## Tab · interface
|
|
2
|
+
|
|
3
|
+
A single tab definition.
|
|
4
|
+
|
|
5
|
+
### tab.id · member
|
|
6
|
+
|
|
7
|
+
Stable id used as the selection value. Falls back to the array index.
|
|
8
|
+
|
|
9
|
+
**Type:** `string`
|
|
10
|
+
|
|
11
|
+
### tab.label · member
|
|
12
|
+
|
|
13
|
+
Tab label shown in the tab strip.
|
|
14
|
+
|
|
15
|
+
**Type:** `Slot`
|
|
16
|
+
|
|
17
|
+
### tab.icon · member
|
|
18
|
+
|
|
19
|
+
Optional leading icon.
|
|
20
|
+
|
|
21
|
+
**Type:** `Slot`
|
|
22
|
+
|
|
23
|
+
### tab.content · member
|
|
24
|
+
|
|
25
|
+
Content rendered in the panel when this tab is active. A string is rendered as rich text.
|
|
26
|
+
|
|
27
|
+
**Type:** `Slot`
|
|
28
|
+
|
|
29
|
+
### tab.disabled · member
|
|
30
|
+
|
|
31
|
+
Disables selecting this tab.
|
|
32
|
+
|
|
33
|
+
**Type:** `boolean`
|