@particle-academy/react-fancy 3.2.1 → 3.3.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
@@ -1,6 +1,6 @@
1
1
  # @particle-academy/react-fancy
2
2
 
3
- React UI component library — the React port of the `fancy-flux` Blade/Livewire component library. The goal is **visual and behavioral parity** with fancy-flux.
3
+ React UI component library for **Human+ UX** controlled, agent-bridgeable primitives that humans and agents share. Every component exposes `value` / `onChange`, stable handles, and JSON-friendly props so MCP bridges can drive the UI without DOM scraping.
4
4
 
5
5
  ## Inertia.js integration
6
6
 
@@ -240,7 +240,7 @@ src/
240
240
 
241
241
  - `Size` — `"xs" | "sm" | "md" | "lg" | "xl"`
242
242
  - `Color` — Full Tailwind color palette (17 colors)
243
- - `ActionColor` — Subset of 10 standalone colors matching fancy-flux
243
+ - `ActionColor` — Subset of 10 standalone colors used by Action and friends
244
244
  - `Variant` — `"solid" | "outline" | "ghost" | "soft"`
245
245
  - `Placement` — `"top" | "bottom" | "left" | "right"` + start/end variants
246
246
 
@@ -268,16 +268,23 @@ Every component follows this structure:
268
268
 
269
269
  Use `lucide-react` as the default icon library. It is a dependency of this package and marked as external in tsup. Components should import icons directly (e.g., `import { X, ChevronDown } from "lucide-react"`).
270
270
 
271
- ### Parity with fancy-flux
271
+ ### Human+ UX contract
272
272
 
273
- - **Always reference the corresponding Blade component** in `packages/fancy-flux/stubs/resources/views/flux/` when implementing or updating a component. Match the Tailwind classes, color values, state logic, and dark mode support exactly.
274
- - React-specific additions (e.g., `loading` spinner, `href` anchor rendering) are fine — they don't exist in Blade but are idiomatic in React.
275
- - Icons are passed as `ReactNode` (not string names like Blade's `<flux:icon>`). This is the correct React pattern.
273
+ Every interactive component must:
274
+
275
+ - Expose **`value` + `onChange`** so an agent can read and write state. No internal-only state for anything an agent might want to inspect or change.
276
+ - Carry **stable handles** — `id`, `data-react-fancy-*` attributes, or a selector prop — so MCP bridges can address elements without guessing DOM structure.
277
+ - Accept **JSON-friendly props** — arrays of objects, primitives, simple discriminated unions. Avoid forcing React children for things the agent needs to populate.
278
+ - Support a **bridgeable surface**: a `register<Surface>Bridge(server, { adapter })` (in `agent-integrations`) should be sketchable in one sitting.
279
+ - Broadcast **`AgentActivity`** events for mutations so presence, undo, and coaching layers can compose.
280
+ - Provide **trust-but-verify** hooks for destructive actions — agents propose, humans confirm via `pendingMode` / staged-write affordances.
281
+
282
+ Purely visual primitives (labels, dividers, layout shells) only owe the first bullet.
276
283
 
277
284
  ### Styling
278
285
 
279
286
  - **Tailwind v4** — CSS-first config. Use `@import "tailwindcss"` not `@tailwind` directives.
280
- - **Dark mode** — Every color variant must include `dark:` equivalents. Check fancy-flux for the exact classes. Portal components get dark mode automatically via the Portal wrapper.
287
+ - **Dark mode** — Every color variant must include `dark:` equivalents. Portal components get dark mode automatically via the Portal wrapper.
281
288
  - **No component library deps** — Only `clsx`, `tailwind-merge`, and `lucide-react`. Don't add Radix, Headless UI, or similar.
282
289
  - Class maps should be `Record<Size, string>` (or similar) constants outside the component function, not inline.
283
290
 
package/dist/index.cjs CHANGED
@@ -6673,6 +6673,127 @@ var Timeline = Object.assign(TimelineRoot, {
6673
6673
  Item: TimelineItem,
6674
6674
  Block: TimelineBlock
6675
6675
  });
6676
+ var TONE_CLASSES = {
6677
+ violet: "bg-violet-500 hover:bg-violet-600",
6678
+ emerald: "bg-emerald-500 hover:bg-emerald-600",
6679
+ sky: "bg-sky-500 hover:bg-sky-600",
6680
+ rose: "bg-rose-500 hover:bg-rose-600",
6681
+ amber: "bg-amber-500 hover:bg-amber-600",
6682
+ indigo: "bg-indigo-500 hover:bg-indigo-600",
6683
+ blue: "bg-blue-500 hover:bg-blue-600",
6684
+ zinc: "bg-zinc-500 hover:bg-zinc-600"
6685
+ };
6686
+ var OFF_CLASSES = "bg-zinc-100 hover:bg-zinc-200 dark:bg-zinc-800 dark:hover:bg-zinc-700";
6687
+ var TimeGrid = react.forwardRef(
6688
+ ({
6689
+ rows,
6690
+ cols,
6691
+ value,
6692
+ onChange,
6693
+ toneOn = "violet",
6694
+ cellWidth = 20,
6695
+ cellHeight = 16,
6696
+ sparseColLabels = true,
6697
+ toggleStripsOnHeaderClick = true,
6698
+ ariaCell,
6699
+ cellId,
6700
+ className
6701
+ }, ref) => {
6702
+ const [drag, setDrag] = react.useState(null);
6703
+ const setCell = (r, c, v) => {
6704
+ if (value[r]?.[c] === v) return;
6705
+ const next = value.map((row) => row.slice());
6706
+ next[r][c] = v;
6707
+ onChange(next);
6708
+ };
6709
+ const toggleRow = (r) => {
6710
+ if (!toggleStripsOnHeaderClick) return;
6711
+ const all = value[r].every(Boolean);
6712
+ const next = value.map((row) => row.slice());
6713
+ for (let c = 0; c < cols.length; c++) next[r][c] = !all;
6714
+ onChange(next);
6715
+ };
6716
+ const toggleCol = (c) => {
6717
+ if (!toggleStripsOnHeaderClick) return;
6718
+ const all = value.every((row) => row[c]);
6719
+ const next = value.map((row) => row.slice());
6720
+ for (let r = 0; r < rows.length; r++) next[r][c] = !all;
6721
+ onChange(next);
6722
+ };
6723
+ const colStep = Math.max(1, Math.floor(cols.length / 6));
6724
+ const onTone = TONE_CLASSES[toneOn];
6725
+ return /* @__PURE__ */ jsxRuntime.jsx(
6726
+ "div",
6727
+ {
6728
+ ref,
6729
+ "data-react-fancy-timegrid": "",
6730
+ className: cn(
6731
+ "select-none overflow-x-auto",
6732
+ className
6733
+ ),
6734
+ onMouseLeave: () => setDrag(null),
6735
+ onMouseUp: () => setDrag(null),
6736
+ children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "text-[10px]", children: [
6737
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
6738
+ /* @__PURE__ */ jsxRuntime.jsx("th", {}),
6739
+ cols.map((label, c) => /* @__PURE__ */ jsxRuntime.jsx(
6740
+ "th",
6741
+ {
6742
+ onClick: () => toggleCol(c),
6743
+ style: { width: cellWidth },
6744
+ "data-react-fancy-timegrid-col": c,
6745
+ className: cn(
6746
+ "text-zinc-400",
6747
+ toggleStripsOnHeaderClick && "cursor-pointer hover:text-zinc-700 dark:hover:text-zinc-200"
6748
+ ),
6749
+ children: sparseColLabels ? c % colStep === 0 ? label : "" : label
6750
+ },
6751
+ c
6752
+ ))
6753
+ ] }) }),
6754
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { children: rows.map((label, r) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
6755
+ /* @__PURE__ */ jsxRuntime.jsx(
6756
+ "th",
6757
+ {
6758
+ onClick: () => toggleRow(r),
6759
+ "data-react-fancy-timegrid-row": r,
6760
+ className: cn(
6761
+ "pr-1 text-right font-medium text-zinc-500",
6762
+ toggleStripsOnHeaderClick && "cursor-pointer hover:text-zinc-800 dark:hover:text-zinc-200"
6763
+ ),
6764
+ children: label
6765
+ }
6766
+ ),
6767
+ cols.map((_, c) => {
6768
+ const on = value[r]?.[c] ?? false;
6769
+ return /* @__PURE__ */ jsxRuntime.jsx("td", { className: "p-px", children: /* @__PURE__ */ jsxRuntime.jsx(
6770
+ "button",
6771
+ {
6772
+ type: "button",
6773
+ onMouseDown: () => {
6774
+ const v = !on;
6775
+ setDrag(v);
6776
+ setCell(r, c, v);
6777
+ },
6778
+ onMouseEnter: () => drag !== null && setCell(r, c, drag),
6779
+ style: { width: cellWidth, height: cellHeight },
6780
+ "data-react-fancy-timegrid-cell": cellId ? cellId(r, c) : `${r}:${c}`,
6781
+ "aria-pressed": on,
6782
+ "aria-label": ariaCell ? ariaCell(r, c, on) : `${rows[r]} ${cols[c]} ${on ? "on" : "off"}`,
6783
+ className: cn(
6784
+ "block rounded-sm transition",
6785
+ on ? onTone : OFF_CLASSES
6786
+ )
6787
+ }
6788
+ ) }, c);
6789
+ })
6790
+ ] }, r)) })
6791
+ ] })
6792
+ }
6793
+ );
6794
+ }
6795
+ );
6796
+ TimeGrid.displayName = "TimeGrid";
6676
6797
  var Tooltip = react.forwardRef(
6677
6798
  function Tooltip2({ children, content, placement = "top", delay = 200, offset = 8, className }, _ref) {
6678
6799
  const [open, setOpen] = react.useState(false);
@@ -13179,6 +13300,7 @@ exports.Table = Table;
13179
13300
  exports.Tabs = Tabs;
13180
13301
  exports.Text = Text;
13181
13302
  exports.Textarea = Textarea;
13303
+ exports.TimeGrid = TimeGrid;
13182
13304
  exports.TimePicker = TimePicker;
13183
13305
  exports.Timeline = Timeline;
13184
13306
  exports.Toast = Toast;