@proveanything/smartlinks 1.13.3 → 1.13.4

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.
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.13.3 | Generated: 2026-05-08T10:30:25.776Z
3
+ Version: 1.13.4 | Generated: 2026-05-09T09:51:34.841Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
@@ -1,510 +1,104 @@
1
- # SmartLinks UI Utils (`@proveanything/smartlinks-utils-ui`)
1
+ # UI Utils `@proveanything/smartlinks-utils-ui`
2
2
 
3
- > Companion React component library for the SmartLinks SDK. Ships the heavy,
4
- > opinionated admin UI pieces that almost every SmartLinks microapp ends up
5
- > needingbuilt once, theme-able, tree-shakeable, and wired straight into
6
- > the SmartLinks SDK.
7
- >
8
- > Package: `@proveanything/smartlinks-utils-ui`
9
- > Tracks: `@proveanything/smartlinks ≥ 1.9` (some hooks require ≥ 1.10)
3
+ > **This page is a pointer.** Detailed, up-to-date docs for every component
4
+ > live in the UI Utils package itself. This SDK doc only summarises *what's
5
+ > in the box* and *when to reach for it* so we don't have to update the
6
+ > SDK every time a UI Utils prop changes.
10
7
 
11
- ---
12
-
13
- ## What is this module for?
14
-
15
- `@proveanything/smartlinks-utils-ui` sits on top of `@proveanything/smartlinks`.
16
- The core SDK handles data — records, configurations, interactions. This module
17
- handles **UI** — the shared React components, hooks, and admin shells that
18
- translate SDK data into consistent admin interfaces.
19
-
20
- **When do you need it?**
21
-
22
- - You are building an admin UI for a records-based microapp (see [app-records-pattern.md](app-records-pattern.md))
23
- - You need a media asset picker, icon picker, or font picker in an admin panel
24
- - You need a recursive rule/conditions editor for targeting or audience logic
25
- - You want the standard inheritance/override editor for scoped records
26
- - You need the `useResolvedRecord` / `useCollectedRecords` hooks on the public widget side
27
-
28
- You do **not** need it for apps that only use `appConfiguration`, basic widgets
29
- without scoped data, or executor bundles.
30
-
31
- > **Admin components are admin-only**: `RecordsAdminShell`, `AssetPicker` upload,
32
- > `FacetRuleEditor`, etc. call the SDK with `admin: true`. Do not render them in
33
- > public-facing views. The public-side hooks (`useResolvedRecord`,
34
- > `useCollectedRecords`) are safe in widgets.
35
-
36
- ---
8
+ - **NPM:** [`@proveanything/smartlinks-utils-ui`](https://www.npmjs.com/package/@proveanything/smartlinks-utils-ui)
9
+ - **Source & docs:** [`smartlinks-ui` repo](https://github.com/proveanything/smartlinks-ui)
10
+ - **Per-component reference:** [`packages/smartlinks-ui/docs/`](https://github.com/proveanything/smartlinks-ui/tree/main/packages/smartlinks-ui/docs)
11
+ - **Tracks:** `@proveanything/smartlinks ≥ 1.13` (peer dep)
37
12
 
38
13
  ## Install
39
14
 
40
15
  ```bash
41
16
  npm install @proveanything/smartlinks-utils-ui
42
- ```
43
-
44
- Peer dependencies (you already have these in a SmartLinks app):
45
-
46
- ```bash
17
+ # Peer deps you probably already have:
47
18
  npm install react react-dom @proveanything/smartlinks @tanstack/react-query
48
19
  ```
49
20
 
50
- `@tanstack/react-query` is required by every hook and by `RecordsAdminShell`
51
- (caching, pagination, optimistic save). Wrap your app in a
52
- `<QueryClientProvider>` somewhere up the tree.
53
-
54
- Import the compiled styles **once** in your app entry (e.g. `main.tsx`):
21
+ Import the styles **once** in your app entry (typically `src/admin-main.tsx`):
55
22
 
56
- ```tsx
23
+ ```ts
57
24
  import '@proveanything/smartlinks-utils-ui/styles.css';
58
25
  ```
59
26
 
60
27
  Components inherit your shadcn-compatible CSS variables (`--primary`,
61
- `--background`, `--border`, …) so they pick up your theme automatically.
28
+ `--background`, `--border`, …) no extra theming required.
62
29
 
63
- ---
30
+ > **Admin-only.** Every component in this package calls the SDK with
31
+ > `admin: true` somewhere (save, upload, etc.). Never import it from a
32
+ > public widget or from `MobileAdminContainer`.
64
33
 
65
34
  ## What's in the box
66
35
 
67
- | Module | What it is | When to reach for it |
68
- |--------|------------|----------------------|
69
- | [Records Admin Shell](#records-admin-shell) | Full admin UI for `app.records` with scope inheritance, cardinality, and rule editing | Per-product / per-variant / per-batch / per-facet config tools |
70
- | [FacetRuleEditor](#facet-rule-editor) | Standalone facet-rule builder with live preview | When you need rule editing outside the admin shell |
71
- | [Asset Picker](#asset-picker) | Browse / upload / paste / URL-import images and files | Any time the admin needs to pick or upload media |
72
- | [Icon Picker](#icon-picker) | Searchable Font Awesome 7 Pro picker | Configurable buttons, badges, menus, tiles |
73
- | [Font Picker](#font-picker) | Google Fonts + custom uploaded fonts | Theme editors, brand customisation panels |
74
- | [Conditions Editor](#conditions-editor) | Recursive AND/OR rule builder for 12 condition types | Targeting, gating, audience rules, segmentation |
36
+ | Module | Subpath import | Use it for |
37
+ |---|---|---|
38
+ | **RecordsAdminShell** | `/records-admin` | Full admin UI for the `app.records` pattern. Top-level scopes: **Global** (`'collection'`), **Rule** (`'rule'`, AND-of-OR over facets), and **Product** with automatic variant & batch drill-down where the collection enables them. Browser pane, editor pane with sticky save/discard/delete, optimistic save, deep-linking, lifecycle hooks, conflict handling. Decide [cardinality](https://github.com/proveanything/smartlinks-ui/blob/main/packages/smartlinks-ui/docs/records-admin-shell.md#-choosing-cardinality-read-this-first) up front: `'singleton'` (one winning record per scope — warranty, nutrition) vs `'list'` (many records per scope auction items, FAQs, gallery). |
39
+ | **Records hooks** | `/records-admin` | `useResolvedRecord`, `useMergedRecord`, `useCollectedRecords`, `useResolveAllRecords`, `useRulePreview`, for the **public widget** side. |
40
+ | **AdminPageHeader** | (root) | Standardised title / subtitle / icon / help / actions header. **Required** for any admin app that doesn't mount `RecordsAdminShell` (which embeds it). No bespoke admin chrome. |
41
+ | **AssetPicker** | `/asset-picker` | Pick / upload / paste / URL-import / AI-generate / stock-search / crop media assets. MIME filtering, scope-aware, tag editor. |
42
+ | **IconPicker** | `/icon-picker` | Searchable Font Awesome 7 Pro picker (requires the FA kit script on the host page). |
43
+ | **FontPicker** | `/font-picker` | Google Fonts catalogue + custom uploaded font families with live previews. |
44
+ | **ConditionsEditor** | `/conditions-editor` | Recursive AND/OR rule builder (12 condition types, facet-aware). |
45
+ | **FacetRuleEditor** | `/facet-rule-editor` | Author server-side facet rules (AND-of-OR) for record targeting. |
46
+ | **LinkPicker** | `/link-picker` | Universal navigation picker: external URL, an installed app, or a deep link inside an app. Stores a `LinkTarget` discriminated union — never a resolved URL. Ships with `resolveLink` and `useLinkTargets`. |
47
+ | **Hints** | (root) | `useHintsPreference`, `useIntroState`, `HintsPreferenceToggle` — global "show/hide intro hints" preference shared across admin apps. |
48
+
49
+ Each module has a per-subpath export so bundlers tree-shake the rest:
75
50
 
76
- ---
51
+ ```ts
52
+ import { RecordsAdminShell } from '@proveanything/smartlinks-utils-ui/records-admin';
53
+ import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
54
+ ```
77
55
 
78
- ## Records Admin Shell
56
+ ## Minimal examples
79
57
 
80
- The primary export. A complete admin UI for managing `app.records` — typed
81
- JSON blobs attached to facets, products, variants, batches, the collection
82
- root, or matched via facet rules — with scope inheritance built in. You
83
- provide the form for one record; the shell handles everything else.
58
+ ### Records admin (most apps need this)
84
59
 
85
60
  ```tsx
86
- import {
87
- RecordsAdminShell,
88
- InheritanceMarker,
89
- type EditorContext,
90
- } from '@proveanything/smartlinks-utils-ui/records-admin';
61
+ import { RecordsAdminShell } from '@proveanything/smartlinks-utils-ui/records-admin';
91
62
  import * as SL from '@proveanything/smartlinks';
92
63
 
93
- <RecordsAdminShell<NutritionData>
64
+ <RecordsAdminShell
94
65
  SL={SL}
95
66
  collectionId={collectionId}
96
67
  appId={appId}
97
68
  recordType="nutrition"
98
69
  label="Nutrition info"
99
- scopes={['facet', 'product', 'variant', 'batch']}
100
- contextScope={{ productId, variantId, batchId }} // from iframe URL optional
70
+ scopes={['collection', 'rule', 'product']} // canonical top-level tabs
71
+ // items={{ cardinality: 'list' }} // opt in for multi-item apps
101
72
  defaultData={() => ({})}
102
- csvSchema={{ columns: [/* ... */] }} // optional — omit to disable CSV
103
73
  renderEditor={(ctx) => <NutritionForm ctx={ctx} />}
104
- renderPreview={({ resolved }) => (
105
- <pre>{JSON.stringify(resolved, null, 2)}</pre>
106
- )}
107
74
  />
108
75
  ```
109
76
 
110
- ### Valid `ScopeKind` values
111
-
112
- ```ts
113
- type ScopeKind = 'collection' | 'product' | 'facet' | 'variant' | 'batch' | 'rule';
114
- ```
115
-
116
- - `'collection'` — terminal default (one record for the whole brand)
117
- - `'facet'` — anchored to a facet value (e.g. `bagel-type=white`)
118
- - `'product'` / `'variant'` / `'batch'` — anchored to that node
119
- - `'rule'` — synthetic UI scope: records targeted via a `facetRule`
120
- (AND-of-OR over facets) rather than pinned to a node. Their refs start
121
- with `rule:`, and the shell renders the `<FacetRuleEditor>` inline when the
122
- user opens one of these records.
123
-
124
- ### What it handles
125
-
126
- - **Browser pane** with scope tabs (Facet / Product / Variant / Batch / Rule), search, and status filter pills
127
- - **Editor pane** with sticky save / discard / delete footer, optimistic save, and an unsaved-drafts tray
128
- - **Per-field `<InheritanceMarker>`** showing whether a value is the record's own or inherited from a parent scope, with one-click revert-to-inherited
129
- - **Inheritance resolver** walks `proof → batch → variant → product → rule → facet → collection` server-side via SDK 1.10 `match()`
130
- - **Collection-aware tabs**: calls `collection.get` and hides Variants / Batches tabs unless `collection.variants` / `collection.batches` are true — no flicker
131
- - **Server-side pagination** via `useInfiniteQuery` — handles thousands of products with a "Load more" button
132
- - **Context-aware**: pass `contextScope` from your iframe URL (`productId` / `variantId` / `batchId`) and the browser is constrained to that subtree with the right tab auto-selected
133
- - **Cardinality**: `cardinality="singleton"` (one record per scope) or `"collection"` (many — FAQs, recipes, SOPs). Collection mode adds an item-list view (`itemViews: ['table' | 'cards' | 'gallery']`) and Back / prev / next nav
134
- - **Multiple presentations** for the rail (`presentations: ['list' | 'compact']`) and right pane
135
- - **CSV import / export** (optional — provide `csvSchema` to enable); failed rows come back as an annotated CSV
136
- - **Bulk actions menu** (apply-to-many, copy-from, clear) via `bulkUpsert` / `bulkDelete`
137
- - **Clipboard** — copy a record's value, paste onto another scope
138
- - **Telemetry hook** (`onTelemetry`) emits typed events for save, delete, scope change, CSV import/export, bulk apply
139
- - **i18n strings** fully overridable via the `i18n` prop
140
-
141
- > Requires a `<QueryClientProvider>` from `@tanstack/react-query` somewhere up the tree.
142
-
143
- ### Lower-level pieces (advanced use)
77
+ ### Public widget — read the resolved value
144
78
 
145
79
  ```tsx
146
- import {
147
- // Hooks
148
- useRecordList, useRecordEditor, useResolvedRecord, useCollectedRecords,
149
- useResolveAllRecords, useRulePreview, useScopeProbe,
150
- // Data helpers
151
- parseRef, buildRef, resolutionChain,
152
- listRecords, getRecordById, createRecord, upsertRecord,
153
- removeRecord, restoreRecord, matchRecords,
154
- bulkUpsert, bulkDelete,
155
- exportCsv, importCsv, downloadBlob,
156
- // UI pieces
157
- RecordBrowser, RecordEditor, ScopeBreadcrumb,
158
- InheritanceMarker, ResolvedPreview, BulkActionsMenu,
159
- // Drafts / unsaved state
160
- DirtyDraftProvider, useDirtyDrafts, useUnsavedGuard,
161
- } from '@proveanything/smartlinks-utils-ui/records-admin';
162
- ```
163
-
164
- Note the names: it's `getRecordById` (not `getRecordByRef`) and `removeRecord`
165
- (not `deleteRecord`). Records are addressed by UUID `id` internally; refs
166
- (`product:abc123`) are only used for display / breadcrumb / URL purposes.
167
-
168
- ### `useResolvedRecord` hook
169
-
170
- Use on the **public widget side** when the app shows one answer for the
171
- current product (singleton cardinality). Walks the inheritance chain
172
- server-side and returns the first match.
173
-
174
- ```ts
175
80
  import { useResolvedRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
176
81
 
177
- const { data, source, sourceRef, recordId, facetRule, isLoading, error } =
178
- useResolvedRecord<NutritionData>({
179
- SL,
180
- appId,
181
- recordType: 'nutrition',
182
- collectionId,
183
- productId,
184
- variantId, // optional
185
- batchId, // optional
186
- proofId, // optional
187
- // recordId, // optional — direct UUID lookup, bypasses inheritance
188
- });
189
- // source: 'self' | 'proof' | 'batch' | 'variant' | 'product' | 'facet' | 'universal' | 'empty'
190
- ```
191
-
192
- - `source` — which scope the winning record came from. `'self'` is returned
193
- when you passed an explicit `recordId`; `'empty'` when nothing matched.
194
- - `sourceRef` — the ref of the matched record (e.g. `product:abc123`).
195
- - `recordId` — the UUID of the matched record.
196
- - `facetRule` — present when the match came from a rule-targeted record.
197
-
198
- Pass `recordId` directly when you already know the UUID (deep links, rule
199
- records) — the hook will skip the inheritance walk entirely.
200
-
201
- ### `useCollectedRecords` hook
202
-
203
- Use on the **public widget side** when the app shows many answers
204
- (collection cardinality — FAQs, recipes, care tips). Returns every matching
205
- record across the chain, most-specific first.
206
-
207
- ```ts
208
- import { useCollectedRecords } from '@proveanything/smartlinks-utils-ui/records-admin';
209
-
210
- const { items, isLoading, error } = useCollectedRecords<FaqEntry>({
211
- SL, appId, collectionId,
212
- recordType: 'faq',
213
- productId,
214
- // sort: { kind: 'field', field: 'order', direction: 'asc' },
215
- });
216
- // items: CollectedRecord<FaqEntry>[] — each has { data, scope, ref, depth }
217
- ```
218
-
219
- `depth: 0` is the most-specific match. Default sort is by specificity
220
- descending; pass `{ kind: 'field', field, direction }` to sort by a payload
221
- field instead (with specificity as a stable tiebreak).
222
-
223
- ### `useResolveAllRecords` hook
224
-
225
- When you need every record of every declared type that applies to a context —
226
- rare, mostly used in executors and SEO surfaces.
227
-
228
- ```ts
229
- import { useResolveAllRecords } from '@proveanything/smartlinks-utils-ui/records-admin';
230
-
231
- const { entries, isLoading } = useResolveAllRecords({
232
- SL, collectionId, appId,
233
- context: { productId, facets: { brand: 'acme' } },
82
+ const { data, source } = useResolvedRecord({
83
+ SL, appId, recordType: 'nutrition',
84
+ collectionId, productId, variantId, batchId,
234
85
  });
86
+ // source: 'proof' | 'batch' | 'variant' | 'product' | 'rule' | 'collection' | null
235
87
  ```
236
88
 
237
- ### `useRulePreview` hook
238
-
239
- Wire into `<FacetRuleEditor>` (or any custom rule UI) to show a live
240
- "matches N products" count as the rule is edited. Debounced — safe to call
241
- on every keystroke.
242
-
243
- ```ts
244
- import { useRulePreview } from '@proveanything/smartlinks-utils-ui/records-admin';
245
-
246
- const preview = useRulePreview({
247
- SL, collectionId, appId,
248
- rule, // FacetRule | null
249
- // limit: 20,
250
- // debounceMs: 350,
251
- });
252
- // preview: {
253
- // totalMatches: number | null;
254
- // sampleProductIds: string[];
255
- // isLoading: boolean;
256
- // isStale: boolean;
257
- // error: Error | null;
258
- // }
259
- ```
260
-
261
- Pass `preview` straight into `<FacetRuleEditor preview={preview} />`.
262
-
263
- ### `useScopeProbe` hook
89
+ For multi-item (`cardinality: 'list'`) apps, use `useResolveAllRecords` /
90
+ `app.records.resolveAll()` instead — it returns every applicable record, not
91
+ just the winner.
264
92
 
265
- Reports whether a collection has variants/batches enabled, so the shell
266
- (or your own UI) can hide the corresponding tabs without flicker.
93
+ For everything else (props, slots, cardinality decisions, deep-link adapters,
94
+ lifecycle hooks, conflict handling, design tokens) **read the package docs**.
267
95
 
268
- ```ts
269
- import { useScopeProbe } from '@proveanything/smartlinks-utils-ui/records-admin';
270
-
271
- const { hasVariants, hasBatches, isLoading } = useScopeProbe({
272
- SL, collectionId,
273
- });
274
- ```
275
-
276
- It's a thin wrapper around `SL.collection.get(collectionId).variants /.batches`
277
- (SDK ≥ 1.9). It does **not** report per-scope record status — that's handled
278
- internally by the shell's status pills.
279
-
280
- ### `parseRef` / `buildRef` utilities
281
-
282
- ```ts
283
- import { parseRef, buildRef } from '@proveanything/smartlinks-utils-ui/records-admin';
284
-
285
- const parsed = parseRef('variant:prod_abc:var_500ml');
286
- // → { kind: 'variant', productId: 'prod_abc', variantId: 'var_500ml', raw: '...' }
287
-
288
- const ref = buildRef({ kind: 'product', productId: 'prod_abc', raw: '' });
289
- // → 'product:prod_abc'
290
- ```
291
-
292
- Refs are for display, breadcrumbs, and URLs. Records are addressed by UUID
293
- `id` everywhere internally.
294
-
295
- ---
296
-
297
- ## Facet Rule Editor
298
-
299
- A standalone facet-rule builder. The `<RecordsAdminShell>` embeds this
300
- automatically when `'rule'` is in `scopes` and the user opens a rule-targeted
301
- record — reach for it directly only when you need rule editing elsewhere
302
- (e.g. a settings page, a conditions sidebar).
303
-
304
- ```tsx
305
- import { FacetRuleEditor } from '@proveanything/smartlinks-utils-ui/facet-rule-editor';
306
- import { useRulePreview } from '@proveanything/smartlinks-utils-ui/records-admin';
307
-
308
- const preview = useRulePreview({ SL, collectionId, appId, rule });
309
-
310
- <FacetRuleEditor
311
- value={rule}
312
- onChange={setRule}
313
- collectionId={collectionId} // lazy-fetches facet definitions via SL.facets.publicList
314
- preview={preview} // optional — wire from useRulePreview
315
- onClear={() => setRule(null)} // optional — renders a "Remove rule" affordance
316
- />
317
- ```
318
-
319
- Props:
320
-
321
- - `value: FacetRule | null` / `onChange: (next: FacetRule | null) => void` — controlled
322
- - `facets?: FacetOption[]` — supply directly, **or** pass `collectionId` and the editor lazy-fetches via `SL.facets.publicList`
323
- - `getFacets?: (collectionId: string) => Promise<FacetOption[]>` — override the lazy-fetcher
324
- - `preview?` — `{ totalMatches, sampleProductIds?, isLoading?, isStale?, error? }` (matches the `useRulePreview` return shape)
325
- - `readOnly?`, `onClear?`, `title?`, `description?`, `className?`
326
-
327
- Free-text facet entry is **not** supported — admins must pick from defined facets.
328
-
329
- See [app-records-pattern.md §4](app-records-pattern.md#4-admin-side----recordsadminshell-the-only-thing-you-should-be-writing) for the standalone usage example.
330
-
331
- ---
332
-
333
- ## Asset Picker
334
-
335
- ```tsx
336
- import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
337
-
338
- <AssetPicker
339
- scope={{ type: 'collection', collectionId: 'abc123' }}
340
- mode="dialog" // or "inline"
341
- allowUpload
342
- accept={['image/*']} // MIME filtering
343
- onSelect={(asset) => setHeroUrl(asset.url)}
344
- trigger={<Button>Choose image</Button>}
345
- />
346
- ```
347
-
348
- Scope shape:
349
-
350
- ```ts
351
- type AssetScope =
352
- | { type: 'collection'; collectionId: string }
353
- | { type: 'product'; collectionId: string; productId: string }
354
- | { type: 'proof'; collectionId: string; productId: string; proofId: string };
355
- ```
356
-
357
- **What it does:**
358
-
359
- - Browses assets at **collection**, **product**, or **proof** scope
360
- - Optional `productScope` prop adds a second tab so users can pick from product-level assets while editing at collection scope (or vice versa)
361
- - Optional `appId` prop stamps every upload with the owning app and adds "This app" / "All in collection" pill tabs with provenance badges on assets owned by other apps
362
- - Four ingest paths: file upload, URL import, clipboard paste (with rename preview), and selection from existing assets
363
- - Inline mode for embedding in a panel; dialog mode for modal pickers
364
- - Double-click a tile to confirm instantly
365
-
366
- ---
367
-
368
- ## Icon Picker
369
-
370
- ```tsx
371
- import { IconPicker } from '@proveanything/smartlinks-utils-ui/icon-picker';
372
-
373
- <IconPicker
374
- mode="dialog"
375
- value="fa-solid fa-heart"
376
- onSelect={(icon) => setIcon(icon.name)}
377
- trigger={<Button>Pick icon</Button>}
378
- />
379
- ```
380
-
381
- `onSelect` receives:
382
-
383
- ```ts
384
- interface IconSelection {
385
- name: string; // full CSS class, e.g. 'fa-solid fa-heart'
386
- family: 'classic' | 'duotone' | 'brands';
387
- style: 'solid' | 'regular' | 'light' | null; // null for brands
388
- label?: string;
389
- }
390
- ```
391
-
392
- **What it does:**
393
-
394
- - Searches **Font Awesome 7 Pro** (uses the shared kit)
395
- - Family hierarchy: Classic (Solid / Regular / Light), Duotone, and Brands
396
- - Search-first with a background catalogue crawler — first results appear instantly, the full index fills in behind
397
- - Auto-switches families intelligently (e.g. brand searches surface brand icons even when "Classic" is selected)
398
-
399
- > Requires the FA kit script to be present on the host page.
400
-
401
- ---
402
-
403
- ## Font Picker
404
-
405
- ```tsx
406
- import { FontPicker } from '@proveanything/smartlinks-utils-ui/font-picker';
407
-
408
- <FontPicker
409
- mode="dialog"
410
- value="Inter"
411
- showPreview
412
- onSelect={(font) => {
413
- console.log(font.family); // "Inter"
414
- console.log(font.cssFontFamily); // "'Inter', ui-sans-serif, system-ui, sans-serif"
415
- console.log(font.loadSnippet); // <link href="..." rel="stylesheet">
416
- }}
417
- />
418
-
419
- {/* With custom fonts uploaded into the collection */}
420
- <FontPicker
421
- mode="dialog"
422
- showCustomFonts
423
- scope={{ collectionId: 'abc123' }}
424
- onSelect={(font) => /* ... */}
425
- />
426
- ```
427
-
428
- **What it does:**
429
-
430
- - Full **Google Fonts** catalogue plus any **custom fonts** uploaded for the brand (stored via `appConfiguration` under `customFonts`)
431
- - Upload zone auto-detects weight/style from the filename (e.g. `MyFont-BoldItalic.woff2`)
432
- - Returns a `FontSelection` with `family`, `cssFontFamily`, and a ready-to-inject `loadSnippet` (`<link>` for Google fonts, `@font-face` CSS for custom uploads)
433
- - Lazy-loads previews via `IntersectionObserver`; includes a management UI for editing custom font definitions
434
-
435
- ---
436
-
437
- ## Conditions Editor
438
-
439
- ```tsx
440
- import { ConditionsEditor } from '@proveanything/smartlinks-utils-ui/conditions-editor';
441
-
442
- <ConditionsEditor
443
- value={rules}
444
- onChange={setRules}
445
- collectionId={collectionId} // auto-loads facet definitions
446
- versions={[{ title: 'Default', value: '' }]}
447
- tags={['featured', 'new']}
448
- />
449
- ```
450
-
451
- **What it does:**
452
-
453
- - Recursive AND / OR group builder — nest conditions to any depth
454
- - **12 condition types:** Version, Country, Value, User, Date, Device, Tag, Facet, Geofence, Product, Item Status, Condition Reference
455
- - **Facet condition** auto-fetches definitions from `facets.publicList(collectionId, { includeValues: true })` when only `collectionId` is passed (SDK ≥ 1.9.20)
456
- - **Country picker** is a searchable multi-select with removable ISO 3166-1 chips and a "Use regions" toggle
457
- - Renders correctly inside iframe contexts — avoids `overflow-hidden` so dropdowns escape their cards
458
-
459
- > `ConditionsEditor` and `FacetRuleEditor` solve different problems.
460
- > `FacetRuleEditor` is a focused AND-of-OR rule over facets only, used to
461
- > target which records apply to which products. `ConditionsEditor` is the
462
- > full recursive logical builder over 12 condition types, used for runtime
463
- > gating, audience segmentation, and version targeting.
464
-
465
- ---
466
-
467
- ## Tree shaking
468
-
469
- Each component has its own subpath export:
470
-
471
- ```tsx
472
- // Bundles only the Asset Picker
473
- import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
474
-
475
- // Barrel import — bundler tree-shakes the rest
476
- import { AssetPicker } from '@proveanything/smartlinks-utils-ui';
477
- ```
478
-
479
- If you use subpath imports, import `styles.css` separately — subpaths do not
480
- pull it in automatically.
481
-
482
- Available subpaths:
483
-
484
- - `/records-admin`
485
- - `/facet-rule-editor`
486
- - `/asset-picker`
487
- - `/icon-picker`
488
- - `/font-picker`
489
- - `/conditions-editor`
490
- - `/styles.css`
491
-
492
- ---
493
-
494
- ## Relationship to the core SDK
495
-
496
- ```text
497
- @proveanything/smartlinks ← data layer (records, config, interactions, …)
498
-
499
- @proveanything/smartlinks-utils-ui ← UI layer (components, hooks, admin shells)
500
-
501
- your microapp ← domain logic and custom forms
502
- ```
96
+ ## Prerequisites
503
97
 
504
- ---
98
+ All components assume `SL.initializeApi()` has already run.
505
99
 
506
- ## Further reading
100
+ ## Where to go next
507
101
 
508
- - [app-records-pattern.md](app-records-pattern.md) — the data contract that `RecordsAdminShell` implements
509
- - [building-react-components.md](building-react-components.md) — dual-mode rendering rules that apply to all components
510
- - [app-manifest.md](app-manifest.md) the `records` manifest block that drives tab generation
102
+ - Component reference: <https://github.com/proveanything/smartlinks-ui/tree/main/packages/smartlinks-ui/docs>
103
+ - Records pattern (concepts, scope inheritance, resolution order): [`records-admin-pattern.md`](./records-admin-pattern.md)
104
+ - Public consumption (how widgets/executors fetch the right records at runtime): the package's [`public-consumption.md`](https://github.com/proveanything/smartlinks-ui/blob/main/packages/smartlinks-ui/docs/public-consumption.md)
@@ -1,6 +1,6 @@
1
1
  # Smartlinks API Summary
2
2
 
3
- Version: 1.13.3 | Generated: 2026-05-08T10:30:25.776Z
3
+ Version: 1.13.4 | Generated: 2026-05-09T09:51:34.841Z
4
4
 
5
5
  This is a concise summary of all available API functions and types.
6
6
 
package/docs/ui-utils.md CHANGED
@@ -1,510 +1,104 @@
1
- # SmartLinks UI Utils (`@proveanything/smartlinks-utils-ui`)
1
+ # UI Utils `@proveanything/smartlinks-utils-ui`
2
2
 
3
- > Companion React component library for the SmartLinks SDK. Ships the heavy,
4
- > opinionated admin UI pieces that almost every SmartLinks microapp ends up
5
- > needingbuilt once, theme-able, tree-shakeable, and wired straight into
6
- > the SmartLinks SDK.
7
- >
8
- > Package: `@proveanything/smartlinks-utils-ui`
9
- > Tracks: `@proveanything/smartlinks ≥ 1.9` (some hooks require ≥ 1.10)
3
+ > **This page is a pointer.** Detailed, up-to-date docs for every component
4
+ > live in the UI Utils package itself. This SDK doc only summarises *what's
5
+ > in the box* and *when to reach for it* so we don't have to update the
6
+ > SDK every time a UI Utils prop changes.
10
7
 
11
- ---
12
-
13
- ## What is this module for?
14
-
15
- `@proveanything/smartlinks-utils-ui` sits on top of `@proveanything/smartlinks`.
16
- The core SDK handles data — records, configurations, interactions. This module
17
- handles **UI** — the shared React components, hooks, and admin shells that
18
- translate SDK data into consistent admin interfaces.
19
-
20
- **When do you need it?**
21
-
22
- - You are building an admin UI for a records-based microapp (see [app-records-pattern.md](app-records-pattern.md))
23
- - You need a media asset picker, icon picker, or font picker in an admin panel
24
- - You need a recursive rule/conditions editor for targeting or audience logic
25
- - You want the standard inheritance/override editor for scoped records
26
- - You need the `useResolvedRecord` / `useCollectedRecords` hooks on the public widget side
27
-
28
- You do **not** need it for apps that only use `appConfiguration`, basic widgets
29
- without scoped data, or executor bundles.
30
-
31
- > **Admin components are admin-only**: `RecordsAdminShell`, `AssetPicker` upload,
32
- > `FacetRuleEditor`, etc. call the SDK with `admin: true`. Do not render them in
33
- > public-facing views. The public-side hooks (`useResolvedRecord`,
34
- > `useCollectedRecords`) are safe in widgets.
35
-
36
- ---
8
+ - **NPM:** [`@proveanything/smartlinks-utils-ui`](https://www.npmjs.com/package/@proveanything/smartlinks-utils-ui)
9
+ - **Source & docs:** [`smartlinks-ui` repo](https://github.com/proveanything/smartlinks-ui)
10
+ - **Per-component reference:** [`packages/smartlinks-ui/docs/`](https://github.com/proveanything/smartlinks-ui/tree/main/packages/smartlinks-ui/docs)
11
+ - **Tracks:** `@proveanything/smartlinks ≥ 1.13` (peer dep)
37
12
 
38
13
  ## Install
39
14
 
40
15
  ```bash
41
16
  npm install @proveanything/smartlinks-utils-ui
42
- ```
43
-
44
- Peer dependencies (you already have these in a SmartLinks app):
45
-
46
- ```bash
17
+ # Peer deps you probably already have:
47
18
  npm install react react-dom @proveanything/smartlinks @tanstack/react-query
48
19
  ```
49
20
 
50
- `@tanstack/react-query` is required by every hook and by `RecordsAdminShell`
51
- (caching, pagination, optimistic save). Wrap your app in a
52
- `<QueryClientProvider>` somewhere up the tree.
53
-
54
- Import the compiled styles **once** in your app entry (e.g. `main.tsx`):
21
+ Import the styles **once** in your app entry (typically `src/admin-main.tsx`):
55
22
 
56
- ```tsx
23
+ ```ts
57
24
  import '@proveanything/smartlinks-utils-ui/styles.css';
58
25
  ```
59
26
 
60
27
  Components inherit your shadcn-compatible CSS variables (`--primary`,
61
- `--background`, `--border`, …) so they pick up your theme automatically.
28
+ `--background`, `--border`, …) no extra theming required.
62
29
 
63
- ---
30
+ > **Admin-only.** Every component in this package calls the SDK with
31
+ > `admin: true` somewhere (save, upload, etc.). Never import it from a
32
+ > public widget or from `MobileAdminContainer`.
64
33
 
65
34
  ## What's in the box
66
35
 
67
- | Module | What it is | When to reach for it |
68
- |--------|------------|----------------------|
69
- | [Records Admin Shell](#records-admin-shell) | Full admin UI for `app.records` with scope inheritance, cardinality, and rule editing | Per-product / per-variant / per-batch / per-facet config tools |
70
- | [FacetRuleEditor](#facet-rule-editor) | Standalone facet-rule builder with live preview | When you need rule editing outside the admin shell |
71
- | [Asset Picker](#asset-picker) | Browse / upload / paste / URL-import images and files | Any time the admin needs to pick or upload media |
72
- | [Icon Picker](#icon-picker) | Searchable Font Awesome 7 Pro picker | Configurable buttons, badges, menus, tiles |
73
- | [Font Picker](#font-picker) | Google Fonts + custom uploaded fonts | Theme editors, brand customisation panels |
74
- | [Conditions Editor](#conditions-editor) | Recursive AND/OR rule builder for 12 condition types | Targeting, gating, audience rules, segmentation |
36
+ | Module | Subpath import | Use it for |
37
+ |---|---|---|
38
+ | **RecordsAdminShell** | `/records-admin` | Full admin UI for the `app.records` pattern. Top-level scopes: **Global** (`'collection'`), **Rule** (`'rule'`, AND-of-OR over facets), and **Product** with automatic variant & batch drill-down where the collection enables them. Browser pane, editor pane with sticky save/discard/delete, optimistic save, deep-linking, lifecycle hooks, conflict handling. Decide [cardinality](https://github.com/proveanything/smartlinks-ui/blob/main/packages/smartlinks-ui/docs/records-admin-shell.md#-choosing-cardinality-read-this-first) up front: `'singleton'` (one winning record per scope — warranty, nutrition) vs `'list'` (many records per scope auction items, FAQs, gallery). |
39
+ | **Records hooks** | `/records-admin` | `useResolvedRecord`, `useMergedRecord`, `useCollectedRecords`, `useResolveAllRecords`, `useRulePreview`, for the **public widget** side. |
40
+ | **AdminPageHeader** | (root) | Standardised title / subtitle / icon / help / actions header. **Required** for any admin app that doesn't mount `RecordsAdminShell` (which embeds it). No bespoke admin chrome. |
41
+ | **AssetPicker** | `/asset-picker` | Pick / upload / paste / URL-import / AI-generate / stock-search / crop media assets. MIME filtering, scope-aware, tag editor. |
42
+ | **IconPicker** | `/icon-picker` | Searchable Font Awesome 7 Pro picker (requires the FA kit script on the host page). |
43
+ | **FontPicker** | `/font-picker` | Google Fonts catalogue + custom uploaded font families with live previews. |
44
+ | **ConditionsEditor** | `/conditions-editor` | Recursive AND/OR rule builder (12 condition types, facet-aware). |
45
+ | **FacetRuleEditor** | `/facet-rule-editor` | Author server-side facet rules (AND-of-OR) for record targeting. |
46
+ | **LinkPicker** | `/link-picker` | Universal navigation picker: external URL, an installed app, or a deep link inside an app. Stores a `LinkTarget` discriminated union — never a resolved URL. Ships with `resolveLink` and `useLinkTargets`. |
47
+ | **Hints** | (root) | `useHintsPreference`, `useIntroState`, `HintsPreferenceToggle` — global "show/hide intro hints" preference shared across admin apps. |
48
+
49
+ Each module has a per-subpath export so bundlers tree-shake the rest:
75
50
 
76
- ---
51
+ ```ts
52
+ import { RecordsAdminShell } from '@proveanything/smartlinks-utils-ui/records-admin';
53
+ import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
54
+ ```
77
55
 
78
- ## Records Admin Shell
56
+ ## Minimal examples
79
57
 
80
- The primary export. A complete admin UI for managing `app.records` — typed
81
- JSON blobs attached to facets, products, variants, batches, the collection
82
- root, or matched via facet rules — with scope inheritance built in. You
83
- provide the form for one record; the shell handles everything else.
58
+ ### Records admin (most apps need this)
84
59
 
85
60
  ```tsx
86
- import {
87
- RecordsAdminShell,
88
- InheritanceMarker,
89
- type EditorContext,
90
- } from '@proveanything/smartlinks-utils-ui/records-admin';
61
+ import { RecordsAdminShell } from '@proveanything/smartlinks-utils-ui/records-admin';
91
62
  import * as SL from '@proveanything/smartlinks';
92
63
 
93
- <RecordsAdminShell<NutritionData>
64
+ <RecordsAdminShell
94
65
  SL={SL}
95
66
  collectionId={collectionId}
96
67
  appId={appId}
97
68
  recordType="nutrition"
98
69
  label="Nutrition info"
99
- scopes={['facet', 'product', 'variant', 'batch']}
100
- contextScope={{ productId, variantId, batchId }} // from iframe URL optional
70
+ scopes={['collection', 'rule', 'product']} // canonical top-level tabs
71
+ // items={{ cardinality: 'list' }} // opt in for multi-item apps
101
72
  defaultData={() => ({})}
102
- csvSchema={{ columns: [/* ... */] }} // optional — omit to disable CSV
103
73
  renderEditor={(ctx) => <NutritionForm ctx={ctx} />}
104
- renderPreview={({ resolved }) => (
105
- <pre>{JSON.stringify(resolved, null, 2)}</pre>
106
- )}
107
74
  />
108
75
  ```
109
76
 
110
- ### Valid `ScopeKind` values
111
-
112
- ```ts
113
- type ScopeKind = 'collection' | 'product' | 'facet' | 'variant' | 'batch' | 'rule';
114
- ```
115
-
116
- - `'collection'` — terminal default (one record for the whole brand)
117
- - `'facet'` — anchored to a facet value (e.g. `bagel-type=white`)
118
- - `'product'` / `'variant'` / `'batch'` — anchored to that node
119
- - `'rule'` — synthetic UI scope: records targeted via a `facetRule`
120
- (AND-of-OR over facets) rather than pinned to a node. Their refs start
121
- with `rule:`, and the shell renders the `<FacetRuleEditor>` inline when the
122
- user opens one of these records.
123
-
124
- ### What it handles
125
-
126
- - **Browser pane** with scope tabs (Facet / Product / Variant / Batch / Rule), search, and status filter pills
127
- - **Editor pane** with sticky save / discard / delete footer, optimistic save, and an unsaved-drafts tray
128
- - **Per-field `<InheritanceMarker>`** showing whether a value is the record's own or inherited from a parent scope, with one-click revert-to-inherited
129
- - **Inheritance resolver** walks `proof → batch → variant → product → rule → facet → collection` server-side via SDK 1.10 `match()`
130
- - **Collection-aware tabs**: calls `collection.get` and hides Variants / Batches tabs unless `collection.variants` / `collection.batches` are true — no flicker
131
- - **Server-side pagination** via `useInfiniteQuery` — handles thousands of products with a "Load more" button
132
- - **Context-aware**: pass `contextScope` from your iframe URL (`productId` / `variantId` / `batchId`) and the browser is constrained to that subtree with the right tab auto-selected
133
- - **Cardinality**: `cardinality="singleton"` (one record per scope) or `"collection"` (many — FAQs, recipes, SOPs). Collection mode adds an item-list view (`itemViews: ['table' | 'cards' | 'gallery']`) and Back / prev / next nav
134
- - **Multiple presentations** for the rail (`presentations: ['list' | 'compact']`) and right pane
135
- - **CSV import / export** (optional — provide `csvSchema` to enable); failed rows come back as an annotated CSV
136
- - **Bulk actions menu** (apply-to-many, copy-from, clear) via `bulkUpsert` / `bulkDelete`
137
- - **Clipboard** — copy a record's value, paste onto another scope
138
- - **Telemetry hook** (`onTelemetry`) emits typed events for save, delete, scope change, CSV import/export, bulk apply
139
- - **i18n strings** fully overridable via the `i18n` prop
140
-
141
- > Requires a `<QueryClientProvider>` from `@tanstack/react-query` somewhere up the tree.
142
-
143
- ### Lower-level pieces (advanced use)
77
+ ### Public widget — read the resolved value
144
78
 
145
79
  ```tsx
146
- import {
147
- // Hooks
148
- useRecordList, useRecordEditor, useResolvedRecord, useCollectedRecords,
149
- useResolveAllRecords, useRulePreview, useScopeProbe,
150
- // Data helpers
151
- parseRef, buildRef, resolutionChain,
152
- listRecords, getRecordById, createRecord, upsertRecord,
153
- removeRecord, restoreRecord, matchRecords,
154
- bulkUpsert, bulkDelete,
155
- exportCsv, importCsv, downloadBlob,
156
- // UI pieces
157
- RecordBrowser, RecordEditor, ScopeBreadcrumb,
158
- InheritanceMarker, ResolvedPreview, BulkActionsMenu,
159
- // Drafts / unsaved state
160
- DirtyDraftProvider, useDirtyDrafts, useUnsavedGuard,
161
- } from '@proveanything/smartlinks-utils-ui/records-admin';
162
- ```
163
-
164
- Note the names: it's `getRecordById` (not `getRecordByRef`) and `removeRecord`
165
- (not `deleteRecord`). Records are addressed by UUID `id` internally; refs
166
- (`product:abc123`) are only used for display / breadcrumb / URL purposes.
167
-
168
- ### `useResolvedRecord` hook
169
-
170
- Use on the **public widget side** when the app shows one answer for the
171
- current product (singleton cardinality). Walks the inheritance chain
172
- server-side and returns the first match.
173
-
174
- ```ts
175
80
  import { useResolvedRecord } from '@proveanything/smartlinks-utils-ui/records-admin';
176
81
 
177
- const { data, source, sourceRef, recordId, facetRule, isLoading, error } =
178
- useResolvedRecord<NutritionData>({
179
- SL,
180
- appId,
181
- recordType: 'nutrition',
182
- collectionId,
183
- productId,
184
- variantId, // optional
185
- batchId, // optional
186
- proofId, // optional
187
- // recordId, // optional — direct UUID lookup, bypasses inheritance
188
- });
189
- // source: 'self' | 'proof' | 'batch' | 'variant' | 'product' | 'facet' | 'universal' | 'empty'
190
- ```
191
-
192
- - `source` — which scope the winning record came from. `'self'` is returned
193
- when you passed an explicit `recordId`; `'empty'` when nothing matched.
194
- - `sourceRef` — the ref of the matched record (e.g. `product:abc123`).
195
- - `recordId` — the UUID of the matched record.
196
- - `facetRule` — present when the match came from a rule-targeted record.
197
-
198
- Pass `recordId` directly when you already know the UUID (deep links, rule
199
- records) — the hook will skip the inheritance walk entirely.
200
-
201
- ### `useCollectedRecords` hook
202
-
203
- Use on the **public widget side** when the app shows many answers
204
- (collection cardinality — FAQs, recipes, care tips). Returns every matching
205
- record across the chain, most-specific first.
206
-
207
- ```ts
208
- import { useCollectedRecords } from '@proveanything/smartlinks-utils-ui/records-admin';
209
-
210
- const { items, isLoading, error } = useCollectedRecords<FaqEntry>({
211
- SL, appId, collectionId,
212
- recordType: 'faq',
213
- productId,
214
- // sort: { kind: 'field', field: 'order', direction: 'asc' },
215
- });
216
- // items: CollectedRecord<FaqEntry>[] — each has { data, scope, ref, depth }
217
- ```
218
-
219
- `depth: 0` is the most-specific match. Default sort is by specificity
220
- descending; pass `{ kind: 'field', field, direction }` to sort by a payload
221
- field instead (with specificity as a stable tiebreak).
222
-
223
- ### `useResolveAllRecords` hook
224
-
225
- When you need every record of every declared type that applies to a context —
226
- rare, mostly used in executors and SEO surfaces.
227
-
228
- ```ts
229
- import { useResolveAllRecords } from '@proveanything/smartlinks-utils-ui/records-admin';
230
-
231
- const { entries, isLoading } = useResolveAllRecords({
232
- SL, collectionId, appId,
233
- context: { productId, facets: { brand: 'acme' } },
82
+ const { data, source } = useResolvedRecord({
83
+ SL, appId, recordType: 'nutrition',
84
+ collectionId, productId, variantId, batchId,
234
85
  });
86
+ // source: 'proof' | 'batch' | 'variant' | 'product' | 'rule' | 'collection' | null
235
87
  ```
236
88
 
237
- ### `useRulePreview` hook
238
-
239
- Wire into `<FacetRuleEditor>` (or any custom rule UI) to show a live
240
- "matches N products" count as the rule is edited. Debounced — safe to call
241
- on every keystroke.
242
-
243
- ```ts
244
- import { useRulePreview } from '@proveanything/smartlinks-utils-ui/records-admin';
245
-
246
- const preview = useRulePreview({
247
- SL, collectionId, appId,
248
- rule, // FacetRule | null
249
- // limit: 20,
250
- // debounceMs: 350,
251
- });
252
- // preview: {
253
- // totalMatches: number | null;
254
- // sampleProductIds: string[];
255
- // isLoading: boolean;
256
- // isStale: boolean;
257
- // error: Error | null;
258
- // }
259
- ```
260
-
261
- Pass `preview` straight into `<FacetRuleEditor preview={preview} />`.
262
-
263
- ### `useScopeProbe` hook
89
+ For multi-item (`cardinality: 'list'`) apps, use `useResolveAllRecords` /
90
+ `app.records.resolveAll()` instead — it returns every applicable record, not
91
+ just the winner.
264
92
 
265
- Reports whether a collection has variants/batches enabled, so the shell
266
- (or your own UI) can hide the corresponding tabs without flicker.
93
+ For everything else (props, slots, cardinality decisions, deep-link adapters,
94
+ lifecycle hooks, conflict handling, design tokens) **read the package docs**.
267
95
 
268
- ```ts
269
- import { useScopeProbe } from '@proveanything/smartlinks-utils-ui/records-admin';
270
-
271
- const { hasVariants, hasBatches, isLoading } = useScopeProbe({
272
- SL, collectionId,
273
- });
274
- ```
275
-
276
- It's a thin wrapper around `SL.collection.get(collectionId).variants /.batches`
277
- (SDK ≥ 1.9). It does **not** report per-scope record status — that's handled
278
- internally by the shell's status pills.
279
-
280
- ### `parseRef` / `buildRef` utilities
281
-
282
- ```ts
283
- import { parseRef, buildRef } from '@proveanything/smartlinks-utils-ui/records-admin';
284
-
285
- const parsed = parseRef('variant:prod_abc:var_500ml');
286
- // → { kind: 'variant', productId: 'prod_abc', variantId: 'var_500ml', raw: '...' }
287
-
288
- const ref = buildRef({ kind: 'product', productId: 'prod_abc', raw: '' });
289
- // → 'product:prod_abc'
290
- ```
291
-
292
- Refs are for display, breadcrumbs, and URLs. Records are addressed by UUID
293
- `id` everywhere internally.
294
-
295
- ---
296
-
297
- ## Facet Rule Editor
298
-
299
- A standalone facet-rule builder. The `<RecordsAdminShell>` embeds this
300
- automatically when `'rule'` is in `scopes` and the user opens a rule-targeted
301
- record — reach for it directly only when you need rule editing elsewhere
302
- (e.g. a settings page, a conditions sidebar).
303
-
304
- ```tsx
305
- import { FacetRuleEditor } from '@proveanything/smartlinks-utils-ui/facet-rule-editor';
306
- import { useRulePreview } from '@proveanything/smartlinks-utils-ui/records-admin';
307
-
308
- const preview = useRulePreview({ SL, collectionId, appId, rule });
309
-
310
- <FacetRuleEditor
311
- value={rule}
312
- onChange={setRule}
313
- collectionId={collectionId} // lazy-fetches facet definitions via SL.facets.publicList
314
- preview={preview} // optional — wire from useRulePreview
315
- onClear={() => setRule(null)} // optional — renders a "Remove rule" affordance
316
- />
317
- ```
318
-
319
- Props:
320
-
321
- - `value: FacetRule | null` / `onChange: (next: FacetRule | null) => void` — controlled
322
- - `facets?: FacetOption[]` — supply directly, **or** pass `collectionId` and the editor lazy-fetches via `SL.facets.publicList`
323
- - `getFacets?: (collectionId: string) => Promise<FacetOption[]>` — override the lazy-fetcher
324
- - `preview?` — `{ totalMatches, sampleProductIds?, isLoading?, isStale?, error? }` (matches the `useRulePreview` return shape)
325
- - `readOnly?`, `onClear?`, `title?`, `description?`, `className?`
326
-
327
- Free-text facet entry is **not** supported — admins must pick from defined facets.
328
-
329
- See [app-records-pattern.md §4](app-records-pattern.md#4-admin-side----recordsadminshell-the-only-thing-you-should-be-writing) for the standalone usage example.
330
-
331
- ---
332
-
333
- ## Asset Picker
334
-
335
- ```tsx
336
- import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
337
-
338
- <AssetPicker
339
- scope={{ type: 'collection', collectionId: 'abc123' }}
340
- mode="dialog" // or "inline"
341
- allowUpload
342
- accept={['image/*']} // MIME filtering
343
- onSelect={(asset) => setHeroUrl(asset.url)}
344
- trigger={<Button>Choose image</Button>}
345
- />
346
- ```
347
-
348
- Scope shape:
349
-
350
- ```ts
351
- type AssetScope =
352
- | { type: 'collection'; collectionId: string }
353
- | { type: 'product'; collectionId: string; productId: string }
354
- | { type: 'proof'; collectionId: string; productId: string; proofId: string };
355
- ```
356
-
357
- **What it does:**
358
-
359
- - Browses assets at **collection**, **product**, or **proof** scope
360
- - Optional `productScope` prop adds a second tab so users can pick from product-level assets while editing at collection scope (or vice versa)
361
- - Optional `appId` prop stamps every upload with the owning app and adds "This app" / "All in collection" pill tabs with provenance badges on assets owned by other apps
362
- - Four ingest paths: file upload, URL import, clipboard paste (with rename preview), and selection from existing assets
363
- - Inline mode for embedding in a panel; dialog mode for modal pickers
364
- - Double-click a tile to confirm instantly
365
-
366
- ---
367
-
368
- ## Icon Picker
369
-
370
- ```tsx
371
- import { IconPicker } from '@proveanything/smartlinks-utils-ui/icon-picker';
372
-
373
- <IconPicker
374
- mode="dialog"
375
- value="fa-solid fa-heart"
376
- onSelect={(icon) => setIcon(icon.name)}
377
- trigger={<Button>Pick icon</Button>}
378
- />
379
- ```
380
-
381
- `onSelect` receives:
382
-
383
- ```ts
384
- interface IconSelection {
385
- name: string; // full CSS class, e.g. 'fa-solid fa-heart'
386
- family: 'classic' | 'duotone' | 'brands';
387
- style: 'solid' | 'regular' | 'light' | null; // null for brands
388
- label?: string;
389
- }
390
- ```
391
-
392
- **What it does:**
393
-
394
- - Searches **Font Awesome 7 Pro** (uses the shared kit)
395
- - Family hierarchy: Classic (Solid / Regular / Light), Duotone, and Brands
396
- - Search-first with a background catalogue crawler — first results appear instantly, the full index fills in behind
397
- - Auto-switches families intelligently (e.g. brand searches surface brand icons even when "Classic" is selected)
398
-
399
- > Requires the FA kit script to be present on the host page.
400
-
401
- ---
402
-
403
- ## Font Picker
404
-
405
- ```tsx
406
- import { FontPicker } from '@proveanything/smartlinks-utils-ui/font-picker';
407
-
408
- <FontPicker
409
- mode="dialog"
410
- value="Inter"
411
- showPreview
412
- onSelect={(font) => {
413
- console.log(font.family); // "Inter"
414
- console.log(font.cssFontFamily); // "'Inter', ui-sans-serif, system-ui, sans-serif"
415
- console.log(font.loadSnippet); // <link href="..." rel="stylesheet">
416
- }}
417
- />
418
-
419
- {/* With custom fonts uploaded into the collection */}
420
- <FontPicker
421
- mode="dialog"
422
- showCustomFonts
423
- scope={{ collectionId: 'abc123' }}
424
- onSelect={(font) => /* ... */}
425
- />
426
- ```
427
-
428
- **What it does:**
429
-
430
- - Full **Google Fonts** catalogue plus any **custom fonts** uploaded for the brand (stored via `appConfiguration` under `customFonts`)
431
- - Upload zone auto-detects weight/style from the filename (e.g. `MyFont-BoldItalic.woff2`)
432
- - Returns a `FontSelection` with `family`, `cssFontFamily`, and a ready-to-inject `loadSnippet` (`<link>` for Google fonts, `@font-face` CSS for custom uploads)
433
- - Lazy-loads previews via `IntersectionObserver`; includes a management UI for editing custom font definitions
434
-
435
- ---
436
-
437
- ## Conditions Editor
438
-
439
- ```tsx
440
- import { ConditionsEditor } from '@proveanything/smartlinks-utils-ui/conditions-editor';
441
-
442
- <ConditionsEditor
443
- value={rules}
444
- onChange={setRules}
445
- collectionId={collectionId} // auto-loads facet definitions
446
- versions={[{ title: 'Default', value: '' }]}
447
- tags={['featured', 'new']}
448
- />
449
- ```
450
-
451
- **What it does:**
452
-
453
- - Recursive AND / OR group builder — nest conditions to any depth
454
- - **12 condition types:** Version, Country, Value, User, Date, Device, Tag, Facet, Geofence, Product, Item Status, Condition Reference
455
- - **Facet condition** auto-fetches definitions from `facets.publicList(collectionId, { includeValues: true })` when only `collectionId` is passed (SDK ≥ 1.9.20)
456
- - **Country picker** is a searchable multi-select with removable ISO 3166-1 chips and a "Use regions" toggle
457
- - Renders correctly inside iframe contexts — avoids `overflow-hidden` so dropdowns escape their cards
458
-
459
- > `ConditionsEditor` and `FacetRuleEditor` solve different problems.
460
- > `FacetRuleEditor` is a focused AND-of-OR rule over facets only, used to
461
- > target which records apply to which products. `ConditionsEditor` is the
462
- > full recursive logical builder over 12 condition types, used for runtime
463
- > gating, audience segmentation, and version targeting.
464
-
465
- ---
466
-
467
- ## Tree shaking
468
-
469
- Each component has its own subpath export:
470
-
471
- ```tsx
472
- // Bundles only the Asset Picker
473
- import { AssetPicker } from '@proveanything/smartlinks-utils-ui/asset-picker';
474
-
475
- // Barrel import — bundler tree-shakes the rest
476
- import { AssetPicker } from '@proveanything/smartlinks-utils-ui';
477
- ```
478
-
479
- If you use subpath imports, import `styles.css` separately — subpaths do not
480
- pull it in automatically.
481
-
482
- Available subpaths:
483
-
484
- - `/records-admin`
485
- - `/facet-rule-editor`
486
- - `/asset-picker`
487
- - `/icon-picker`
488
- - `/font-picker`
489
- - `/conditions-editor`
490
- - `/styles.css`
491
-
492
- ---
493
-
494
- ## Relationship to the core SDK
495
-
496
- ```text
497
- @proveanything/smartlinks ← data layer (records, config, interactions, …)
498
-
499
- @proveanything/smartlinks-utils-ui ← UI layer (components, hooks, admin shells)
500
-
501
- your microapp ← domain logic and custom forms
502
- ```
96
+ ## Prerequisites
503
97
 
504
- ---
98
+ All components assume `SL.initializeApi()` has already run.
505
99
 
506
- ## Further reading
100
+ ## Where to go next
507
101
 
508
- - [app-records-pattern.md](app-records-pattern.md) — the data contract that `RecordsAdminShell` implements
509
- - [building-react-components.md](building-react-components.md) — dual-mode rendering rules that apply to all components
510
- - [app-manifest.md](app-manifest.md) the `records` manifest block that drives tab generation
102
+ - Component reference: <https://github.com/proveanything/smartlinks-ui/tree/main/packages/smartlinks-ui/docs>
103
+ - Records pattern (concepts, scope inheritance, resolution order): [`records-admin-pattern.md`](./records-admin-pattern.md)
104
+ - Public consumption (how widgets/executors fetch the right records at runtime): the package's [`public-consumption.md`](https://github.com/proveanything/smartlinks-ui/blob/main/packages/smartlinks-ui/docs/public-consumption.md)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "1.13.3",
3
+ "version": "1.13.4",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",