@particle-academy/react-fancy 4.12.0 → 4.13.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.
@@ -22,22 +22,22 @@ import { AccordionPanel } from "@particle-academy/react-fancy";
22
22
  ```tsx
23
23
  <AccordionPanel orientation="horizontal" defaultValue={["wishlist"]}>
24
24
  <AccordionPanel.Section id="home" pinned>
25
- <Action icon="home" />
25
+ <Button icon="home" />
26
26
  </AccordionPanel.Section>
27
27
 
28
28
  <AccordionPanel.Section id="wishlist">
29
29
  <AccordionPanel.Trigger />
30
30
  <AccordionPanel.Content>
31
- <Action icon="list">Wishlist</Action>
32
- <Action icon="mail">Feedback</Action>
33
- <Action icon="file-text">Plans</Action>
31
+ <Button icon="list">Wishlist</Button>
32
+ <Button icon="mail">Feedback</Button>
33
+ <Button icon="file-text">Plans</Button>
34
34
  </AccordionPanel.Content>
35
35
  </AccordionPanel.Section>
36
36
 
37
37
  <AccordionPanel.Section id="board">
38
38
  <AccordionPanel.Trigger />
39
39
  <AccordionPanel.Content>
40
- <Action icon="grid">Board</Action>
40
+ <Button icon="grid">Board</Button>
41
41
  </AccordionPanel.Content>
42
42
  </AccordionPanel.Section>
43
43
  </AccordionPanel>
@@ -55,7 +55,7 @@ When closed, the Trigger renders as a standalone chevron button. When open, the
55
55
  {({ open }) => (open ? "Collapse" : "Expand")}
56
56
  </AccordionPanel.Trigger>
57
57
  <AccordionPanel.Content>
58
- <Action>Releases</Action>
58
+ <Button>Releases</Button>
59
59
  </AccordionPanel.Content>
60
60
  </AccordionPanel.Section>
61
61
  ```
package/docs/Action.md CHANGED
@@ -10,86 +10,7 @@
10
10
  > ```
11
11
  >
12
12
  > `ActionProps` → `ButtonProps`, `ActionColor` → `ButtonColor` (also deprecated
13
- > aliases). Full, current documentation lives in **[Button](./Button.md)**.
13
+ > aliases).
14
14
 
15
- A versatile button component with support for icons, emojis, avatars, badges, loading states, and color variants.
16
-
17
- ## Import
18
-
19
- ```tsx
20
- import { Action } from "@particle-academy/react-fancy"; // deprecated — prefer Button
21
- ```
22
-
23
- ## Basic Usage
24
-
25
- ```tsx
26
- <Action>Click me</Action> // identical to <Button>Click me</Button>
27
- ```
28
-
29
- ## Props
30
-
31
- | Prop | Type | Default | Description |
32
- |------|------|---------|-------------|
33
- | variant | `"default" \| "circle" \| "ghost"` | `"default"` | Shape/fill variant. `"ghost"` is transparent with subtle hover. |
34
- | color | `ActionColor` | - | Standalone color (overrides state colors). One of: `"blue"`, `"emerald"`, `"amber"`, `"red"`, `"violet"`, `"indigo"`, `"sky"`, `"rose"`, `"orange"`, `"zinc"` |
35
- | size | `"xs" \| "sm" \| "md" \| "lg" \| "xl"` | `"md"` | Button size |
36
- | active | `boolean` | - | Active state (blue highlight) |
37
- | checked | `boolean` | - | Checked state (emerald highlight) |
38
- | warn | `boolean` | - | Warning state (amber highlight) |
39
- | alert | `boolean` | - | Pulsing animation on the entire button |
40
- | icon | `string` | - | Leading icon slug resolved via the Icon component |
41
- | iconTrailing | `string` | - | Trailing icon slug |
42
- | iconPlace | `string` | `"left"` | Icon placement. Supports: `"left"`, `"right"`, `"top"`, `"bottom"`, and compound positions like `"top left"`, `"bottom right"` |
43
- | alertIcon | `string` | - | Pulsing alert icon slug |
44
- | alertIconTrailing | `boolean` | - | Position alert icon on trailing side |
45
- | emoji | `string` | - | Leading emoji slug |
46
- | emojiTrailing | `string` | - | Trailing emoji slug |
47
- | avatar | `string` | - | Avatar image URL |
48
- | avatarTrailing | `boolean` | - | Position avatar on trailing side |
49
- | badge | `string` | - | Badge text |
50
- | badgeTrailing | `boolean` | - | Position badge on trailing side |
51
- | sort | `string` | `"eiab"` | Sort order of decorative elements: `e`=emoji, `i`=icon, `a`=avatar, `b`=badge |
52
- | loading | `boolean` | `false` | Show loading spinner (disables the button) |
53
- | disabled | `boolean` | - | Disable the button |
54
- | href | `string` | - | Render as an anchor tag instead of a button. Sanitized to a safe-protocol allow-list (since v2.5.0) — `javascript:`, `data:`, `vbscript:` are silently dropped and the component renders a `<button>` instead of an `<a>`. |
55
-
56
- Also extends all native `<button>` HTML attributes (except `color`).
57
-
58
- ## Examples
59
-
60
- ### Icon button with color
61
-
62
- ```tsx
63
- <Action icon="pencil" color="blue" size="lg">
64
- Edit
65
- </Action>
66
- ```
67
-
68
- ### Circle icon button
69
-
70
- ```tsx
71
- <Action variant="circle" icon="plus" color="emerald" />
72
- ```
73
-
74
- ### Loading state with badge
75
-
76
- ```tsx
77
- <Action loading badge="3" badgeTrailing>
78
- Messages
79
- </Action>
80
- ```
81
-
82
- ### Link button with trailing icon
83
-
84
- ```tsx
85
- <Action href="/docs" iconTrailing="arrow-right">
86
- Read docs
87
- </Action>
88
- ```
89
-
90
- ### Ghost variant
91
-
92
- ```tsx
93
- <Action variant="ghost" color="red" icon="trash-2">Delete</Action>
94
- <Action variant="ghost" icon="download">Export</Action>
95
- ```
15
+ Do not use `Action` in new code, examples, or generated snippets always use
16
+ `Button`. Full, current documentation lives in **[Button](./Button.md)**.
@@ -0,0 +1,231 @@
1
+ # FileBrowser
2
+
3
+ Remote-capable file/folder browser + directory picker. Browses any tree the host can describe — local FS, HTTP, an MCP bridge, or a remote machine streaming snapshots — through two feeding modes that can be combined:
4
+
5
+ - **Provider mode (lazy pull):** supply `provider.loadChildren(path)`. Folders load on first expand with per-node loading and error states. The tree is never walked eagerly.
6
+ - **Snapshot mode (streamed push):** pass a JSON-friendly `snapshot` tree and replace/patch it as chunks arrive (relay / WebSocket / MCP). The snapshot is the source of truth for every path it covers; with a provider also present, unknown-depth folders stay lazily loadable (hybrid).
7
+
8
+ Fully controlled per the Human+ component contract; every row carries a `data-path` stable handle. Read-only in v1 — no content preview (pair with fancy-code's `FileViewer`) and no write operations.
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { FileBrowser } from "@particle-academy/react-fancy";
14
+ import type { FileEntry, FileSnapshotNode } from "@particle-academy/react-fancy";
15
+ ```
16
+
17
+ ## Basic Usage — provider mode
18
+
19
+ Any async listing works. A fake in-memory FS:
20
+
21
+ ```tsx
22
+ const FS: Record<string, FileEntry[]> = {
23
+ "/": [
24
+ { path: "/src", name: "src", kind: "dir", hasChildren: true },
25
+ { path: "/package.json", name: "package.json", kind: "file", size: 1204, mtime: "2026-07-01T10:00:00Z" },
26
+ { path: "/README.md", name: "README.md", kind: "file", size: 5310, mtime: "2026-06-28T09:30:00Z" },
27
+ ],
28
+ "/src": [
29
+ { path: "/src/App.tsx", name: "App.tsx", kind: "file", size: 2048 },
30
+ { path: "/src/components", name: "components", kind: "dir", hasChildren: true },
31
+ ],
32
+ "/src/components": [
33
+ { path: "/src/components/Button.tsx", name: "Button.tsx", kind: "file", size: 980 },
34
+ ],
35
+ };
36
+
37
+ const provider = {
38
+ loadChildren: async (path: string) => {
39
+ await new Promise((r) => setTimeout(r, 300)); // network latency
40
+ const entries = FS[path];
41
+ if (!entries) throw new Error(`No such directory: ${path}`);
42
+ return entries;
43
+ },
44
+ };
45
+
46
+ const [selected, setSelected] = useState<string | null>(null);
47
+
48
+ <FileBrowser
49
+ provider={provider}
50
+ value={selected}
51
+ onChange={(value) => setSelected(value as string | null)}
52
+ className="h-80"
53
+ />
54
+ ```
55
+
56
+ Each folder is fetched once, on first expand (or when it becomes the current directory). Failed loads show an inline error with a Retry button and fire `onError(path, error)`.
57
+
58
+ ## Snapshot mode — streamed tree
59
+
60
+ Feed a JSON-friendly tree and update it from outside as chunks land. The component re-derives everything from the new value — selection, expansion, and current path survive because paths are the identity:
61
+
62
+ ```tsx
63
+ const [tree, setTree] = useState<FileSnapshotNode[]>([
64
+ { path: "/logs", name: "logs", kind: "dir" }, // children unknown so far
65
+ ]);
66
+
67
+ // Later, as the remote machine pushes a chunk over your transport:
68
+ socket.on("fs-chunk", (chunk: FileSnapshotNode[]) => {
69
+ setTree((prev) => mergeChunk(prev, chunk)); // host-side merge — any shape works
70
+ });
71
+
72
+ <FileBrowser snapshot={tree} select="file" className="h-80" />
73
+ ```
74
+
75
+ - `children: []` marks a known-empty folder (no expand affordance, same as `hasChildren: false`); `children: undefined` marks unknown depth.
76
+ - Without a provider, unknown-depth folders are not expandable (there is nothing to load them with).
77
+ - **Hybrid:** pass `snapshot` *and* `provider` — the snapshot seeds instantly, the provider lazily fills folders the snapshot left unknown. Where the snapshot speaks, it wins.
78
+
79
+ ## Directory picker
80
+
81
+ ```tsx
82
+ const [dir, setDir] = useState<string | null>(null);
83
+
84
+ <Modal open={open} onClose={() => setOpen(false)}>
85
+ <Modal.Header>Choose a folder</Modal.Header>
86
+ <Modal.Body>
87
+ <FileBrowser
88
+ provider={provider}
89
+ select="directory"
90
+ value={dir}
91
+ onChange={(value) => setDir(value as string | null)}
92
+ defaultPath="/"
93
+ className="h-72"
94
+ />
95
+ </Modal.Body>
96
+ <Modal.Footer>
97
+ <Button disabled={!dir} onClick={() => pick(dir!)}>Select {dir ?? "…"}</Button>
98
+ </Modal.Footer>
99
+ </Modal>
100
+ ```
101
+
102
+ With `select="directory"`, files stay visible and browsable but only folders are selectable. Double-click a folder (or use the path input) to drill into it.
103
+
104
+ ## Multiple selection
105
+
106
+ ```tsx
107
+ const [paths, setPaths] = useState<string[]>([]);
108
+
109
+ <FileBrowser
110
+ provider={provider}
111
+ select="both"
112
+ multiple
113
+ value={paths}
114
+ onChange={(value) => setPaths(value as string[])}
115
+ />
116
+ ```
117
+
118
+ In `multiple` mode `value` is `string[]` and clicking (or Space) toggles membership. Disabled entries (`disabled: true`) can never be selected.
119
+
120
+ ## Props
121
+
122
+ ### FileBrowser (root)
123
+
124
+ | Prop | Type | Default | Description |
125
+ |------|------|---------|-------------|
126
+ | provider | `FileBrowserProvider` | - | Async data source — `loadChildren(path) => Promise<FileEntry[]>`, called lazily per folder |
127
+ | snapshot | `FileSnapshotNode[]` | - | JSON-friendly tree value, replaceable/patchable from outside; combinable with `provider` (hybrid) |
128
+ | select | `"file" \| "directory" \| "both"` | `"file"` | Which entry kinds are selectable |
129
+ | multiple | `boolean` | `false` | Multi-select; `value` becomes `string[]` |
130
+ | value | `string \| string[] \| null` | - | Controlled selection (paths) |
131
+ | defaultValue | `string \| string[] \| null` | - | Initial selection (uncontrolled) |
132
+ | onChange | `(value, entries: FileEntry[]) => void` | - | Selection callback (next value + matching known entries) |
133
+ | path | `string` | - | Controlled current directory |
134
+ | defaultPath | `string` | `"/"` | Initial current directory (uncontrolled) |
135
+ | onPathChange | `(path: string) => void` | - | Current-directory callback |
136
+ | expandedPaths | `string[]` | - | Controlled expanded folders |
137
+ | defaultExpandedPaths | `string[]` | - | Initially expanded folders (uncontrolled) |
138
+ | onExpandedChange | `(paths: string[]) => void` | - | Expanded-set callback |
139
+ | sort | `FileSort` | - | Controlled sort order |
140
+ | defaultSort | `FileSort` | `{ by: "name", direction: "asc" }` | Initial sort (uncontrolled) |
141
+ | onSortChange | `(sort: FileSort) => void` | - | Sort callback |
142
+ | filter | `string` | - | Controlled name filter (client-side, over loaded nodes) |
143
+ | defaultFilter | `string` | `""` | Initial filter (uncontrolled) |
144
+ | onFilterChange | `(filter: string) => void` | - | Filter callback |
145
+ | onError | `(path: string, error: unknown) => void` | - | Provider load failure callback |
146
+ | indentSize | `number` | `16` | Indent per nesting level in px |
147
+ | showIcons | `boolean` | `true` | Show file/folder icons |
148
+ | className | `string` | - | Extra classes on the outer shell |
149
+ | children | `ReactNode` | PathBar + Toolbar + Tree | Custom compound layout |
150
+
151
+ ### FileEntry
152
+
153
+ | Field | Type | Description |
154
+ |-------|------|-------------|
155
+ | path | `string` | Stable identity — POSIX-style path (required) |
156
+ | name | `string` | Display name (required) |
157
+ | kind | `"file" \| "dir"` | Entry kind (required) |
158
+ | size | `number` | Bytes; shown on file rows and used by size sorting |
159
+ | mtime | `string` | ISO 8601 timestamp; used by mtime sorting |
160
+ | hasChildren | `boolean` | Dirs: `false` = known-empty (no chevron), `undefined` = unknown (loadable with a provider), `true` = has children |
161
+ | disabled | `boolean` | Dimmed; cannot be selected, expanded, or navigated into |
162
+
163
+ ### FileSnapshotNode
164
+
165
+ `FileEntry` plus `children?: FileSnapshotNode[]` — `[]` = known-empty, `undefined` = unknown depth.
166
+
167
+ ### FileSort
168
+
169
+ `{ by: "name" | "size" | "mtime"; direction: "asc" | "desc" }`. Directories always sort before files; ties break by name.
170
+
171
+ ## Path bar
172
+
173
+ The breadcrumb trail navigates on click. The pencil button (or double-clicking the trail) switches to an editable path input: type a POSIX-style path and press **Enter** to navigate — in provider mode the target directory loads on arrival. **Escape** or blur cancels. Input is normalized (`\` → `/`, collapsed slashes, `.`/`..` resolved). Navigating clears the name filter.
174
+
175
+ ## Keyboard navigation
176
+
177
+ The tree uses ARIA `tree`/`treeitem`/`group` semantics with a roving tabindex — one Tab stop for the whole tree.
178
+
179
+ | Key | Action |
180
+ |-----|--------|
181
+ | `↓` / `↑` | Move focus through visible rows |
182
+ | `→` | Expand a collapsed folder (lazy-loads it); on an expanded folder, move to its first child |
183
+ | `←` | Collapse an expanded folder; otherwise move focus to the parent row |
184
+ | `Home` / `End` | First / last visible row |
185
+ | `Enter` | Select (if selectable) and toggle a folder; on a non-selectable, non-expandable folder, navigate into it |
186
+ | `Space` | Toggle selection |
187
+
188
+ ## Sorting and filtering
189
+
190
+ The toolbar sorts by **Name**, **Size**, or **Modified** (click the active field to flip direction — directories always list first) and filters by name. The filter is client-side over *loaded* nodes only: it never triggers loads, and a folder stays visible while it matches or any loaded descendant matches.
191
+
192
+ ## Custom layout
193
+
194
+ The default layout is `PathBar + Toolbar + Tree`. Pass children to rearrange or omit chrome:
195
+
196
+ ```tsx
197
+ <FileBrowser provider={provider} select="file" className="h-80">
198
+ <FileBrowser.Toolbar filterPlaceholder="Search files" />
199
+ <FileBrowser.Tree ariaLabel="Project files" />
200
+ <FileBrowser.PathBar editable={false} />
201
+ </FileBrowser>
202
+ ```
203
+
204
+ `useFileBrowser()` exposes the full context (entries, statuses, selection, navigation, sort/filter) for fully custom subcomponents.
205
+
206
+ ## Agent bridge (Human+)
207
+
208
+ Every row carries `data-path` — paths are the stable handles agents target, never indexes. The controlled props (`value`, `path`, `expandedPaths`) plus the provider/snapshot contract are the full agent surface.
209
+
210
+ A `registerFilesBridge(server, { adapter })` ships later in `@particle-academy/agent-integrations`, exposing MCP tools over this surface — `files_list(path)`, `files_expand(path)` / `files_collapse(path)`, `files_select(paths)`, `files_navigate(path)`, `files_request_snapshot(path, depth)` — with each mutation emitting an `AgentActivity` event so presence, undo, and coaching layers compose for free.
211
+
212
+ ## SSR
213
+
214
+ SSR-safe: no browser APIs outside event handlers, and rendering is deterministic (name sorting is pinned to a fixed locale so server and client order identically). In provider mode the server renders the loading state; hydration kicks off the first load.
215
+
216
+ ## Data Attributes
217
+
218
+ | Attribute | Element |
219
+ |-----------|---------|
220
+ | `data-react-fancy-file-browser` | Root shell |
221
+ | `data-react-fancy-file-browser-path` | Path bar |
222
+ | `data-react-fancy-file-browser-path-segment` | Breadcrumb segment |
223
+ | `data-react-fancy-file-browser-path-input` | Editable path input |
224
+ | `data-react-fancy-file-browser-toolbar` | Toolbar |
225
+ | `data-react-fancy-file-browser-filter` | Name filter box |
226
+ | `data-react-fancy-file-browser-sort` | Sort control group (buttons carry `data-sort-field`) |
227
+ | `data-react-fancy-file-browser-tree` | Tree pane (`role="tree"`) |
228
+ | `data-react-fancy-file-browser-node` | Tree item (also carries `data-path` + `data-kind`) |
229
+ | `data-react-fancy-file-browser-row` | Visual row inside a tree item |
230
+ | `data-react-fancy-file-browser-node-children` | Children group of an expanded folder |
231
+ | `data-react-fancy-file-browser-status` | Loading / error / empty placeholder rows |
package/docs/utilities.md CHANGED
@@ -55,12 +55,13 @@ type Color =
55
55
  type Variant = "solid" | "outline" | "ghost" | "soft";
56
56
  ```
57
57
 
58
- ### ActionColor
58
+ ### ButtonColor
59
+
60
+ Standalone color accepted by `Button` and friends — an alias of `Color` (the
61
+ full palette above). `ActionColor` remains as a deprecated alias.
59
62
 
60
63
  ```ts
61
- type ActionColor =
62
- | "blue" | "emerald" | "amber" | "red" | "violet"
63
- | "indigo" | "sky" | "rose" | "orange" | "zinc";
64
+ type ButtonColor = Color;
64
65
  ```
65
66
 
66
67
  ### Placement
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/react-fancy",
3
- "version": "4.12.0",
3
+ "version": "4.13.0",
4
4
  "description": "React UI component library for Human+ UX — controlled, agent-bridgeable primitives",
5
5
  "repository": {
6
6
  "type": "git",