@gratiaos/ui 1.0.5 β†’ 1.2.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 CHANGED
@@ -7,6 +7,15 @@
7
7
  **Garden UI** is the shared component library that gives shape and soul to the Garden.
8
8
  It’s where headless primitives meet design tokens, growing together into a living interface system.
9
9
 
10
+ ## πŸ›°οΈ Garden Stack naming (infra-facing)
11
+
12
+ - **Pattern Engine** β†’ underlying model stack (training / inference / retrieval). Use this wording for infra/capability talk.
13
+ - **Presence Node** β†’ surfaced endpoint humans touch (web UI, CLI, scripts, voice, agents).
14
+ - **Mode** β†’ behavioral / conversational contract for a Presence Node (e.g. `Codex-mode`, `Monday-mode`). Styles, not identities.
15
+ - **Garden Stack** β†’ Pattern Engine + Presence Nodes + Modes working together.
16
+
17
+ Route any β€œAI” mention to the correct layer so UI docs stay aligned with Garden + M3.
18
+
10
19
  ---
11
20
 
12
21
  ## ✨ Vision
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Garden UI β€” PersonalPulse primitive (headless)
4
+ * ----------------------------------------------
5
+ * Whisper: "tiny beacon, still here." 🌬️
6
+ *
7
+ * Purpose
8
+ * β€’ Mirrors the shared pulse so the interface feels alive even when idle.
9
+ * β€’ Falls back to a soft idle rhythm when no beats arrive for a while.
10
+ */
11
+ export declare const PersonalPulse: React.FC;
12
+ //# sourceMappingURL=PersonalPulse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PersonalPulse.d.ts","sourceRoot":"","sources":["../../src/footer/PersonalPulse.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAiEjC,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { pulse$ } from '@gratiaos/presence-kernel';
4
+ /**
5
+ * Garden UI β€” PersonalPulse primitive (headless)
6
+ * ----------------------------------------------
7
+ * Whisper: "tiny beacon, still here." 🌬️
8
+ *
9
+ * Purpose
10
+ * β€’ Mirrors the shared pulse so the interface feels alive even when idle.
11
+ * β€’ Falls back to a soft idle rhythm when no beats arrive for a while.
12
+ */
13
+ export const PersonalPulse = () => {
14
+ const ref = React.useRef(null);
15
+ const [reduceMotion, setReduceMotion] = React.useState(() => {
16
+ if (typeof window === 'undefined')
17
+ return false;
18
+ return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
19
+ });
20
+ React.useEffect(() => {
21
+ if (typeof window === 'undefined')
22
+ return;
23
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
24
+ const update = () => setReduceMotion(mq.matches);
25
+ mq.addEventListener('change', update);
26
+ update();
27
+ return () => mq.removeEventListener('change', update);
28
+ }, []);
29
+ React.useEffect(() => {
30
+ if (reduceMotion) {
31
+ const node = ref.current;
32
+ if (node)
33
+ node.style.animation = 'none';
34
+ return;
35
+ }
36
+ const node = ref.current;
37
+ if (!node)
38
+ return;
39
+ let idleTimer = null;
40
+ const beginIdle = () => {
41
+ if (!node)
42
+ return;
43
+ node.style.animation = 'idle-pulse 6s ease-in-out infinite';
44
+ };
45
+ const cancelIdle = () => {
46
+ if (!node)
47
+ return;
48
+ node.style.animation = 'none';
49
+ };
50
+ const handleBeat = () => {
51
+ cancelIdle();
52
+ node.animate([
53
+ { opacity: 0.25, transform: 'scale(1)' },
54
+ { opacity: 0.55, transform: 'scale(1.15)' },
55
+ { opacity: 0.25, transform: 'scale(1)' },
56
+ ], {
57
+ duration: 420,
58
+ easing: 'ease-out',
59
+ });
60
+ if (idleTimer)
61
+ clearTimeout(idleTimer);
62
+ idleTimer = window.setTimeout(beginIdle, 8000);
63
+ };
64
+ const stop = pulse$.subscribe(handleBeat);
65
+ idleTimer = window.setTimeout(beginIdle, 8000);
66
+ return () => {
67
+ stop();
68
+ if (idleTimer)
69
+ clearTimeout(idleTimer);
70
+ };
71
+ }, [reduceMotion]);
72
+ return _jsx("div", { ref: ref, "data-ui": "personal-pulse", "aria-hidden": "true" });
73
+ };
74
+ //# sourceMappingURL=PersonalPulse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PersonalPulse.js","sourceRoot":"","sources":["../../src/footer/PersonalPulse.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,aAAa,GAAa,GAAG,EAAE;IAC1C,MAAM,GAAG,GAAG,KAAK,CAAC,MAAM,CAAwB,IAAI,CAAC,CAAC;IACtD,MAAM,CAAC,YAAY,EAAE,eAAe,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE;QAC1D,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO,KAAK,CAAC;QAChD,OAAO,MAAM,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAC,OAAO,CAAC;IACvE,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,OAAO,MAAM,KAAK,WAAW;YAAE,OAAO;QAC1C,MAAM,EAAE,GAAG,MAAM,CAAC,UAAU,CAAC,kCAAkC,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,eAAe,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC;QACjD,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QACtC,MAAM,EAAE,CAAC;QACT,OAAO,GAAG,EAAE,CAAC,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE;QACnB,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC;YACzB,IAAI,IAAI;gBAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;YACxC,OAAO;QACT,CAAC;QAED,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC;QACzB,IAAI,CAAC,IAAI;YAAE,OAAO;QAElB,IAAI,SAAS,GAAyC,IAAI,CAAC;QAE3D,MAAM,SAAS,GAAG,GAAG,EAAE;YACrB,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,oCAAoC,CAAC;QAC9D,CAAC,CAAC;QAEF,MAAM,UAAU,GAAG,GAAG,EAAE;YACtB,IAAI,CAAC,IAAI;gBAAE,OAAO;YAClB,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;QAChC,CAAC,CAAC;QAEF,MAAM,UAAU,GAAG,GAAG,EAAE;YACtB,UAAU,EAAE,CAAC;YACb,IAAI,CAAC,OAAO,CACV;gBACE,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE;gBACxC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE;gBAC3C,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE;aACzC,EACD;gBACE,QAAQ,EAAE,GAAG;gBACb,MAAM,EAAE,UAAU;aACnB,CACF,CAAC;YACF,IAAI,SAAS;gBAAE,YAAY,CAAC,SAAS,CAAC,CAAC;YACvC,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QACjD,CAAC,CAAC;QAEF,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC1C,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;QAE/C,OAAO,GAAG,EAAE;YACV,IAAI,EAAE,CAAC;YACP,IAAI,SAAS;gBAAE,YAAY,CAAC,SAAS,CAAC,CAAC;QACzC,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC;IAEnB,OAAO,cAAK,GAAG,EAAE,GAAG,aAAU,gBAAgB,iBAAa,MAAM,GAAG,CAAC;AACvE,CAAC,CAAC"}
@@ -0,0 +1,30 @@
1
+ import * as React from 'react';
2
+ import { type KernelAuthority } from '@gratiaos/presence-kernel';
3
+ /**
4
+ * Garden UI β€” ConductorChip primitive (headless)
5
+ * ---------------------------------------------
6
+ * Whisper: "name the conductor softly." 🌬️
7
+ *
8
+ * Purpose
9
+ * β€’ Display the active presence authority as a compact HUD chip.
10
+ * β€’ Mirrors kernel signals without requiring a kernel instance.
11
+ *
12
+ * Data API
13
+ * β€’ [data-ui="conductor-chip"] β€” root hook for skins.
14
+ * β€’ [data-authority="…"] β€” matches KernelAuthority enum.
15
+ *
16
+ * A11y
17
+ * β€’ Title defaults to "Authority: …" unless overridden via props.
18
+ *
19
+ * Theming
20
+ * β€’ Reads tone tokens (--tone-accent, --tone-ink) for visuals.
21
+ *
22
+ * Notes
23
+ * β€’ Headless: visuals live in styles/header.css.
24
+ */
25
+ export interface ConductorChipProps extends React.ComponentPropsWithoutRef<'span'> {
26
+ /** Optional custom label formatter. Receives the raw authority string. */
27
+ formatLabel?: (authority: KernelAuthority) => React.ReactNode;
28
+ }
29
+ export declare const ConductorChip: React.FC<ConductorChipProps>;
30
+ //# sourceMappingURL=ConductorChip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConductorChip.d.ts","sourceRoot":"","sources":["../../src/header/ConductorChip.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAc,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE7E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,wBAAwB,CAAC,MAAM,CAAC;IAChF,0EAA0E;IAC1E,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,eAAe,KAAK,KAAK,CAAC,SAAS,CAAC;CAC/D;AAQD,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAkBtD,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { authority$ } from '@gratiaos/presence-kernel';
4
+ const DEFAULT_LABELS = {
5
+ 'local-primary': 'Local',
6
+ 'remote-primary': 'Remote',
7
+ distributed: 'Mesh',
8
+ };
9
+ export const ConductorChip = ({ className, title, children, formatLabel, ...rest }) => {
10
+ const [authority, setAuthority] = React.useState(authority$.value);
11
+ React.useEffect(() => authority$.subscribe(setAuthority), []);
12
+ const label = React.useMemo(() => {
13
+ if (formatLabel)
14
+ return formatLabel(authority);
15
+ return DEFAULT_LABELS[authority] ?? authority;
16
+ }, [authority, formatLabel]);
17
+ const resolvedTitle = title ?? `Authority: ${authority}`;
18
+ const resolvedClassName = ['conductor-chip', className].filter(Boolean).join(' ') || undefined;
19
+ return (_jsx("span", { "data-ui": "conductor-chip", "data-authority": authority, className: resolvedClassName, title: resolvedTitle, ...rest, children: children ?? label }));
20
+ };
21
+ //# sourceMappingURL=ConductorChip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConductorChip.js","sourceRoot":"","sources":["../../src/header/ConductorChip.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAwB,MAAM,2BAA2B,CAAC;AA6B7E,MAAM,cAAc,GAA2B;IAC7C,eAAe,EAAE,OAAO;IACxB,gBAAgB,EAAE,QAAQ;IAC1B,WAAW,EAAE,MAAM;CACpB,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAiC,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE;IAClH,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAkB,UAAU,CAAC,KAAK,CAAC,CAAC;IAEpF,KAAK,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC,CAAC;IAE9D,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE;QAC/B,IAAI,WAAW;YAAE,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAC/C,OAAO,cAAc,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC;IAChD,CAAC,EAAE,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC;IAE7B,MAAM,aAAa,GAAG,KAAK,IAAI,cAAc,SAAS,EAAE,CAAC;IACzD,MAAM,iBAAiB,GAAG,CAAC,gBAAgB,EAAE,SAAS,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;IAE/F,OAAO,CACL,0BAAc,gBAAgB,oBAAiB,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,KAAK,EAAE,aAAa,KAAM,IAAI,YACnH,QAAQ,IAAI,KAAK,GACb,CACR,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,17 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * Garden UI β€” Constellation primitive (headless)
4
+ * ---------------------------------------------
5
+ * Whisper: "quiet company overhead." 🌬️
6
+ *
7
+ * Purpose
8
+ * β€’ Minimal belonging indicator β€” signals presence context without interaction.
9
+ *
10
+ * Data API
11
+ * β€’ [data-ui="constellation"] β€” root wrapper hook.
12
+ *
13
+ * Notes
14
+ * β€’ Static dots; peers are abstract and not tied to live presence counts.
15
+ */
16
+ export declare const Constellation: React.FC;
17
+ //# sourceMappingURL=Constellation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Constellation.d.ts","sourceRoot":"","sources":["../../src/header/Constellation.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAUjC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Garden UI β€” Constellation primitive (headless)
4
+ * ---------------------------------------------
5
+ * Whisper: "quiet company overhead." 🌬️
6
+ *
7
+ * Purpose
8
+ * β€’ Minimal belonging indicator β€” signals presence context without interaction.
9
+ *
10
+ * Data API
11
+ * β€’ [data-ui="constellation"] β€” root wrapper hook.
12
+ *
13
+ * Notes
14
+ * β€’ Static dots; peers are abstract and not tied to live presence counts.
15
+ */
16
+ export const Constellation = () => {
17
+ return (_jsxs("div", { "data-ui": "constellation", "aria-hidden": "true", children: [_jsx("span", { className: "dot" }), _jsx("span", { className: "dot" }), _jsx("span", { className: "dot active" }), _jsx("span", { className: "dot" }), _jsx("span", { className: "dot" })] }));
18
+ };
19
+ //# sourceMappingURL=Constellation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Constellation.js","sourceRoot":"","sources":["../../src/header/Constellation.tsx"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,aAAa,GAAa,GAAG,EAAE;IAC1C,OAAO,CACL,0BAAa,eAAe,iBAAa,MAAM,aAC7C,eAAM,SAAS,EAAC,KAAK,GAAG,EACxB,eAAM,SAAS,EAAC,KAAK,GAAG,EACxB,eAAM,SAAS,EAAC,YAAY,GAAG,EAC/B,eAAM,SAAS,EAAC,KAAK,GAAG,EACxB,eAAM,SAAS,EAAC,KAAK,GAAG,IACpB,CACP,CAAC;AACJ,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -6,5 +6,10 @@ export * from './primitives/field.js';
6
6
  export * from './primitives/badge.js';
7
7
  export * from './primitives/toast.js';
8
8
  export * from './primitives/whisper.js';
9
+ export * from './primitives/select.js';
10
+ export * from './primitives/toolbar.js';
9
11
  export * from './hooks/index.js';
12
+ export * from './header/ConductorChip.js';
13
+ export * from './header/Constellation.js';
14
+ export * from './footer/PersonalPulse.js';
10
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC"}
package/dist/index.js CHANGED
@@ -6,5 +6,10 @@ export * from './primitives/field.js';
6
6
  export * from './primitives/badge.js';
7
7
  export * from './primitives/toast.js';
8
8
  export * from './primitives/whisper.js';
9
+ export * from './primitives/select.js';
10
+ export * from './primitives/toolbar.js';
9
11
  export * from './hooks/index.js';
12
+ export * from './header/ConductorChip.js';
13
+ export * from './header/Constellation.js';
14
+ export * from './footer/PersonalPulse.js';
10
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,2BAA2B,CAAC"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Garden UI β€” Select primitive (headless)
3
+ * ---------------------------------------
4
+ * Whisper: "let choices stay simple." 🌿
5
+ *
6
+ * Purpose
7
+ * β€’ Accessible wrapper around native <select> for Garden forms.
8
+ * β€’ Headless: visuals live in `styles/select.css`; this file emits structure + data-attrs.
9
+ * β€’ Works standalone or inside <Field> (as the [data-part="control"] payload).
10
+ *
11
+ * Data API
12
+ * β€’ [data-ui="select"] β€” root hook for the skin
13
+ * β€’ [data-state="valid|invalid"] β€” derived from `aria-invalid`
14
+ * β€’ [data-disabled] β€” present when `disabled` is true
15
+ * β€’ [data-tone="subtle|accent|positive|warning|danger"]
16
+ * β€’ [data-variant="ghost"] β€” minimal chrome (toolbar/inline)
17
+ *
18
+ * A11y
19
+ * β€’ Keeps native <select> semantics, focus, and keyboard behavior.
20
+ * β€’ Use `aria-invalid="true"` to flag validation errors; skin will render
21
+ * the `invalid` state via [data-state].
22
+ *
23
+ * Theming
24
+ * β€’ Skin reads Garden tokens only (no hard-coded colors) in `styles/select.css`.
25
+ * β€’ Pair with <Field> for labels, hints, and error messages.
26
+ *
27
+ * Notes
28
+ * β€’ If you use this inside <Field>, wrap it in the [data-part="control"] slot.
29
+ * β€’ Tone is optional; defaults to "subtle" for calm, non-distracting selects.
30
+ */
31
+ import * as React from 'react';
32
+ import type { Tone } from './field.js';
33
+ export interface SelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
34
+ /** Visual tone hint for the skin (subtle/accent/positive/warning/danger). */
35
+ tone?: Tone;
36
+ /** Optional visual variant (e.g., "ghost" for minimal chrome). */
37
+ variant?: 'ghost' | (string & {});
38
+ }
39
+ export declare const Select: React.ForwardRefExoticComponent<SelectProps & React.RefAttributes<HTMLSelectElement>>;
40
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/primitives/select.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,WAAY,SAAQ,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC;IAChF,6EAA6E;IAC7E,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,kEAAkE;IAClE,OAAO,CAAC,EAAE,OAAO,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;CACnC;AAED,eAAO,MAAM,MAAM,uFAiBjB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Garden UI β€” Select primitive (headless)
4
+ * ---------------------------------------
5
+ * Whisper: "let choices stay simple." 🌿
6
+ *
7
+ * Purpose
8
+ * β€’ Accessible wrapper around native <select> for Garden forms.
9
+ * β€’ Headless: visuals live in `styles/select.css`; this file emits structure + data-attrs.
10
+ * β€’ Works standalone or inside <Field> (as the [data-part="control"] payload).
11
+ *
12
+ * Data API
13
+ * β€’ [data-ui="select"] β€” root hook for the skin
14
+ * β€’ [data-state="valid|invalid"] β€” derived from `aria-invalid`
15
+ * β€’ [data-disabled] β€” present when `disabled` is true
16
+ * β€’ [data-tone="subtle|accent|positive|warning|danger"]
17
+ * β€’ [data-variant="ghost"] β€” minimal chrome (toolbar/inline)
18
+ *
19
+ * A11y
20
+ * β€’ Keeps native <select> semantics, focus, and keyboard behavior.
21
+ * β€’ Use `aria-invalid="true"` to flag validation errors; skin will render
22
+ * the `invalid` state via [data-state].
23
+ *
24
+ * Theming
25
+ * β€’ Skin reads Garden tokens only (no hard-coded colors) in `styles/select.css`.
26
+ * β€’ Pair with <Field> for labels, hints, and error messages.
27
+ *
28
+ * Notes
29
+ * β€’ If you use this inside <Field>, wrap it in the [data-part="control"] slot.
30
+ * β€’ Tone is optional; defaults to "subtle" for calm, non-distracting selects.
31
+ */
32
+ import * as React from 'react';
33
+ export const Select = React.forwardRef(function Select({ tone = 'subtle', variant, className, disabled, ...rest }, ref) {
34
+ const ariaInvalid = rest['aria-invalid'];
35
+ const state = ariaInvalid === true || ariaInvalid === 'true' ? 'invalid' : 'valid';
36
+ return (_jsx("select", { ...rest, ref: ref, disabled: disabled, "data-ui": "select", "data-state": state, "data-disabled": disabled ? '' : undefined, "data-tone": tone, "data-variant": variant, className: className }));
37
+ });
38
+ Select.displayName = 'Select';
39
+ //# sourceMappingURL=select.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/primitives/select.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAU/B,MAAM,CAAC,MAAM,MAAM,GAAG,KAAK,CAAC,UAAU,CAAiC,SAAS,MAAM,CAAC,EAAE,IAAI,GAAG,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,IAAI,EAAE,EAAE,GAAG;IACpJ,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,CAAC;IACzC,MAAM,KAAK,GAAwB,WAAW,KAAK,IAAI,IAAI,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;IAExG,OAAO,CACL,oBACM,IAAI,EACR,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,QAAQ,aACV,QAAQ,gBACJ,KAAK,mBACF,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,eAC7B,IAAI,kBACD,OAAO,EACrB,SAAS,EAAE,SAAS,GACpB,CACH,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Garden UI β€” Toolbar primitive (headless)
3
+ * ---------------------------------------
4
+ * Whisper: "tools should feel near, not loud." 🌬️
5
+ *
6
+ * Purpose
7
+ * β€’ Structural container for icon/text controls (filters, view toggles, etc.).
8
+ * β€’ Headless: visuals live in `styles/toolbar.css`; this file emits structure + data-attrs.
9
+ * β€’ Works with <Button>, <Select>, and other Garden primitives inside.
10
+ *
11
+ * Data API
12
+ * β€’ [data-ui="toolbar"] β€” root
13
+ * β€’ [data-orientation="horizontal|vertical"] β€” layout hint; default: "horizontal"
14
+ * β€’ [data-density="cozy|snug"] β€” vertical padding; default: "cozy"
15
+ * β€’ [data-part="group"] β€” optional sub-group wrapper
16
+ *
17
+ * A11y
18
+ * β€’ Renders role="toolbar" on the root.
19
+ * β€’ Pass `aria-label` or `aria-labelledby` so screen readers know what this toolbar does.
20
+ *
21
+ * Theming
22
+ * β€’ Skin reads Garden tokens only (no hard-coded colors) in `styles/toolbar.css`.
23
+ * β€’ Typical pattern: place ghost/subtle Buttons inside so toolbar feels calm by default.
24
+ */
25
+ import * as React from 'react';
26
+ export type ToolbarOrientation = 'horizontal' | 'vertical';
27
+ export type ToolbarDensity = 'cozy' | 'snug';
28
+ export interface ToolbarProps extends React.HTMLAttributes<HTMLDivElement> {
29
+ /** Layout direction; horizontal by default. */
30
+ orientation?: ToolbarOrientation;
31
+ /** Vertical density; "cozy" = default, "snug" = tighter. */
32
+ density?: ToolbarDensity;
33
+ }
34
+ /**
35
+ * Root toolbar container.
36
+ *
37
+ * Example:
38
+ * <Toolbar aria-label="Presence view tools">
39
+ * <ToolbarGroup>
40
+ * <Button variant="ghost" tone="default">Today</Button>
41
+ * <Button variant="ghost" tone="default">Week</Button>
42
+ * </ToolbarGroup>
43
+ * <ToolbarGroup>
44
+ * <Select>...</Select>
45
+ * </ToolbarGroup>
46
+ * </Toolbar>
47
+ */
48
+ export declare const Toolbar: React.ForwardRefExoticComponent<ToolbarProps & React.RefAttributes<HTMLDivElement>>;
49
+ export interface ToolbarGroupProps extends React.HTMLAttributes<HTMLDivElement> {
50
+ }
51
+ /**
52
+ * Optional group wrapper for logically related controls inside a toolbar.
53
+ *
54
+ * <Toolbar>
55
+ * <ToolbarGroup>primary controls…</ToolbarGroup>
56
+ * <ToolbarGroup>secondary controls…</ToolbarGroup>
57
+ * </Toolbar>
58
+ */
59
+ export declare const ToolbarGroup: React.ForwardRefExoticComponent<ToolbarGroupProps & React.RefAttributes<HTMLDivElement>>;
60
+ //# sourceMappingURL=toolbar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolbar.d.ts","sourceRoot":"","sources":["../../src/primitives/toolbar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,kBAAkB,GAAG,YAAY,GAAG,UAAU,CAAC;AAC3D,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;AAE7C,MAAM,WAAW,YAAa,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IACxE,+CAA+C;IAC/C,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,4DAA4D;IAC5D,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,OAAO,qFAmBlB,CAAC;AAEH,MAAM,WAAW,iBAAkB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;CAAG;AAElF;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,0FAYvB,CAAC"}
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Garden UI β€” Toolbar primitive (headless)
4
+ * ---------------------------------------
5
+ * Whisper: "tools should feel near, not loud." 🌬️
6
+ *
7
+ * Purpose
8
+ * β€’ Structural container for icon/text controls (filters, view toggles, etc.).
9
+ * β€’ Headless: visuals live in `styles/toolbar.css`; this file emits structure + data-attrs.
10
+ * β€’ Works with <Button>, <Select>, and other Garden primitives inside.
11
+ *
12
+ * Data API
13
+ * β€’ [data-ui="toolbar"] β€” root
14
+ * β€’ [data-orientation="horizontal|vertical"] β€” layout hint; default: "horizontal"
15
+ * β€’ [data-density="cozy|snug"] β€” vertical padding; default: "cozy"
16
+ * β€’ [data-part="group"] β€” optional sub-group wrapper
17
+ *
18
+ * A11y
19
+ * β€’ Renders role="toolbar" on the root.
20
+ * β€’ Pass `aria-label` or `aria-labelledby` so screen readers know what this toolbar does.
21
+ *
22
+ * Theming
23
+ * β€’ Skin reads Garden tokens only (no hard-coded colors) in `styles/toolbar.css`.
24
+ * β€’ Typical pattern: place ghost/subtle Buttons inside so toolbar feels calm by default.
25
+ */
26
+ import * as React from 'react';
27
+ /**
28
+ * Root toolbar container.
29
+ *
30
+ * Example:
31
+ * <Toolbar aria-label="Presence view tools">
32
+ * <ToolbarGroup>
33
+ * <Button variant="ghost" tone="default">Today</Button>
34
+ * <Button variant="ghost" tone="default">Week</Button>
35
+ * </ToolbarGroup>
36
+ * <ToolbarGroup>
37
+ * <Select>...</Select>
38
+ * </ToolbarGroup>
39
+ * </Toolbar>
40
+ */
41
+ export const Toolbar = React.forwardRef(function Toolbar({ orientation = 'horizontal', density = 'cozy', className, role, ...rest }, ref) {
42
+ const dataAttrs = {
43
+ 'data-ui': 'toolbar',
44
+ 'data-orientation': orientation,
45
+ 'data-density': density,
46
+ };
47
+ return (_jsx("div", { ...rest, ...dataAttrs, ref: ref, role: role ?? 'toolbar', className: className }));
48
+ });
49
+ /**
50
+ * Optional group wrapper for logically related controls inside a toolbar.
51
+ *
52
+ * <Toolbar>
53
+ * <ToolbarGroup>primary controls…</ToolbarGroup>
54
+ * <ToolbarGroup>secondary controls…</ToolbarGroup>
55
+ * </Toolbar>
56
+ */
57
+ export const ToolbarGroup = React.forwardRef(function ToolbarGroup({ className, ...rest }, ref) {
58
+ return (_jsx("div", { ...rest, ref: ref, "data-part": "group", className: className }));
59
+ });
60
+ Toolbar.displayName = 'Toolbar';
61
+ ToolbarGroup.displayName = 'ToolbarGroup';
62
+ //# sourceMappingURL=toolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolbar.js","sourceRoot":"","sources":["../../src/primitives/toolbar.tsx"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAY/B;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,KAAK,CAAC,UAAU,CAA+B,SAAS,OAAO,CACpF,EAAE,WAAW,GAAG,YAAY,EAAE,OAAO,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,EAC1E,GAAG;IAEH,MAAM,SAAS,GAAG;QAChB,SAAS,EAAE,SAAS;QACpB,kBAAkB,EAAE,WAAW;QAC/B,cAAc,EAAE,OAAO;KACf,CAAC;IAEX,OAAO,CACL,iBACM,IAAI,KACJ,SAAS,EACb,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,IAAI,IAAI,SAAS,EACvB,SAAS,EAAE,SAAS,GACpB,CACH,CAAC;AACJ,CAAC,CAAC,CAAC;AAIH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC,UAAU,CAAoC,SAAS,YAAY,CACnG,EAAE,SAAS,EAAE,GAAG,IAAI,EAAE,EACtB,GAAG;IAEH,OAAO,CACL,iBACM,IAAI,EACR,GAAG,EAAE,GAAG,eACE,OAAO,EACjB,SAAS,EAAE,SAAS,GACpB,CACH,CAAC;AACJ,CAAC,CAAC,CAAC;AAEH,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;AAChC,YAAY,CAAC,WAAW,GAAG,cAAc,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gratiaos/ui",
3
- "version": "1.0.5",
3
+ "version": "1.2.0",
4
4
  "funding": "https://github.com/sponsors/GratiaOS",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0-only",
@@ -46,6 +46,10 @@
46
46
  "types": "./dist/primitives/badge.d.ts",
47
47
  "import": "./dist/primitives/badge.js"
48
48
  },
49
+ "./primitives/Toolbar": {
50
+ "types": "./dist/primitives/toolbar.d.ts",
51
+ "import": "./dist/primitives/toolbar.js"
52
+ },
49
53
  "./styles/base.css": {
50
54
  "default": "./styles/base.css"
51
55
  },
@@ -72,6 +76,9 @@
72
76
  },
73
77
  "./styles/badge.css": {
74
78
  "default": "./styles/badge.css"
79
+ },
80
+ "./styles/toolbar.css": {
81
+ "default": "./styles/toolbar.css"
75
82
  }
76
83
  },
77
84
  "files": [
@@ -92,6 +99,9 @@
92
99
  "peerDependencies": {
93
100
  "react": "^18 || ^19"
94
101
  },
102
+ "dependencies": {
103
+ "@gratiaos/presence-kernel": "workspace:*"
104
+ },
95
105
  "devDependencies": {
96
106
  "typescript": "^5.9.3",
97
107
  "react": "^19.2.0",
package/styles/base.css CHANGED
@@ -6,3 +6,6 @@
6
6
  @import './toast.css';
7
7
  @import './badge.css';
8
8
  @import './whisper.css';
9
+ @import './header.css';
10
+ @import './select.css';
11
+ @import './toolbar.css';
package/styles/field.css CHANGED
@@ -123,17 +123,6 @@
123
123
  min-height: 2.5rem;
124
124
  }
125
125
 
126
- /* Select tweaks */
127
- [data-ui='field'] [data-part='control'] select {
128
- appearance: none;
129
- padding-right: 1.5rem; /* room for chevron */
130
- background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
131
- background-position: right 0.5rem center, right 0.25rem center;
132
- background-size: 6px 6px, 6px 6px;
133
- background-repeat: no-repeat;
134
- color: var(--text);
135
- }
136
-
137
126
  /* Disabled/read-only native controls (mirrors root [data-disabled]) */
138
127
  [data-ui='field'] [data-part='control'] :is(input, textarea, select):disabled,
139
128
  [data-ui='field'][data-disabled] [data-part='control'] :is(input, textarea, select) {
@@ -0,0 +1,93 @@
1
+ /* ─────────────────────────────────────────
2
+ Garden UI β€” ConductorChip skin (opt-in)
3
+ Whisper: "name the conductor softly." 🌬️
4
+ Purpose | Data API | Notes
5
+ ─────────────────────────────────────── */
6
+ @layer components {
7
+ [data-ui='conductor-chip'] {
8
+ display: inline-flex;
9
+ align-items: center;
10
+ justify-content: center;
11
+ gap: 0.35rem;
12
+ padding: 0.125rem 0.5625rem;
13
+ border-radius: 9999px;
14
+ font-size: 0.6875rem;
15
+ font-weight: 600;
16
+ letter-spacing: 0.02em;
17
+ text-transform: uppercase;
18
+ color: var(--tone-ink);
19
+ background: color-mix(in oklab, var(--tone-accent) 22%, transparent);
20
+ border: 1px solid color-mix(in oklab, var(--tone-accent) 38%, transparent);
21
+ box-shadow: var(--tone-glow, 0 0 24px color-mix(in oklab, var(--tone-accent) 30%, transparent));
22
+ backdrop-filter: blur(8px);
23
+ pointer-events: auto;
24
+ transition:
25
+ background var(--duration-snug, 160ms) var(--ease-soft, ease),
26
+ color var(--duration-snug, 160ms) var(--ease-soft, ease),
27
+ border-color var(--duration-snug, 160ms) var(--ease-soft, ease),
28
+ box-shadow var(--duration-snug, 160ms) var(--ease-soft, ease);
29
+ }
30
+
31
+ @media (prefers-reduced-motion: reduce) {
32
+ [data-ui='conductor-chip'] {
33
+ transition: none;
34
+ }
35
+ }
36
+
37
+ [data-ui='personal-pulse'] {
38
+ position: absolute;
39
+ bottom: 18px;
40
+ left: 72px;
41
+ width: 6px;
42
+ height: 6px;
43
+ border-radius: 999px;
44
+ background: var(--tone-accent);
45
+ opacity: 0.25;
46
+ will-change: transform, opacity;
47
+ pointer-events: none;
48
+ }
49
+
50
+ @media (prefers-reduced-motion: reduce) {
51
+ [data-ui='personal-pulse'] {
52
+ animation: none !important;
53
+ }
54
+ }
55
+
56
+ @keyframes idle-pulse {
57
+ 0%,
58
+ 100% {
59
+ opacity: 0.18;
60
+ transform: scale(1);
61
+ }
62
+ 50% {
63
+ opacity: 0.4;
64
+ transform: scale(1.12);
65
+ }
66
+ }
67
+
68
+ :root {
69
+ --constellation-dot: color-mix(in oklab, var(--tone-ink) 55%, transparent);
70
+ --constellation-active: var(--tone-accent);
71
+ }
72
+
73
+ [data-ui='constellation'] {
74
+ display: flex;
75
+ gap: 8px;
76
+ justify-content: center;
77
+ opacity: 0.32;
78
+ pointer-events: none;
79
+ user-select: none;
80
+ }
81
+
82
+ [data-ui='constellation'] .dot {
83
+ width: 3px;
84
+ height: 3px;
85
+ border-radius: 50%;
86
+ background: var(--constellation-dot);
87
+ }
88
+
89
+ [data-ui='constellation'] .dot.active {
90
+ background: var(--constellation-active);
91
+ opacity: 0.9;
92
+ }
93
+ }
package/styles/pad.css CHANGED
@@ -1,17 +1,28 @@
1
1
  /* 1) Pad runtime mappings β€” inherit from global theme */
2
2
  @layer base {
3
3
  :root {
4
+ /* Tone alignment β€” sync Pad surfacing with main layout frequency mix */
5
+ --tone-surface: var(--color-surface, #0F1317);
6
+ --tone-ink: var(--color-ink, var(--color-text, #E6EDF5));
7
+ --tone-accent: var(--color-accent, #3ED0C8);
8
+
9
+ --pad-bg-0: color-mix(in oklch, var(--tone-surface) 94%, var(--tone-ink));
10
+ --pad-bg-1: color-mix(in oklch, var(--tone-surface) 90%, var(--tone-ink));
11
+ --pad-ink-muted: color-mix(in oklch, var(--tone-ink) 65%, var(--tone-surface));
12
+ --pad-border-soft: color-mix(in oklch, var(--tone-ink) 8%, var(--tone-surface));
13
+ --pad-focus-ring: color-mix(in oklch, var(--tone-accent) 70%, transparent);
14
+
4
15
  /* Surfaces & text */
5
- --pad-surface: var(--color-surface);
6
- --pad-elev: var(--color-elev);
7
- --pad-border: var(--color-border);
8
- --pad-text: var(--color-text);
9
- --pad-text-muted: var(--color-muted);
16
+ --pad-surface: var(--pad-bg-0, var(--color-surface));
17
+ --pad-elev: var(--pad-bg-1, var(--color-elev));
18
+ --pad-border: var(--pad-border-soft, var(--color-border));
19
+ --pad-text: var(--tone-ink, var(--color-text));
20
+ --pad-text-muted: var(--pad-ink-muted, var(--color-muted));
10
21
  --pad-text-subtle: var(--color-subtle);
11
22
  --pad-text-faint: var(--color-faint);
12
23
 
13
24
  /* Accent system */
14
- --pad-accent: var(--color-accent);
25
+ --pad-accent: var(--tone-accent);
15
26
  --pad-onaccent: var(--color-on-accent);
16
27
 
17
28
  /* Shape & depth */
@@ -44,6 +55,43 @@
44
55
  .pad-ring {
45
56
  box-shadow: 0 0 0 3px var(--pad-ring);
46
57
  }
58
+
59
+ /* Tabs */
60
+ [data-pad-tabs] [role='tab'] {
61
+ background: transparent;
62
+ color: var(--pad-text-muted);
63
+ border: 0;
64
+ border-bottom: 1px solid var(--pad-border);
65
+ border-radius: 0;
66
+ padding-bottom: 0.4rem;
67
+ }
68
+ [data-pad-tabs] [role='tab'][aria-selected='true'] {
69
+ color: var(--pad-text);
70
+ border-bottom-color: var(--pad-accent);
71
+ box-shadow: 0 2px 0 0 color-mix(in oklch, var(--pad-accent) 55%, transparent);
72
+ }
73
+
74
+ /* Inputs */
75
+ .codex-portal,
76
+ .pad-input,
77
+ .pad-textarea {
78
+ background: var(--pad-elev);
79
+ color: var(--pad-text);
80
+ border: 1px solid var(--pad-border);
81
+ border-radius: var(--pad-radius);
82
+ transition: border-color var(--duration-snug, 160ms) var(--ease-soft),
83
+ box-shadow var(--duration-snug, 160ms) var(--ease-soft);
84
+ }
85
+ .codex-portal:focus,
86
+ .pad-input:focus,
87
+ .pad-textarea:focus {
88
+ outline: 0;
89
+ border-color: color-mix(in oklch, var(--pad-accent) 35%, var(--pad-border));
90
+ box-shadow: 0 0 0 2px var(--pad-focus-ring);
91
+ }
92
+ .pad-textarea::placeholder {
93
+ color: var(--pad-text-muted);
94
+ }
47
95
  }
48
96
 
49
97
  /* 3) Pad mood & depth utilities (Layered Synesthetic UI) */
@@ -0,0 +1,145 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ Garden UI β€” Select skin
3
+ Whisper: "choices should feel gentle, never rushed." 🌬️
4
+
5
+ Purpose
6
+ β€’ Visual skin for the headless Select primitive.
7
+ β€’ Provides chrome for standalone <select>; yields to Field when nested.
8
+
9
+ Data API
10
+ β€’ [data-ui='select'] β€” root hook
11
+ β€’ [data-disabled] β€” dim + block interaction
12
+ β€’ [data-variant='ghost'] β€” minimal chrome, ideal for toolbars
13
+ β€’ [data-state='invalid'] β€” highlight as error/invalid
14
+
15
+ Integration
16
+ β€’ When placed inside [data-ui='field'] [data-part='control'],
17
+ the Field shell owns layout, border, and focus ring; this skin
18
+ strips its own chrome so the two don’t fight.
19
+
20
+ Notes
21
+ β€’ Uses Garden tokens (surface, border, accent) β€” no hardcoded hex.
22
+ β€’ Caret is drawn with CSS gradients; no external icon asset.
23
+ ──────────────────────────────────────────────────────────── */
24
+
25
+ [data-ui='select'] {
26
+ appearance: none;
27
+ background: var(--surface-elevated, color-mix(in oklab, var(--color-surface) 90%, white 10%));
28
+ border-radius: var(--radius-lg, 0.85rem);
29
+ border: 1px solid var(--border-subtle, color-mix(in oklab, var(--color-accent) 15%, transparent 85%));
30
+ padding: 0.5rem 1.9rem 0.5rem 0.85rem;
31
+ font: inherit;
32
+ color: var(--text, inherit);
33
+ min-width: 8rem;
34
+ line-height: 1.4;
35
+ position: relative;
36
+ transition:
37
+ border-color 160ms var(--ease-soft, ease),
38
+ box-shadow 160ms var(--ease-soft, ease),
39
+ background 160ms var(--ease-soft, ease);
40
+ }
41
+
42
+ [data-ui='select'][data-variant='ghost'] {
43
+ background: transparent;
44
+ border-color: transparent;
45
+ box-shadow: none;
46
+ border-radius: var(--radius-md, 0.375rem);
47
+ padding-inline: 0.25rem 1.5rem; /* tighter, but keep room for caret */
48
+ min-width: 0;
49
+ }
50
+
51
+ [data-ui='select'][data-variant='ghost']:hover:not([data-disabled]) {
52
+ background: color-mix(in oklab, var(--color-accent) 10%, transparent 90%);
53
+ border-color: transparent;
54
+ }
55
+
56
+ [data-ui='select'][data-variant='ghost']:focus-visible {
57
+ outline: none;
58
+ border-color: transparent;
59
+ box-shadow:
60
+ 0 0 0 1px color-mix(in oklab, var(--color-accent) 35%, transparent 65%),
61
+ 0 0 0 3px color-mix(in oklab, var(--color-accent) 12%, transparent 88%);
62
+ }
63
+
64
+ [data-ui='select']::after {
65
+ content: '';
66
+ position: absolute;
67
+ pointer-events: none;
68
+ top: 50%;
69
+ right: 0.85rem;
70
+ transform: translateY(-45%);
71
+ width: 0;
72
+ height: 0;
73
+ border-left: 4px solid transparent;
74
+ border-right: 4px solid transparent;
75
+ border-top: 5px solid color-mix(in oklab, currentColor 75%, transparent);
76
+ opacity: 0.7;
77
+ transition:
78
+ transform 160ms var(--ease-soft, ease),
79
+ opacity 160ms var(--ease-soft, ease);
80
+ }
81
+
82
+ [data-ui='select']:hover:not([data-disabled])::after {
83
+ opacity: 0.9;
84
+ }
85
+
86
+ [data-ui='select']:focus-visible::after {
87
+ transform: translateY(-45%) scale(1.05);
88
+ opacity: 1;
89
+ }
90
+
91
+ [data-ui='select']:hover:not([data-disabled]) {
92
+ border-color: color-mix(in oklab, var(--color-accent) 35%, transparent 65%);
93
+ }
94
+
95
+ [data-ui='select']:focus-visible {
96
+ outline: 0;
97
+ box-shadow:
98
+ 0 0 0 1px color-mix(in oklab, var(--color-accent) 45%, transparent 55%),
99
+ 0 0 0 4px color-mix(in oklab, var(--color-accent) 18%, transparent 82%);
100
+ }
101
+
102
+ [data-ui='select'][data-disabled] {
103
+ opacity: 0.55;
104
+ cursor: not-allowed;
105
+ }
106
+
107
+ [data-ui='select'][data-state='invalid'] {
108
+ border-color: var(--border-danger, color-mix(in oklab, var(--color-danger) 55%, transparent 45%));
109
+ }
110
+
111
+ /* ==========================================================================
112
+ Select inside Field
113
+ When used inside a Garden Field, the Select strips its own shell chrome
114
+ (background, border, etc.) but still renders its caret. The Field shell
115
+ provides the outer layout, border, and focus ring; Select only draws its
116
+ dropdown chevron inside.
117
+ ========================================================================== */
118
+
119
+ [data-ui='field'] [data-part='control'] [data-ui='select'] {
120
+ /* let Field own the shell chrome; Select just draws its caret */
121
+ appearance: none;
122
+ background: transparent;
123
+ border: 0;
124
+ padding: 0;
125
+ padding-right: 1.5rem; /* room for caret */
126
+ min-width: 0;
127
+ box-shadow: none;
128
+ background-image:
129
+ linear-gradient(45deg, transparent 50%, color-mix(in oklab, currentColor 70%, transparent) 50%),
130
+ linear-gradient(135deg, color-mix(in oklab, currentColor 70%, transparent) 50%, transparent 50%);
131
+ background-position:
132
+ right 0.75rem center,
133
+ right 0.55rem center;
134
+ background-repeat: no-repeat;
135
+ background-size: 6px 6px, 6px 6px;
136
+ }
137
+
138
+ [data-ui='field'] [data-part='control'] [data-ui='select']::after {
139
+ content: none;
140
+ }
141
+
142
+ [data-ui='field'] [data-part='control'] [data-ui='select']:focus-visible {
143
+ outline: none;
144
+ box-shadow: none;
145
+ }
package/styles/theme.css CHANGED
@@ -162,6 +162,49 @@
162
162
 
163
163
  /* === Runtime dark mapping (no duplicate @theme) === */
164
164
  @layer base {
165
+ :root {
166
+ --tone-accent: color-mix(in oklab, var(--color-accent) 82%, white 18%);
167
+ --tone-surface: color-mix(in oklab, var(--color-surface) 92%, var(--tone-accent) 8%);
168
+ --tone-ink: color-mix(in oklab, var(--color-text) 96%, white 4%);
169
+ --tone-glow: 0 0 24px color-mix(in oklab, var(--tone-accent) 32%, transparent);
170
+ }
171
+
172
+ :root[data-mood='soft'] {
173
+ --tone-surface: color-mix(in oklab, var(--color-surface) 92%, var(--tone-accent) 8%);
174
+ --tone-ink: color-mix(in oklab, var(--color-text) 96%, white 4%);
175
+ --tone-glow: 0 0 24px color-mix(in oklab, var(--tone-accent) 32%, transparent);
176
+ }
177
+
178
+ :root[data-mood='presence'] {
179
+ --tone-surface: color-mix(in oklab, var(--color-surface) 88%, var(--tone-accent) 12%);
180
+ --tone-ink: color-mix(in oklab, var(--color-text) 94%, white 6%);
181
+ --tone-glow: 0 0 28px color-mix(in oklab, var(--tone-accent) 38%, transparent);
182
+ }
183
+
184
+ :root[data-mood='focused'] {
185
+ --tone-surface: color-mix(in oklab, var(--color-surface) 84%, var(--tone-accent) 16%);
186
+ --tone-ink: color-mix(in oklab, var(--color-text) 92%, white 8%);
187
+ --tone-glow: 0 0 32px color-mix(in oklab, var(--tone-accent) 46%, transparent);
188
+ }
189
+
190
+ :root[data-mood='celebratory'] {
191
+ --tone-surface: color-mix(in oklab, var(--color-surface) 82%, var(--tone-accent) 18%);
192
+ --tone-ink: color-mix(in oklab, var(--color-text) 90%, white 10%);
193
+ --tone-glow: 0 0 36px color-mix(in oklab, var(--tone-accent) 54%, transparent);
194
+ }
195
+
196
+ :root[data-authority='local-primary'] {
197
+ --tone-accent: #9fc2ff;
198
+ }
199
+
200
+ :root[data-authority='remote-primary'] {
201
+ --tone-accent: #b2ffa8;
202
+ }
203
+
204
+ :root[data-authority='distributed'] {
205
+ --tone-accent: #ffd59e;
206
+ }
207
+
165
208
  :root[data-theme='dark'],
166
209
  :root.dark {
167
210
  --color-surface: oklch(21% 0.07 241);
@@ -223,6 +266,12 @@
223
266
  --drop-shadow-depth-1: 0 1px 1px rgb(0 0 0 / 0.25);
224
267
  --drop-shadow-depth-2: 0 3px 3px rgb(0 0 0 / 0.32);
225
268
  --drop-shadow-depth-3: 0 6px 6px rgb(0 0 0 / 0.42);
269
+
270
+ --tone-accent: color-mix(in oklab, var(--color-accent) 70%, white 30%);
271
+ --tone-surface: color-mix(in oklab, var(--color-surface) 88%, var(--tone-accent) 12%);
272
+ --tone-ink: color-mix(in oklab, var(--color-text) 98%, white 2%);
273
+ --tone-glow: 0 0 32px color-mix(in oklab, var(--tone-accent) 42%, transparent);
274
+ --constellation-dot: color-mix(in oklab, var(--tone-ink) 65%, transparent);
226
275
  }
227
276
  }
228
277
 
@@ -256,11 +305,11 @@
256
305
  --glow-intensity: 0.08;
257
306
  --grain-strength: 0.1;
258
307
  --grain-size: 160px; /* larger tile -> softer grain geometry */
259
- --track-glow: color-mix(in oklab, var(--color-accent) 45%, transparent);
260
- --track-shadow: color-mix(in oklab, var(--color-text) 25%, transparent);
261
- --scene-highlight: color-mix(in oklab, var(--color-accent) 55%, transparent);
262
- --presence-dot-color: color-mix(in oklab, var(--color-accent) 65%, white 15%);
263
- --ghost-trail-color: color-mix(in oklab, var(--color-accent) 18%, transparent);
308
+ --track-glow: color-mix(in oklab, var(--tone-accent) 45%, transparent);
309
+ --track-shadow: color-mix(in oklab, var(--tone-accent) 25%, transparent);
310
+ --scene-highlight: color-mix(in oklab, var(--tone-accent) 55%, transparent);
311
+ --presence-dot-color: color-mix(in oklab, var(--tone-accent) 65%, white 15%);
312
+ --ghost-trail-color: color-mix(in oklab, var(--tone-accent) 18%, transparent);
264
313
 
265
314
  /* calmer hero gradient and slower entrances */
266
315
  --pad-hero-start: color-mix(in oklab, var(--accent) 8%, var(--surface));
@@ -279,11 +328,11 @@
279
328
  --glow-intensity: 0.12;
280
329
  --grain-strength: 0.12;
281
330
  --grain-size: 130px; /* tighter tile -> slightly crisper grain */
282
- --track-glow: color-mix(in oklab, var(--color-accent) 65%, transparent);
283
- --track-shadow: color-mix(in oklab, var(--color-accent) 28%, transparent);
284
- --scene-highlight: color-mix(in oklab, var(--color-accent) 70%, transparent);
285
- --presence-dot-color: color-mix(in oklab, var(--color-accent) 78%, white 12%);
286
- --ghost-trail-color: color-mix(in oklab, var(--color-accent) 24%, transparent);
331
+ --track-glow: color-mix(in oklab, var(--tone-accent) 65%, transparent);
332
+ --track-shadow: color-mix(in oklab, var(--tone-accent) 28%, transparent);
333
+ --scene-highlight: color-mix(in oklab, var(--tone-accent) 70%, transparent);
334
+ --presence-dot-color: color-mix(in oklab, var(--tone-accent) 78%, white 12%);
335
+ --ghost-trail-color: color-mix(in oklab, var(--tone-accent) 24%, transparent);
287
336
 
288
337
  --pad-hero-start: color-mix(in oklab, var(--accent) 16%, var(--surface));
289
338
  --pad-hero-end: color-mix(in oklab, var(--accent) 6%, var(--surface));
@@ -301,11 +350,11 @@
301
350
  --glow-intensity: 0.18;
302
351
  --grain-strength: 0.14;
303
352
  --grain-size: 120px; /* smallest tile -> more lively grain */
304
- --track-glow: color-mix(in oklab, var(--color-accent) 90%, white 10%);
305
- --track-shadow: color-mix(in oklab, var(--color-accent) 35%, transparent);
306
- --scene-highlight: color-mix(in oklab, var(--color-accent) 92%, white 8%);
307
- --presence-dot-color: color-mix(in oklab, var(--color-accent) 94%, white 12%);
308
- --ghost-trail-color: color-mix(in oklab, var(--color-accent) 32%, transparent);
353
+ --track-glow: color-mix(in oklab, var(--tone-accent) 90%, white 10%);
354
+ --track-shadow: color-mix(in oklab, var(--tone-accent) 35%, transparent);
355
+ --scene-highlight: color-mix(in oklab, var(--tone-accent) 92%, white 8%);
356
+ --presence-dot-color: color-mix(in oklab, var(--tone-accent) 94%, white 12%);
357
+ --ghost-trail-color: color-mix(in oklab, var(--tone-accent) 32%, transparent);
309
358
 
310
359
  --pad-hero-start: color-mix(in oklab, var(--accent) 24%, var(--surface));
311
360
  --pad-hero-end: color-mix(in oklab, var(--accent) 12%, var(--surface));
@@ -363,11 +412,11 @@
363
412
  --ring-alpha: 0.18; /* 0–1 β€” whisper ring strength */
364
413
  --grain-strength: 0.12; /* 0–1 β€” ambient grain strength */
365
414
  --grain-size: 140px; /* px β€” grain tile size used by card grain overlay */
366
- --track-glow: color-mix(in oklab, var(--color-accent) 55%, transparent);
367
- --track-shadow: color-mix(in oklab, var(--color-text) 25%, transparent);
368
- --scene-highlight: color-mix(in oklab, var(--color-accent) 60%, transparent);
369
- --presence-dot-color: color-mix(in oklab, var(--color-accent) 70%, white 15%);
370
- --ghost-trail-color: color-mix(in oklab, var(--color-accent) 22%, transparent);
415
+ --track-glow: color-mix(in oklab, var(--tone-accent) 55%, transparent);
416
+ --track-shadow: color-mix(in oklab, var(--tone-accent) 32%, transparent);
417
+ --scene-highlight: color-mix(in oklab, var(--tone-accent) 60%, transparent);
418
+ --presence-dot-color: color-mix(in oklab, var(--tone-accent) 70%, white 15%);
419
+ --ghost-trail-color: color-mix(in oklab, var(--tone-accent) 22%, transparent);
371
420
  }
372
421
 
373
422
  @layer utilities {
@@ -0,0 +1,77 @@
1
+ /* ─────────────────────────────────────────────────────────────
2
+ Garden UI β€” Toolbar skin
3
+ Whisper: "tools should feel near, not loud." 🌬️
4
+
5
+ Purpose
6
+ β€’ Calm container for view / filter / mode controls.
7
+ β€’ Pairs well with ghost/subtle Buttons and small Selects.
8
+
9
+ Data API (from primitives/toolbar.tsx)
10
+ β€’ [data-ui='toolbar'] β€” root
11
+ β€’ [data-orientation='horizontal|vertical']
12
+ β€’ [data-density='cozy|snug']
13
+ β€’ [data-part='group'] β€” logical clusters of controls
14
+
15
+ Theming
16
+ β€’ Uses Garden tokens:
17
+ --surface, --elev, --color-border, --radius-lg, --shadow-soft,
18
+ --space-*, --text-subtle.
19
+
20
+ Notes
21
+ β€’ Keep it low-contrast by default; toolbar is a frame, not the star.
22
+ β€’ Use in layouts like: card header, pad header, page sub-nav.
23
+ ──────────────────────────────────────────────────────────── */
24
+
25
+ @layer components {
26
+ [data-ui='toolbar'] {
27
+ display: inline-flex;
28
+ align-items: center;
29
+ gap: var(--space-2, 0.5rem);
30
+
31
+ padding: 0.375rem 0.5rem;
32
+ border-radius: var(--radius-lg, 0.75rem);
33
+ border: 1px solid color-mix(in oklab, var(--color-border) 80%, transparent);
34
+ background: color-mix(in oklab, var(--surface) 88%, var(--elev) 12%);
35
+ box-shadow: var(--shadow-soft, 0 8px 24px rgba(15, 23, 42, 0.08));
36
+
37
+ color: var(--text-subtle);
38
+ }
39
+
40
+ [data-ui='toolbar'][data-orientation='vertical'] {
41
+ flex-direction: column;
42
+ align-items: stretch;
43
+ }
44
+
45
+ [data-ui='toolbar'][data-density='snug'] {
46
+ padding-block: 0.25rem;
47
+ }
48
+
49
+ /* groups: slight separation when multiple clusters exist */
50
+ [data-ui='toolbar'] [data-part='group'] {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ gap: var(--space-1, 0.375rem);
54
+ }
55
+
56
+ /* subtle divider between groups, only when there are siblings */
57
+ [data-ui='toolbar'] [data-part='group'] + [data-part='group']::before {
58
+ content: '';
59
+ display: inline-block;
60
+ width: 1px;
61
+ align-self: stretch;
62
+ margin-inline: var(--space-1, 0.375rem);
63
+ background: color-mix(in oklab, var(--color-border) 70%, transparent);
64
+ }
65
+
66
+ /* Make ghost/subtle buttons feel extra calm inside toolbars (optional hint) */
67
+ [data-ui='toolbar'] [data-ui='button'][data-variant='ghost'],
68
+ [data-ui='toolbar'] [data-ui='button'][data-variant='subtle'] {
69
+ --button-bg-hover: color-mix(in oklab, var(--surface) 80%, var(--elev) 20%);
70
+ --button-fg: var(--text-subtle);
71
+ }
72
+
73
+ /* Selects inside toolbar β€” a bit tighter */
74
+ [data-ui='toolbar'] [data-ui='select'] {
75
+ font-size: 0.875rem;
76
+ }
77
+ }