staffa 0.1.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.
Files changed (53) hide show
  1. package/README.md +186 -0
  2. package/dist/components/autocomplete.d.ts +44 -0
  3. package/dist/components/autocomplete.js +250 -0
  4. package/dist/components/box.d.ts +31 -0
  5. package/dist/components/box.js +48 -0
  6. package/dist/components/button.d.ts +67 -0
  7. package/dist/components/button.js +83 -0
  8. package/dist/components/buttonGroup.d.ts +31 -0
  9. package/dist/components/buttonGroup.js +46 -0
  10. package/dist/components/checkbox.d.ts +24 -0
  11. package/dist/components/checkbox.js +63 -0
  12. package/dist/components/dialog.d.ts +97 -0
  13. package/dist/components/dialog.js +214 -0
  14. package/dist/components/field.d.ts +50 -0
  15. package/dist/components/field.js +78 -0
  16. package/dist/components/form.d.ts +42 -0
  17. package/dist/components/form.js +59 -0
  18. package/dist/components/main.d.ts +47 -0
  19. package/dist/components/main.js +89 -0
  20. package/dist/components/modal.d.ts +2 -0
  21. package/dist/components/modal.js +2 -0
  22. package/dist/components/select.d.ts +27 -0
  23. package/dist/components/select.js +57 -0
  24. package/dist/components/tabs.d.ts +41 -0
  25. package/dist/components/tabs.js +108 -0
  26. package/dist/components/textarea.d.ts +31 -0
  27. package/dist/components/textarea.js +49 -0
  28. package/dist/components/textline.d.ts +38 -0
  29. package/dist/components/textline.js +32 -0
  30. package/dist/core.d.ts +73 -0
  31. package/dist/core.js +18 -0
  32. package/dist/index.d.ts +83 -0
  33. package/dist/index.js +72 -0
  34. package/dist/skye.esm.js +1 -0
  35. package/dist/theme.d.ts +87 -0
  36. package/dist/theme.js +135 -0
  37. package/package.json +35 -0
  38. package/src/components/autocomplete.ts +272 -0
  39. package/src/components/box.ts +62 -0
  40. package/src/components/button.ts +137 -0
  41. package/src/components/buttonGroup.ts +63 -0
  42. package/src/components/checkbox.ts +70 -0
  43. package/src/components/dialog.ts +257 -0
  44. package/src/components/field.ts +115 -0
  45. package/src/components/form.ts +84 -0
  46. package/src/components/main.ts +110 -0
  47. package/src/components/select.ts +75 -0
  48. package/src/components/tabs.ts +144 -0
  49. package/src/components/textarea.ts +68 -0
  50. package/src/components/textline.ts +66 -0
  51. package/src/core.ts +88 -0
  52. package/src/index.ts +98 -0
  53. package/src/theme.ts +195 -0
@@ -0,0 +1,38 @@
1
+ import type { Bindable } from "../core.js";
2
+ import { type FieldOptions } from "./field.js";
3
+ /**
4
+ * The `<input>` types {@link textline} supports. Deliberately excludes types
5
+ * that need their own widget (`checkbox`, `radio`, `color`, `range`, `file`,
6
+ * `button`, ...) — use the dedicated components for those.
7
+ */
8
+ export type TextlineType = "text" | "password" | "email" | "number" | "tel" | "url" | "search" | "date" | "time" | "datetime-local" | "month" | "week";
9
+ /** Options for {@link textline}. */
10
+ export interface TextlineOptions extends FieldOptions {
11
+ /** Input type. Defaults to `"text"`. */
12
+ type?: TextlineType;
13
+ /** Placeholder text. */
14
+ placeholder?: string;
15
+ /** Two-way binding target (e.g. `A.ref($user, "name")`). */
16
+ bind?: Bindable<string | number>;
17
+ /** Static initial value (use {@link TextlineOptions.bind | bind} for reactivity). */
18
+ value?: string | number;
19
+ /** Autocomplete hint passed to the native `autocomplete` attribute. */
20
+ autocomplete?: string;
21
+ /** Fired on every `input` event with the native event. */
22
+ input?: (event: Event) => void;
23
+ /** Fired on `change` (commit) with the native event. */
24
+ change?: (event: Event) => void;
25
+ }
26
+ /**
27
+ * A single-line text input — covering text, passwords, numbers, email, dates and
28
+ * the other line-oriented `<input>` types.
29
+ *
30
+ * Renders inside the standard {@link drawField} chrome (label, control,
31
+ * help/error), so it aligns cleanly inside a {@link form}.
32
+ *
33
+ * @example
34
+ * ```ts
35
+ * S.textline({ label: "Email", type: "email", required: true, bind: A.ref($user, "email") });
36
+ * ```
37
+ */
38
+ export declare function textline(opts?: TextlineOptions): void;
@@ -0,0 +1,32 @@
1
+ import A from "aberdeen";
2
+ import { applyControlAttrs, drawField } from "./field.js";
3
+ /**
4
+ * A single-line text input — covering text, passwords, numbers, email, dates and
5
+ * the other line-oriented `<input>` types.
6
+ *
7
+ * Renders inside the standard {@link drawField} chrome (label, control,
8
+ * help/error), so it aligns cleanly inside a {@link form}.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * S.textline({ label: "Email", type: "email", required: true, bind: A.ref($user, "email") });
13
+ * ```
14
+ */
15
+ export function textline(opts = {}) {
16
+ drawField(opts, (id, isInvalid) => {
17
+ A("input.S_input", opts.control, () => {
18
+ A("type=", opts.type ?? "text");
19
+ if (opts.placeholder != null)
20
+ A("placeholder=", opts.placeholder);
21
+ if (opts.autocomplete != null)
22
+ A("autocomplete=", opts.autocomplete);
23
+ if (opts.value != null && !opts.bind)
24
+ A("value=", opts.value);
25
+ if (opts.input)
26
+ A("input=", opts.input);
27
+ if (opts.change)
28
+ A("change=", opts.change);
29
+ applyControlAttrs(opts, id, isInvalid, opts.bind);
30
+ });
31
+ });
32
+ }
package/dist/core.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Shared building blocks for the Skye component library.
3
+ *
4
+ * Every component in Skye is "just an Aberdeen draw function": a plain function
5
+ * that takes a single, strongly typed options object and emits DOM through
6
+ * Aberdeen's {@link A} function. This module defines the option-type hierarchy
7
+ * that all components build on, plus a couple of tiny helpers.
8
+ */
9
+ /**
10
+ * An Aberdeen attribute/style/class string, e.g. `"display:flex gap:$3 .my-class"`.
11
+ *
12
+ * These strings are passed straight through to {@link A} as positional
13
+ * arguments, so they accept the full Aberdeen shorthand syntax: CSS shortcuts
14
+ * (`p`, `mt`, `bg`, `r`, ...), spacing variables (`$1`..`$12`), CSS custom
15
+ * properties (`$sPrimary`), classes (`.foo`) and attributes (`aria-label=Hi`).
16
+ *
17
+ * Note: because Aberdeen interprets a leading bare word as an element name, write
18
+ * `display:flex` rather than just `flex`.
19
+ */
20
+ export type Styling = string;
21
+ /** A reactive "value box", such as the result of `A.proxy(x)` or `A.ref(obj, key)`. */
22
+ export type Bindable<T> = {
23
+ value: T;
24
+ };
25
+ /** A content function. It runs inside the relevant element's reactive scope. */
26
+ export type Content = () => void;
27
+ /**
28
+ * Something that renders a small piece of content: either a plain string (drawn
29
+ * as a text node) or a draw function (for icons, badges, custom markup, ...).
30
+ */
31
+ export type Slot = string | Content;
32
+ /**
33
+ * Options shared by *every* Skye component.
34
+ *
35
+ * The {@link BaseOptions.root | root} string is applied to the outermost element
36
+ * of the widget, letting callers tweak layout, spacing or add classes without
37
+ * forking the component.
38
+ */
39
+ export interface BaseOptions {
40
+ /**
41
+ * Aberdeen attr/style string applied to the widget's root element.
42
+ *
43
+ * It is passed as a positional argument to {@link A}, so a *change* to it on a
44
+ * proxied options object re-runs the caller's scope (recreating the widget).
45
+ * That's fine for `root` — it rarely changes at runtime.
46
+ */
47
+ root?: Styling;
48
+ }
49
+ /**
50
+ * Options for components that wrap a single block of caller-provided content.
51
+ *
52
+ * Such components render an *inner* element (the one that actually holds the
53
+ * children) which is given sensible default padding and `gap` in CSS. Override
54
+ * those via {@link ContentOptions.inner | inner}, whose declarations win because
55
+ * they're applied as inline styles.
56
+ */
57
+ export interface ContentOptions extends BaseOptions {
58
+ /** Draws the children of this component. */
59
+ content?: Content;
60
+ /**
61
+ * Aberdeen attr/style string applied to the inner (content-holding) element.
62
+ * Add `display:flex` here if you want the children laid out as a flex
63
+ * row/column.
64
+ */
65
+ inner?: Styling;
66
+ }
67
+ /** Generates a process-unique id, used to wire `<label for>` to its control. */
68
+ export declare function uniqueId(prefix?: string): string;
69
+ /**
70
+ * Draw a {@link Slot} into the current element: call it if it's a function,
71
+ * otherwise emit it as a text node.
72
+ */
73
+ export declare function drawSlot(slot: Slot | undefined): void;
package/dist/core.js ADDED
@@ -0,0 +1,18 @@
1
+ import A from "aberdeen";
2
+ let idCounter = 0;
3
+ /** Generates a process-unique id, used to wire `<label for>` to its control. */
4
+ export function uniqueId(prefix = "s") {
5
+ return `${prefix}-${++idCounter}`;
6
+ }
7
+ /**
8
+ * Draw a {@link Slot} into the current element: call it if it's a function,
9
+ * otherwise emit it as a text node.
10
+ */
11
+ export function drawSlot(slot) {
12
+ if (slot == null)
13
+ return;
14
+ if (typeof slot === "function")
15
+ slot();
16
+ else
17
+ A("#", slot);
18
+ }
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Skye — a small, opinionated component library for the
3
+ * {@link https://aberdeenjs.org | Aberdeen} reactive UI library.
4
+ *
5
+ * Import the default `S` object and call its component functions:
6
+ *
7
+ * ```ts
8
+ * import S from "skye";
9
+ *
10
+ * S.main({
11
+ * title: "Hello",
12
+ * maxWidth: "48rem",
13
+ * content: () => {
14
+ * S.box({ header: "Login", content: () => {
15
+ * S.form({
16
+ * content: () => {
17
+ * S.textline({ label: "Email", type: "email", bind: A.ref($u, "email") });
18
+ * S.checkbox({ label: "Remember me", bind: A.ref($u, "remember") });
19
+ * },
20
+ * actions: () => S.button({ text: "Sign in", type: "submit" }),
21
+ * });
22
+ * }});
23
+ * },
24
+ * });
25
+ * ```
26
+ *
27
+ * Every component takes a single typed options object (see each function's
28
+ * docs). The options object — or parts of it — may be an Aberdeen proxy, in
29
+ * which case the component re-renders the affected parts in place when you
30
+ * mutate it. See `AGENTS.md` for the design philosophy.
31
+ */
32
+ import { setDarkMode, getDarkMode } from "./theme.js";
33
+ import { autocomplete } from "./components/autocomplete.js";
34
+ import { box } from "./components/box.js";
35
+ import { button } from "./components/button.js";
36
+ import { buttonGroup } from "./components/buttonGroup.js";
37
+ import { checkbox } from "./components/checkbox.js";
38
+ import { form } from "./components/form.js";
39
+ import { main } from "./components/main.js";
40
+ import { dialog, alert, confirm, prompt } from "./components/dialog.js";
41
+ import { select } from "./components/select.js";
42
+ import { tabs } from "./components/tabs.js";
43
+ import { textarea } from "./components/textarea.js";
44
+ import { textline } from "./components/textline.js";
45
+ /** The Skye component namespace. */
46
+ export declare const S: {
47
+ main: typeof main;
48
+ box: typeof box;
49
+ dialog: typeof dialog;
50
+ alert: typeof alert;
51
+ confirm: typeof confirm;
52
+ prompt: typeof prompt;
53
+ form: typeof form;
54
+ textline: typeof textline;
55
+ textarea: typeof textarea;
56
+ checkbox: typeof checkbox;
57
+ tabs: typeof tabs;
58
+ button: typeof button;
59
+ buttonGroup: typeof buttonGroup;
60
+ autocomplete: typeof autocomplete;
61
+ select: typeof select;
62
+ darkTheme: import("./theme.js").Theme;
63
+ lightTheme: import("./theme.js").Theme;
64
+ setDarkMode: typeof setDarkMode;
65
+ getDarkMode: typeof getDarkMode;
66
+ };
67
+ export default S;
68
+ export { type Theme, darkTheme, lightTheme, setDarkMode, getDarkMode } from "./theme.js";
69
+ export type { BaseOptions, ContentOptions, Bindable, Content, Slot, Styling, } from "./core.js";
70
+ export { drawSlot, uniqueId } from "./core.js";
71
+ export type { FieldOptions } from "./components/field.js";
72
+ export type { MainOptions } from "./components/main.js";
73
+ export type { BoxOptions } from "./components/box.js";
74
+ export type { FormOptions } from "./components/form.js";
75
+ export type { TextlineOptions, TextlineType } from "./components/textline.js";
76
+ export type { TextareaOptions } from "./components/textarea.js";
77
+ export type { CheckboxOptions } from "./components/checkbox.js";
78
+ export type { Tab, TabsOptions } from "./components/tabs.js";
79
+ export type { ButtonOptions, ButtonVariant, ButtonColor } from "./components/button.js";
80
+ export type { ButtonGroupOptions } from "./components/buttonGroup.js";
81
+ export type { AutocompleteOptions, AutocompleteOptionInput } from "./components/autocomplete.js";
82
+ export type { SelectOptions, SelectOptionInput } from "./components/select.js";
83
+ export type { DialogOptions } from "./components/dialog.js";
package/dist/index.js ADDED
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Skye — a small, opinionated component library for the
3
+ * {@link https://aberdeenjs.org | Aberdeen} reactive UI library.
4
+ *
5
+ * Import the default `S` object and call its component functions:
6
+ *
7
+ * ```ts
8
+ * import S from "skye";
9
+ *
10
+ * S.main({
11
+ * title: "Hello",
12
+ * maxWidth: "48rem",
13
+ * content: () => {
14
+ * S.box({ header: "Login", content: () => {
15
+ * S.form({
16
+ * content: () => {
17
+ * S.textline({ label: "Email", type: "email", bind: A.ref($u, "email") });
18
+ * S.checkbox({ label: "Remember me", bind: A.ref($u, "remember") });
19
+ * },
20
+ * actions: () => S.button({ text: "Sign in", type: "submit" }),
21
+ * });
22
+ * }});
23
+ * },
24
+ * });
25
+ * ```
26
+ *
27
+ * Every component takes a single typed options object (see each function's
28
+ * docs). The options object — or parts of it — may be an Aberdeen proxy, in
29
+ * which case the component re-renders the affected parts in place when you
30
+ * mutate it. See `AGENTS.md` for the design philosophy.
31
+ */
32
+ // Importing the theme module installs spacing vars, the reactive theme and the
33
+ // base stylesheet. Customise by mutating S.darkTheme / S.lightTheme.
34
+ import { darkTheme, lightTheme, setDarkMode, getDarkMode } from "./theme.js";
35
+ import { autocomplete } from "./components/autocomplete.js";
36
+ import { box } from "./components/box.js";
37
+ import { button } from "./components/button.js";
38
+ import { buttonGroup } from "./components/buttonGroup.js";
39
+ import { checkbox } from "./components/checkbox.js";
40
+ import { form } from "./components/form.js";
41
+ import { main } from "./components/main.js";
42
+ import { dialog, alert, confirm, prompt } from "./components/dialog.js";
43
+ import { select } from "./components/select.js";
44
+ import { tabs } from "./components/tabs.js";
45
+ import { textarea } from "./components/textarea.js";
46
+ import { textline } from "./components/textline.js";
47
+ /** The Skye component namespace. */
48
+ export const S = {
49
+ main,
50
+ box,
51
+ dialog,
52
+ alert,
53
+ confirm,
54
+ prompt,
55
+ form,
56
+ textline,
57
+ textarea,
58
+ checkbox,
59
+ tabs,
60
+ button,
61
+ buttonGroup,
62
+ autocomplete,
63
+ select,
64
+ darkTheme,
65
+ lightTheme,
66
+ setDarkMode,
67
+ getDarkMode,
68
+ };
69
+ export default S;
70
+ // Re-export theming and shared types for advanced use.
71
+ export { darkTheme, lightTheme, setDarkMode, getDarkMode } from "./theme.js";
72
+ export { drawSlot, uniqueId } from "./core.js";
@@ -0,0 +1 @@
1
+ import w from"aberdeen";var P=w.proxy({sBg:"#0e1015",sSurface:"#181b22",sSurfaceHi:"#222632",sFg:"#e8eaf0",sFgMuted:"#a6acba",sFgFaint:"#6b7280",sBorder:"#2c313c",sBorderStrong:"#3c4352",sPrimary:"#8b7bff",sPrimaryHover:"#a99dff",sPrimaryFg:"#0c0a1a",sDanger:"#ff6b6b",sSuccess:"#46d39a",sWarning:"#fbbf24",sFocus:"rgba(139, 123, 255, 0.45)",sRadius:"10px",sRadiusLg:"16px",sShadow:"0 8px 30px rgba(0, 0, 0, 0.45)"}),D=w.proxy({sBg:"#f3f4f8",sSurface:"#ffffff",sSurfaceHi:"#eceef4",sFg:"#1b1e27",sFgMuted:"#5b6273",sFgFaint:"#9aa1b2",sBorder:"#e2e5ee",sBorderStrong:"#c7ccda",sPrimary:"#6c5ce7",sPrimaryHover:"#5847d4",sPrimaryFg:"#ffffff",sDanger:"#e23b3b",sSuccess:"#1f9d6b",sWarning:"#d97706",sFocus:"rgba(108, 92, 231, 0.35)",sRadius:"10px",sRadiusLg:"16px",sShadow:"0 6px 24px rgba(20, 24, 40, 0.12)"}),R="skye:darkMode",V=w.proxy({value:ae()});function ae(){try{let e=localStorage.getItem(R);if(e==="dark")return!0;if(e==="light")return!1}catch{}}function j(e){V.value=e;try{e===void 0?localStorage.removeItem(R):localStorage.setItem(R,e?"dark":"light")}catch{}}function z(e=!1){let t=V.value;return t===void 0&&!e?w.darkMode():t}w.setSpacingCssVars();w(()=>{w.merge(w.cssVars,z()?P:D)});w.insertGlobalCss({"*, *::before, *::after":"box-sizing:border-box",html:"text-size-adjust:100%",body:"m:0 bg:$sBg fg:$sFg line-height:1.5 font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; -webkit-font-smoothing:antialiased",a:"fg:$sPrimary text-decoration:underline text-underline-offset:2px","a:hover":"fg:$sPrimaryHover","input, button, textarea, select":"font:inherit color:inherit","code, kbd, samp, pre":"font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;",code:"bg:$sSurfaceHi padding: 0.12em 0.34em; r:4px font-size:0.9em",pre:"bg:$sSurface p:$3 r:$sRadius overflow:auto","pre code":"bg:transparent p:0","img, svg, video, canvas":"max-width:100% h:auto",hr:"border:0 border-top: 1px solid $sBorder; margin: $3 0;","::placeholder":"fg:$sFgFaint opacity:1",":focus-visible":"outline: 2px solid $sFocus; outline-offset:2px"});import o from"aberdeen";import le from"aberdeen";var se=0;function O(e="s"){return`${e}-${++se}`}function c(e){e!=null&&(typeof e=="function"?e():le("#",e))}import b from"aberdeen";b.insertGlobalCss({".S_field":{"&":"display:flex flex-direction:column gap:$1","> label":"font-weight:600 font-size:0.9em fg:$sFg user-select:none"},".S_req":"fg:$sDanger margin-left:2px",".S_help":"font-size:0.82em fg:$sFgMuted",".S_error":"font-size:0.82em fg:$sDanger",".S_input":{"&":"w:100% bg:$sSurface fg:$sFg border: 1px solid $sBorder; r:$sRadius padding: 0.55em 0.7em; transition: border-color 0.15s, box-shadow 0.15s;","&:hover:not(:disabled)":"border-color:$sBorderStrong","&:focus-visible":"border-color:$sPrimary box-shadow: 0 0 0 3px $sFocus; outline:none","&:disabled":"opacity:0.6 cursor:not-allowed","&[aria-invalid=true]":"border-color:$sDanger"}});function C(e,t){let n=e.id??O("field"),i=()=>!!e.error;b("div.S_field",e.root,()=>{b(()=>{e.label!=null&&b(`label for=${n}`,()=>{c(e.label),e.required&&b("span.S_req aria-hidden=true #*")})}),t(n,i),b(()=>{e.help!=null&&!e.error&&b("div.S_help",()=>c(e.help))}),b(()=>{e.error&&b("div.S_error role=alert #",e.error)})})}function I(e,t,n,i){b(`id=${t}`),e.name&&b(`name=${e.name}`),b(()=>{e.disabled&&b("disabled=true")}),b(()=>{e.required&&b("aria-required=true")}),b(()=>b("aria-invalid=",n()?"true":"false")),i&&b("bind=",i)}o.insertGlobalCss({".S_ac":{"&":"position:relative","> .S_control":"display:flex flex-wrap:wrap align-items:center gap:$1 bg:$sSurface fg:$sFg border: 1px solid $sBorder; r:$sRadius padding: 0.3em 0.4em; cursor:text; transition: border-color 0.15s, box-shadow 0.15s;","> .S_control:hover":"border-color:$sBorderStrong","> .S_control:focus-within":"border-color:$sPrimary box-shadow: 0 0 0 3px $sFocus;","&[aria-invalid=true] > .S_control":"border-color:$sDanger",".S_chip":"display:inline-flex align-items:center gap:$1 font-size:0.85em bg:$sSurfaceHi border: 1px solid $sBorder; r:$sRadius padding: 0.1em 0.2em 0.1em 0.5em;",".S_chip > button":"cursor:pointer border:0 background:transparent fg:$sFgMuted font-size:1.1em line-height:1 padding: 0 0.2em; r:4px",".S_chip > button:hover":"fg:$sFg background:$sBorder",input:"flex:1 min-width:6ch border:0 background:transparent color:inherit outline:none padding:0.25em","> .S_menu":"position:absolute top:100% left:0 right:0 z-index:20 margin-top:4px max-height:15rem overflow-y:auto list-style:none p:$1 margin-bottom:0 bg:$sSurface border: 1px solid $sBorder; r:$sRadius box-shadow:$sShadow",".S_option":"padding: 0.45em 0.6em; r:6px cursor:pointer",".S_option[aria-selected=true]":"background:$sSurfaceHi",".S_add":"fg:$sPrimary font-style:italic",".S_empty":"padding: 0.45em 0.6em; fg:$sFgMuted"}});function de(e){return typeof e=="string"?{value:e,label:e}:{value:e.value,label:e.label??e.value}}function W(e){let t=O("ac-menu"),n=o.proxy({query:"",open:!1,active:0}),i=()=>(typeof e.options=="function"?e.options():e.options).map(de),r=()=>{let l=e.bind?.value;return l==null||l===""?[]:Array.isArray(l)?l:[l]},s=l=>i().find(h=>h.value===l)?.label??l;if(!e.multi){let l=e.bind?o.peek(e.bind,"value"):void 0;typeof l=="string"&&l&&(n.query=o.peek(()=>s(l)))}let a=()=>{let l=new Set(r()),h=i();e.multi&&(h=h.filter(g=>!l.has(g.value)));let m=n.query.trim().toLowerCase();return m&&(h=h.filter(g=>g.label.toLowerCase().includes(m))),h},d=(l,h)=>{if(e.multi){let m=Array.isArray(e.bind?.value)?[...e.bind.value]:[];m.includes(l)||m.push(l),e.bind&&(e.bind.value=m),n.query=""}else e.bind&&(e.bind.value=l),n.query=s(l),n.open=!1;n.active=0,h?.focus()},x=l=>{if(!e.bind)return;let h=e.bind.value??[];e.bind.value=h.filter(m=>m!==l)};C(e,(l,h)=>{o("div.S_ac",e.control,()=>{o(()=>o("aria-invalid=",h()?"true":"false"));let m;o("div.S_control",()=>{o("click=",()=>m?.focus()),o(()=>{if(e.multi)for(let g of r())o("span.S_chip",()=>{o("span #",o.peek(()=>s(g))),o("button type=button aria-label=",`Remove ${g}`,()=>{o("#\xD7"),o("click=",y=>{y.stopPropagation(),x(g),m?.focus()})})})}),m=o("input type=text role=combobox autocomplete=off",()=>{o(`id=${l} aria-controls=${t} aria-autocomplete=list`),e.placeholder!=null&&o("placeholder=",e.placeholder),e.disabled&&o("disabled=true"),e.required&&o("aria-required=true"),o("bind=",o.ref(n,"query")),o(()=>o("aria-expanded=",n.open?"true":"false")),o(()=>{let y=a()[n.active];o("aria-activedescendant=",n.open&&y?`${t}-opt-${n.active}`:"")}),o("input=",()=>{n.open=!0,n.active=0}),o("focus=",()=>{n.open=!0}),o("blur=",()=>{setTimeout(()=>L(),150)}),o("keydown=",g=>B(g,m))})}),o(()=>{if(!n.open)return;let g=a(),y=n.query.trim(),K=e.allowCustom!==!1&&y!==""&&!g.some(E=>E.label.toLowerCase()===y.toLowerCase());o("ul.S_menu role=listbox",`id=${t}`,()=>{g.forEach((E,G)=>{o("li.S_option role=option",`id=${t}-opt-${G}`,()=>{o(()=>o("aria-selected=",n.active===G?"true":"false")),o("#",E.label),o("mousedown=",ie=>ie.preventDefault()),o("click=",()=>d(E.value,m)),o("mousemove=",()=>{n.active=G})})}),K&&o("li.S_option.S_add role=option",()=>{o("#",`Add "${y}"`),o("mousedown=",E=>E.preventDefault()),o("click=",()=>d(y,m))}),g.length===0&&!K&&o("li.S_empty #No matches")})}),o(()=>{if(e.name)if(e.multi)for(let g of r())o("input type=hidden",()=>{o("name=",e.name),o("value=",g)});else o("input type=hidden",()=>{o("name=",e.name),o("value=",r()[0]??"")})})})});function B(l,h){let m=a(),g=m.length-1;if(l.key==="ArrowDown")l.preventDefault(),n.open=!0,n.active=Math.min(g,n.active+1);else if(l.key==="ArrowUp")l.preventDefault(),n.active=Math.max(0,n.active-1);else if(l.key==="Enter"){l.preventDefault();let y=m[n.active];y?d(y.value,h):e.allowCustom!==!1&&n.query.trim()?d(n.query.trim(),h):n.open&&(n.open=!1)}else if(l.key==="Escape")n.open=!1,e.multi||(n.query=s(r()[0]??""));else if(l.key==="Backspace"&&e.multi&&n.query===""){let y=r();y.length&&x(y[y.length-1])}}function L(){n.open=!1,e.multi?n.query="":e.allowCustom!==!1&&n.query.trim()?d(n.query.trim()):n.query=s(r()[0]??"")}}import F from"aberdeen";F.insertGlobalCss({".S_box":{"&":"display:flex flex-direction:column bg:$sSurface border: 1px solid $sBorder; r:$sRadius overflow:hidden","> header":"display:flex align-items:center gap:$2 padding: $2 $3; bg:$sSurfaceHi border-bottom: 1px solid $sBorder; font-weight:600","> footer":"display:flex align-items:center gap:$2 padding: $2 $3; bg:$sSurfaceHi border-top: 1px solid $sBorder;","> div":"p:$3 gap:$3"}});function U(e={}){let t=typeof e=="function"?{content:e}:e;F("section.S_box",t.root,()=>{F(()=>{t.header!=null&&F("header",t.headerInner,()=>c(t.header))}),F("div",t.inner,()=>{t.content&&t.content()}),F(()=>{t.footer!=null&&F("footer",t.footerInner,()=>c(t.footer))})})}import k from"aberdeen";k.insertGlobalCss({".S_btn":{"&":"--c:$sPrimary --cfg:$sPrimaryFg display:inline-flex align-items:center justify-content:center gap:$2 font-weight:600 line-height:1.2 white-space:nowrap cursor:pointer text-decoration:none border: 1px solid transparent; r:$sRadius padding: 0.5em 1em; transition: background 0.15s, border-color 0.15s, filter 0.15s, box-shadow 0.15s;","&:focus-visible":"outline:none box-shadow: 0 0 0 3px $sFocus;","&:disabled, &[aria-disabled=true]":"opacity:0.45 cursor:not-allowed pointer-events:none filter:saturate(0.6)","&.S_neutral":"--c:$sBorderStrong --cfg:$sFg","&.S_danger":"--c:$sDanger --cfg:#fff","&.S_success":"--c:$sSuccess --cfg:#08110d","&.S_filled":"background:$c color:$cfg border-color:$c","&.S_filled:hover":"filter:brightness(1.1)","&.S_tonal":"color:$c background: color-mix(in srgb, $c 20%, transparent); border-color: color-mix(in srgb, $c 30%, transparent);","&.S_tonal:hover":"background: color-mix(in srgb, $c 30%, transparent);","&.S_outlined":"color:$c background:transparent border-color: color-mix(in srgb, $c 55%, $sBorder);","&.S_outlined:hover":"background: color-mix(in srgb, $c 12%, transparent);","&.S_sm":"padding: 0.32em 0.7em; font-size:0.85em","&.S_lg":"padding: 0.66em 1.3em; font-size:1.1em"}});function $(e={}){let t=typeof e=="string"?{text:e}:typeof e=="function"?{content:e}:e,n=t.href!=null?"a":"button",i=t.variant??"filled",r=t.color??"primary",s=t.size==="sm"||t.size==="lg"?`.S_${t.size}`:"",a=r==="primary"||r==="neutral"||r==="danger"||r==="success",d=a?`.S_${r}`:"",x=k(`${n}.S_btn.S_${i}${d}${s}`,t.root,t.inner,()=>{t.href!=null?(k(`href=${t.href} role=button`),t.disabled&&k("aria-disabled=true")):(k("type=",t.type??"button"),t.disabled&&k("disabled=true")),t.ariaLabel&&k("aria-label=",t.ariaLabel),t.click&&k("click=",t.click),c(t.icon),t.content?t.content():t.text!=null&&k("#",t.text)});!a&&x instanceof HTMLElement&&x.style.setProperty("--c",r.startsWith("$")?`var(--${r.slice(1)})`:r)}import Y from"aberdeen";Y.insertGlobalCss({".S_bgroup":{"&":"display:inline-flex align-items:stretch","&.S_spaced":"gap:$2 flex-wrap:wrap","&.S_vertical":"flex-direction:column","&.S_attached":"gap:0","&.S_attached:not(.S_vertical) > .S_btn:not(:first-child)":"margin-left:-1px","&.S_attached:not(.S_vertical) > .S_btn:not(:first-child):not(:last-child)":"r:0","&.S_attached:not(.S_vertical) > .S_btn:first-child:not(:last-child)":"border-top-right-radius:0 border-bottom-right-radius:0","&.S_attached:not(.S_vertical) > .S_btn:last-child:not(:first-child)":"border-top-left-radius:0 border-bottom-left-radius:0","&.S_attached.S_vertical > .S_btn:not(:first-child)":"margin-top:-1px","&.S_attached.S_vertical > .S_btn:not(:first-child):not(:last-child)":"r:0","&.S_attached.S_vertical > .S_btn:first-child:not(:last-child)":"border-bottom-left-radius:0 border-bottom-right-radius:0","&.S_attached.S_vertical > .S_btn:last-child:not(:first-child)":"border-top-left-radius:0 border-top-right-radius:0","&.S_attached > .S_btn:hover, &.S_attached > .S_btn:focus-visible":"z-index:1"}});function M(e={}){let n=`.S_${e.layout??"attached"}${e.vertical?".S_vertical":""}`;Y(`div.S_bgroup${n} role=group`,e.root,e.inner,()=>{if(e.buttons)for(let i of e.buttons)$(i);e.content&&e.content()})}import p from"aberdeen";p.insertGlobalCss({".S_check":{"&":"display:flex flex-direction:column gap:$1","> label":"display:flex align-items:center gap:$2 cursor:pointer user-select:none","> label:has(input:disabled)":"cursor:not-allowed opacity:0.6",input:"width:1.15em height:1.15em accent-color:$sPrimary cursor:inherit m:0"}});function N(e={}){let t=e.id??O("check");p("div.S_check",e.root,()=>{p(`label for=${t}`,()=>{p("input type=checkbox",e.control,()=>{p(`id=${t}`),e.name&&p(`name=${e.name}`),e.checked&&!e.bind&&p("checked=true"),e.change&&p("change=",e.change),p(()=>{e.disabled&&p("disabled=true")}),p(()=>{e.required&&p("aria-required=true")}),e.bind&&p("bind=",e.bind)}),p(()=>{e.label!=null&&c(e.label),e.required&&p("span.S_req aria-hidden=true #*")})}),p(()=>{e.help!=null&&!e.error&&p("div.S_help",()=>c(e.help))}),p(()=>{e.error&&p("div.S_error role=alert #",e.error)})})}import T from"aberdeen";T.insertGlobalCss({".S_form":{"&":"display:flex flex-direction:column gap:$3","&.grid":"display:grid grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap:$3","&.grid > .S_wide, &.grid > footer":"grid-column: 1 / -1;","> footer":"display:flex align-items:center gap:$2 flex-wrap:wrap margin-top:$1"}});function J(e={}){let t=typeof e=="function"?{content:e}:e;T("form.S_form",t.root,t.inner,()=>{T(()=>{T(".grid=",t.layout==="grid")}),T("submit=",n=>{if(n.preventDefault(),t.submit){let i=new FormData(n.target),r={};for(let s of new Set(i.keys())){let a=i.getAll(s);r[s]=a.length===1?a[0]:a}t.submit(r,n)}}),t.content&&t.content(),T(()=>{t.actions&&T("footer",t.actionsInner,()=>t.actions?.())})})}import f from"aberdeen";f.insertGlobalCss({".S_main":{"&":"display:flex flex-direction:column min-height:100vh max-height:100vh bg:$sBg fg:$sFg","> header":"display:flex align-items:center gap:$3 padding: $2 $3; bg:$sSurfaceHi border-bottom: 1px solid $sBorder; position:sticky top:0 z-index:10","> header .S_icon":"display:flex align-items:center font-size:1.4em","> header .S_titles":"display:flex flex-direction:column min-width:0 flex:1","> header .S_title":"font-weight:700 font-size:1.1em line-height:1.2 overflow:hidden text-overflow:ellipsis white-space:nowrap","> header .S_subtitle":"fg:$sFgMuted font-size:0.85em overflow:hidden text-overflow:ellipsis white-space:nowrap","> header .S_menu":"display:flex align-items:center gap:$2","> main":"flex:1 overflow-y:auto display:flex flex-direction:column","> main > .S_content":"width:100% flex:1","> main > .S_content.S_framed":"margin: $3 auto; bg:$sSurface border: 1px solid $sBorder; r:$sRadiusLg box-shadow:$sShadow p:$4","> main > .S_content.S_plain":"p:$3","> footer":"display:flex align-items:center gap:$2 padding: $2 $3; bg:$sSurfaceHi border-top: 1px solid $sBorder; fg:$sFgMuted"}});function Q(e={}){f("div.S_main",e.root,()=>{f(()=>{(e.title!=null||e.subtitle!=null||e.icon!=null||e.menu!=null)&&f("header",e.topbarInner,()=>{f(()=>{e.icon!=null&&f("div.S_icon",()=>c(e.icon))}),f("div.S_titles",()=>{f(()=>{e.title!=null&&f("div.S_title",()=>c(e.title))}),f(()=>{e.subtitle!=null&&f("div.S_subtitle",()=>c(e.subtitle))})}),f(()=>{e.menu&&f("div.S_menu",()=>e.menu?.())})})}),f("main",()=>{f("div.S_content",e.inner,()=>{f(()=>{let t=e.maxWidth;t!=null?f(".S_framed max-width:",t):f(".S_plain")}),e.content&&e.content()})}),f(()=>{e.footer!=null&&f("footer",()=>c(e.footer))})})}import u from"aberdeen";import A from"aberdeen";function H(e={}){C(e,(t,n)=>{A("input.S_input",e.control,()=>{A("type=",e.type??"text"),e.placeholder!=null&&A("placeholder=",e.placeholder),e.autocomplete!=null&&A("autocomplete=",e.autocomplete),e.value!=null&&!e.bind&&A("value=",e.value),e.input&&A("input=",e.input),e.change&&A("change=",e.change),I(e,t,n,e.bind)})})}u.insertGlobalCss({".S_backdrop":{"&":"position:fixed inset:0 z-index:200; background: rgba(0,0,0,0.55); transition: opacity 0.2s ease;","&:not(:has(~ .S_backdrop))":"display:block","&:not(:has(~ .S_backdrop)) + .S_dialog":"display:flex flex-direction:column","&.hidden":"opacity:0 pointer-events:none"},".S_dialog":{"&":"position:fixed z-index:201 top:50% left:50% transform:translate(-50%,-50%) min-width:20rem max-width:min(90vw,44rem) max-height:min(88vh,800px) bg:$sSurface border: 1px solid $sBorder; r:$sRadiusLg box-shadow:$sShadow overflow:hidden transition: opacity 0.2s ease, transform 0.2s ease;","> header":"display:flex align-items:center gap:$2 padding: $2 $3; bg:$sSurfaceHi border-bottom: 1px solid $sBorder; font-weight:600 flex-shrink:0","> footer":"display:flex align-items:center gap:$2 padding: $2 $3; bg:$sSurfaceHi border-top: 1px solid $sBorder; flex-shrink:0","> div":"p:$3 gap:$3 display:flex flex-direction:column overflow-y:auto flex:1 min-height:0","&.hidden":"opacity:0 pointer-events:none transform: translate(-50%, calc(-50% + 20px));","&.hidden *":"pointer-events:none"}});function q(e){return new Promise(t=>{let n=u.proxy(!1),i=()=>{n.value=!0},r=!1,s=()=>{r||(r=!0,e.onClose?.(),t())};u.mount(document.body,()=>{if(u.peek(n,"value"),n.value)return;let a=d=>{d.key==="Escape"&&e.allowCancel!==!1&&i()};document.addEventListener("keydown",a),u.clean(()=>{document.removeEventListener("keydown",a),s()}),u("div.S_backdrop create=hidden destroy=hidden",()=>{u("click=",()=>{e.allowCancel!==!1&&i()})}),u("div.S_dialog create=hidden destroy=hidden",e.root,()=>{u(()=>{e.header!=null&&u("header",e.headerInner,()=>c(e.header))}),u("div",e.inner,()=>{e.content&&e.content(i)}),u(()=>{e.footer!=null&&u("footer",e.footerInner,()=>c(e.footer))})})})})}function X(e,t={}){return q({header:"Alert",allowCancel:!0,content:n=>{u("p",()=>{u("#",e)}),M({layout:"spaced",root:"align-self:flex-end",content:()=>{$({text:"OK",click:n})}})},...t})}function Z(e,t={}){return new Promise(n=>{let i=!1;q({header:"Confirm",allowCancel:!0,content:r=>{u("p",()=>{u("#",e)}),M({layout:"spaced",root:"align-self:flex-end",content:()=>{$({text:"Cancel",variant:"outlined",color:"neutral",click:r}),$({text:"OK",click:()=>{i=!0,r()}})}})},...t,onClose:()=>{n(i),t.onClose?.()}})})}function ee(e,t="",n={}){return new Promise(i=>{let r=null;q({header:"Input",allowCancel:!0,content:s=>{u("p",()=>{u("#",e)});let a=u.proxy({value:t});u("form display:contents",()=>{u("submit=",d=>{d.preventDefault(),r=a.value,s()}),H({bind:u.ref(a,"value")}),M({layout:"spaced",root:"align-self:flex-end",content:()=>{$({text:"Cancel",variant:"outlined",color:"neutral",type:"button",click:s}),$({text:"OK",type:"submit"})}})})},...n,onClose:()=>{i(r),n.onClose?.()}})})}import v from"aberdeen";v.insertGlobalCss({".S_select_wrap":{"&":"position:relative display:block",select:"w:100% cursor:pointer padding-right:2.2em; appearance:none","&::after":"content: '\u25BE'; position:absolute right:0.7em top:50%; transform: translateY(-50%); pointer-events:none fg:$sFgMuted font-size:0.85em"}});function te(e){C(e,(t,n)=>{v("div.S_select_wrap",e.control,()=>{v("select.S_input",()=>{I(e,t,n),v("change=",i=>{e.bind&&(e.bind.value=i.target.value)}),v(()=>{let i=typeof e.options=="function"?e.options():e.options,r=e.bind?.value??"";e.placeholder!=null&&v("option",()=>{v("value= disabled=true hidden=true"),r||v("selected=true"),v("#",e.placeholder)});for(let s of i){let a=typeof s=="string"?{value:s,label:s}:{value:s.value,label:s.label??s.value};v("option",()=>{v("value=",a.value),a.value===r&&v("selected=true"),v("#",a.label)})}})})})})}import S from"aberdeen";S.insertGlobalCss({".S_tabs":{"&":"display:flex flex-direction:column gap:$3",".S_tablist":"display:flex gap:$1 align-items:stretch",".S_tab":"display:inline-flex align-items:center gap:$2 cursor:pointer background:transparent border:0 fg:$sFgMuted font-weight:600 padding: 0.6em 0.9em; transition: color 0.15s, background 0.15s, border-color 0.15s;",".S_tab:hover:not(:disabled)":"fg:$sFg",".S_tab:disabled":"opacity:0.5 cursor:not-allowed",".S_tab:focus-visible":"outline:none box-shadow: 0 0 0 3px $sFocus; r:$sRadius","&.S_underline .S_tablist":"border-bottom: 1px solid $sBorder;","&.S_underline .S_tab":"border-bottom: 2px solid transparent; margin-bottom:-1px","&.S_underline .S_tab[aria-selected=true]":"fg:$sFg border-bottom-color:$sPrimary","&.S_pills .S_tab":"r:$sRadius","&.S_pills .S_tab[aria-selected=true]":"fg:$sPrimaryFg background:$sPrimary",".S_tabpanel":"display:block"}});function ne(e){let t=e.variant??"underline",n=O("tabs"),i=(a,d)=>a.id??String(d),r=e.bind??S.proxy(i(e.tabs[0]??{label:""},0)),s=(a,d)=>{a.disabled||(r.value=i(a,d))};S(`div.S_tabs.S_${t}`,e.root,()=>{S("div.S_tablist role=tablist",()=>{e.tabs.forEach((a,d)=>{let x=i(a,d);S("button.S_tab type=button role=tab",()=>{S(`id=${n}-tab-${x} aria-controls=${n}-panel-${x}`),S(()=>{let B=r.value===x;S("aria-selected=",B?"true":"false"),S("tabindex=",B?"0":"-1")}),a.disabled&&S("disabled=true"),S("click=",()=>s(a,d)),S("keydown=",B=>ce(B,e.tabs,d,s)),c(a.icon),c(a.label)})})}),S("div.S_tabpanel role=tabpanel",e.inner,()=>{S(()=>{let a=r.value,d=e.tabs.findIndex((B,L)=>i(B,L)===a),x=e.tabs[d]??e.tabs[0];x&&(S(`id=${n}-panel-${i(x,d)} aria-labelledby=${n}-tab-${i(x,d)}`),x.content?.())})})})}function ce(e,t,n,i){let r=n;if(e.key==="ArrowRight"||e.key==="ArrowDown")r=(n+1)%t.length;else if(e.key==="ArrowLeft"||e.key==="ArrowUp")r=(n-1+t.length)%t.length;else if(e.key==="Home")r=0;else if(e.key==="End")r=t.length-1;else return;e.preventDefault();let s=r>=n?1:-1;for(let a=0;a<t.length;a++){let d=t[r];if(d&&!d.disabled){i(d,r),e.currentTarget?.parentElement?.children[r]?.focus();return}r=(r+s+t.length)%t.length}}import _ from"aberdeen";_.insertGlobalCss({"textarea.S_input":"resize:vertical min-height:3em line-height:1.45","textarea.S_input.S_autoGrow":"resize:none min-height:2.5em overflow-y:hidden"});function re(e={}){let t=e.autoGrow!==!1;C(e,(n,i)=>{let r=_("textarea.S_input",e.control,()=>{t?(_(".S_autoGrow"),_("input=",s=>{oe(s.currentTarget),e.input&&e.input(s)})):(_("rows=",e.rows??4),_("resize:",e.resize??"vertical"),e.input&&_("input=",e.input)),e.placeholder!=null&&_("placeholder=",e.placeholder),e.value!=null&&!e.bind&&_("value=",e.value),e.change&&_("change=",e.change),I(e,n,i,e.bind)});t&&requestAnimationFrame(()=>oe(r))})}function oe(e){e.style.height="auto",e.style.height=`${e.scrollHeight}px`}var ue={main:Q,box:U,dialog:q,alert:X,confirm:Z,prompt:ee,form:J,textline:H,textarea:re,checkbox:N,tabs:ne,button:$,buttonGroup:M,autocomplete:W,select:te,darkTheme:P,lightTheme:D,setDarkMode:j,getDarkMode:z},ht=ue;export{ue as S,P as darkTheme,ht as default,c as drawSlot,z as getDarkMode,D as lightTheme,j as setDarkMode,O as uniqueId};
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Theming and global base styles for Skye.
3
+ *
4
+ * Skye is themed entirely through CSS custom properties (via Aberdeen's
5
+ * {@link A.cssVars}). Components reference these with `var(--sPrimary)` etc., so
6
+ * changing a single variable restyles the whole app — at runtime, reactively.
7
+ *
8
+ * Unlike typical Aberdeen apps (which use component-local `insertCss`), Skye uses
9
+ * **global** CSS (`insertGlobalCss`) with class names prefixed `S_`. This is a
10
+ * deliberate trade-off: it lets application authors override any Skye style from
11
+ * their own stylesheet without fighting scoped class names.
12
+ */
13
+ /**
14
+ * The set of CSS custom properties Skye understands. All are plain CSS color /
15
+ * length strings. Override any subset by mutating {@link darkTheme} /
16
+ * {@link lightTheme}.
17
+ */
18
+ export interface Theme {
19
+ /** Page background — the darkest surface. */
20
+ sBg: string;
21
+ /** Default surface for cards, inputs, menus. */
22
+ sSurface: string;
23
+ /** Raised surface for headers, footers, chips, hover states. */
24
+ sSurfaceHi: string;
25
+ /** Primary foreground / text color. */
26
+ sFg: string;
27
+ /** Muted text (help text, subtitles). */
28
+ sFgMuted: string;
29
+ /** Faint text (placeholders, disabled). */
30
+ sFgFaint: string;
31
+ /** Default border color. */
32
+ sBorder: string;
33
+ /** Stronger border / neutral control color. */
34
+ sBorderStrong: string;
35
+ /** Brand / accent color. */
36
+ sPrimary: string;
37
+ /** Brand color, hover/brighter. */
38
+ sPrimaryHover: string;
39
+ /** Text drawn on top of {@link Theme.sPrimary}. */
40
+ sPrimaryFg: string;
41
+ /** Destructive / error color. */
42
+ sDanger: string;
43
+ /** Positive / success color. */
44
+ sSuccess: string;
45
+ /** Caution color. */
46
+ sWarning: string;
47
+ /** Focus-ring color (usually a translucent primary). */
48
+ sFocus: string;
49
+ /** Default corner radius. */
50
+ sRadius: string;
51
+ /** Larger corner radius (e.g. the {@link import("./components/main").main} sheet). */
52
+ sRadiusLg: string;
53
+ /** Elevation shadow for menus, dialogs, the framed content sheet. */
54
+ sShadow: string;
55
+ }
56
+ /**
57
+ * The default dark Skye theme: modern and intentionally a little vivid so it
58
+ * stands out of the box.
59
+ *
60
+ * This is a live Aberdeen proxy — mutate it (e.g. `darkTheme.sPrimary = "..."`)
61
+ * and, while dark mode is active, the change flows straight into the CSS
62
+ * variables. Use this to retheme dark and {@link lightTheme} independently.
63
+ */
64
+ export declare const darkTheme: Theme;
65
+ /**
66
+ * The light Skye theme — the same lavender brand, retuned for a bright,
67
+ * modern surface: white cards on a soft grey page, a deeper primary so it
68
+ * reads well on light backgrounds, and a softer elevation shadow.
69
+ *
70
+ * Like {@link darkTheme}, a live proxy: mutate it to retheme light mode.
71
+ */
72
+ export declare const lightTheme: Theme;
73
+ /**
74
+ * Force dark mode (`true`), light mode (`false`), or follow the OS preference
75
+ * (`undefined`). Takes effect immediately and is persisted to localStorage, so
76
+ * the choice survives reloads.
77
+ */
78
+ export declare function setDarkMode(value: boolean | undefined): void;
79
+ /**
80
+ * Whether dark mode is currently active. Reactive — read it inside a scope to
81
+ * re-run on changes.
82
+ *
83
+ * @param allowAuto - When `true`, returns `undefined` (rather than resolving to
84
+ * a boolean) if the user is following the OS preference, so a dark/light/auto
85
+ * control can tell the three states apart.
86
+ */
87
+ export declare function getDarkMode(allowAuto?: boolean): boolean | undefined;
package/dist/theme.js ADDED
@@ -0,0 +1,135 @@
1
+ import A from "aberdeen";
2
+ /**
3
+ * The default dark Skye theme: modern and intentionally a little vivid so it
4
+ * stands out of the box.
5
+ *
6
+ * This is a live Aberdeen proxy — mutate it (e.g. `darkTheme.sPrimary = "..."`)
7
+ * and, while dark mode is active, the change flows straight into the CSS
8
+ * variables. Use this to retheme dark and {@link lightTheme} independently.
9
+ */
10
+ export const darkTheme = A.proxy({
11
+ sBg: "#0e1015",
12
+ sSurface: "#181b22",
13
+ sSurfaceHi: "#222632",
14
+ sFg: "#e8eaf0",
15
+ sFgMuted: "#a6acba",
16
+ sFgFaint: "#6b7280",
17
+ sBorder: "#2c313c",
18
+ sBorderStrong: "#3c4352",
19
+ sPrimary: "#8b7bff",
20
+ sPrimaryHover: "#a99dff",
21
+ sPrimaryFg: "#0c0a1a",
22
+ sDanger: "#ff6b6b",
23
+ sSuccess: "#46d39a",
24
+ sWarning: "#fbbf24",
25
+ sFocus: "rgba(139, 123, 255, 0.45)",
26
+ sRadius: "10px",
27
+ sRadiusLg: "16px",
28
+ sShadow: "0 8px 30px rgba(0, 0, 0, 0.45)",
29
+ });
30
+ /**
31
+ * The light Skye theme — the same lavender brand, retuned for a bright,
32
+ * modern surface: white cards on a soft grey page, a deeper primary so it
33
+ * reads well on light backgrounds, and a softer elevation shadow.
34
+ *
35
+ * Like {@link darkTheme}, a live proxy: mutate it to retheme light mode.
36
+ */
37
+ export const lightTheme = A.proxy({
38
+ sBg: "#f3f4f8",
39
+ sSurface: "#ffffff",
40
+ sSurfaceHi: "#eceef4",
41
+ sFg: "#1b1e27",
42
+ sFgMuted: "#5b6273",
43
+ sFgFaint: "#9aa1b2",
44
+ sBorder: "#e2e5ee",
45
+ sBorderStrong: "#c7ccda",
46
+ sPrimary: "#6c5ce7",
47
+ sPrimaryHover: "#5847d4",
48
+ sPrimaryFg: "#ffffff",
49
+ sDanger: "#e23b3b",
50
+ sSuccess: "#1f9d6b",
51
+ sWarning: "#d97706",
52
+ sFocus: "rgba(108, 92, 231, 0.35)",
53
+ sRadius: "10px",
54
+ sRadiusLg: "16px",
55
+ sShadow: "0 6px 24px rgba(20, 24, 40, 0.12)",
56
+ });
57
+ const STORAGE_KEY = "skye:darkMode";
58
+ /**
59
+ * The explicit dark-mode choice — `true` (force dark), `false` (force light) or
60
+ * `undefined` (follow the OS via {@link A.darkMode}). A reactive proxy, seeded
61
+ * from localStorage so the persisted preference applies on the first paint.
62
+ */
63
+ const $override = A.proxy({ value: readStoredOverride() });
64
+ /** Read the persisted dark-mode override from localStorage (defensively). */
65
+ function readStoredOverride() {
66
+ try {
67
+ const v = localStorage.getItem(STORAGE_KEY);
68
+ if (v === "dark")
69
+ return true;
70
+ if (v === "light")
71
+ return false;
72
+ }
73
+ catch {
74
+ // localStorage may be unavailable (SSR, privacy mode) — ignore.
75
+ }
76
+ return undefined;
77
+ }
78
+ /**
79
+ * Force dark mode (`true`), light mode (`false`), or follow the OS preference
80
+ * (`undefined`). Takes effect immediately and is persisted to localStorage, so
81
+ * the choice survives reloads.
82
+ */
83
+ export function setDarkMode(value) {
84
+ $override.value = value;
85
+ try {
86
+ if (value === undefined)
87
+ localStorage.removeItem(STORAGE_KEY);
88
+ else
89
+ localStorage.setItem(STORAGE_KEY, value ? "dark" : "light");
90
+ }
91
+ catch {
92
+ // Persistence is best-effort; ignore storage failures.
93
+ }
94
+ }
95
+ /**
96
+ * Whether dark mode is currently active. Reactive — read it inside a scope to
97
+ * re-run on changes.
98
+ *
99
+ * @param allowAuto - When `true`, returns `undefined` (rather than resolving to
100
+ * a boolean) if the user is following the OS preference, so a dark/light/auto
101
+ * control can tell the three states apart.
102
+ */
103
+ export function getDarkMode(allowAuto = false) {
104
+ const v = $override.value;
105
+ return v === undefined && !allowAuto ? A.darkMode() : v;
106
+ }
107
+ // Set up everything as this module loads. Spacing scale first ($1 = 0.25rem,
108
+ // $2 = 0.5rem, $3 = 1rem, ...), then reactively merge the active theme into the
109
+ // CSS variables. This scope runs synchronously now — before the first paint —
110
+ // so the correct colors are in place immediately (no flash), and A.merge
111
+ // subscribes to the theme it reads, so toggling the mode or mutating
112
+ // darkTheme / lightTheme re-applies automatically.
113
+ A.setSpacingCssVars();
114
+ A(() => {
115
+ A.merge(A.cssVars, getDarkMode() ? darkTheme : lightTheme);
116
+ });
117
+ // A deliberately light reset. It sets box-sizing and sensible colors/fonts, but
118
+ // does NOT strip margins from headings/paragraphs/lists, so rendered rich
119
+ // content (e.g. markdown-to-HTML) keeps reasonable default rhythm.
120
+ A.insertGlobalCss({
121
+ "*, *::before, *::after": "box-sizing:border-box",
122
+ html: "text-size-adjust:100%",
123
+ body: "m:0 bg:$sBg fg:$sFg line-height:1.5 font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; -webkit-font-smoothing:antialiased",
124
+ a: "fg:$sPrimary text-decoration:underline text-underline-offset:2px",
125
+ "a:hover": "fg:$sPrimaryHover",
126
+ "input, button, textarea, select": "font:inherit color:inherit",
127
+ "code, kbd, samp, pre": "font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;",
128
+ code: "bg:$sSurfaceHi padding: 0.12em 0.34em; r:4px font-size:0.9em",
129
+ pre: "bg:$sSurface p:$3 r:$sRadius overflow:auto",
130
+ "pre code": "bg:transparent p:0",
131
+ "img, svg, video, canvas": "max-width:100% h:auto",
132
+ hr: "border:0 border-top: 1px solid $sBorder; margin: $3 0;",
133
+ "::placeholder": "fg:$sFgFaint opacity:1",
134
+ ":focus-visible": "outline: 2px solid $sFocus; outline-offset:2px",
135
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "staffa",
3
+ "version": "0.1.0",
4
+ "description": "An opinionated component library for the Aberdeen reactive UI library.",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "module",
8
+ "main": "dist/index.js",
9
+ "types": "dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "./all.js": "./dist/staffa.esm.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc && tsc -p demo && esbuild src/index.ts --bundle --external:aberdeen --minify --format=esm --outfile=dist/staffa.esm.js",
23
+ "typecheck": "tsc --noEmit && tsc -p demo --noEmit",
24
+ "smoke": "tsc && node smoke.mjs"
25
+ },
26
+ "peerDependencies": {
27
+ "aberdeen": "^1.15.0"
28
+ },
29
+ "devDependencies": {
30
+ "aberdeen": "^1.15.0",
31
+ "esbuild": "^0.28.0",
32
+ "jsdom": "^29.1.1",
33
+ "typescript": "^5.9.3"
34
+ }
35
+ }