Skip to content

Commit f9fb122

Browse files
authored
doc updates (#133)
1 parent b00d32f commit f9fb122

File tree

2 files changed

+213
-6
lines changed

2 files changed

+213
-6
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<p align="center">
44
<img src="./docs/images/oer-finder-plugin-logo.png" width=250 />
55
</p>
6-
An Open Educational Resources (OER) discovery system built on Nostr, providing:
6+
An Open Educational Resources (OER) discovery system built on Nostr and built on [AMB](https://dini-ag-kim.github.io/amb/draft/), providing:
77

88
1. **Proxy Service**: Forwards search queries to configurable source adapters and returns unified OER results via a public API. Supports searching an AMB Nostr relay, Openverse, ARASAAC, RPI-Virtuell, and more through an **extendable adapter system** - add your own adapters to integrate any external API.
99
2. **Source Adapters**: Pluggable adapters for OER sources (e.g., AMB relay, ARASAAC, Openverse) that integrate seamlessly with search results. The adapter plugin system makes it easy to add new sources.

docs/client-packages-react.md

Lines changed: 212 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,71 @@ pnpm add @edufeed-org/oer-finder-plugin-react
1212

1313
This package depends on `@edufeed-org/oer-finder-plugin` internally — you do not need to install the base plugin separately.
1414

15-
### Direct Client Mode
15+
## Operating Modes
1616

17-
For direct client mode (no `apiUrl`), register adapters before rendering:
17+
The plugin supports two operating modes, determined by whether the `apiUrl` prop is provided:
18+
19+
### Server-Proxy Mode (with `apiUrl`)
20+
21+
When `apiUrl` is set, all search requests are routed through your backend proxy. The server handles adapter logic, so **no adapter code is bundled into your client application**.
22+
23+
**Use this mode when:**
24+
- You have a deployed OER Proxy backend
25+
- You want to keep client bundle size small
26+
- You need server-side features like image proxying, rate limiting, or signed URLs
27+
28+
```tsx
29+
<OerSearch
30+
apiUrl="https://your-api-url.com"
31+
sources={SOURCES}
32+
// ...event handlers
33+
>
34+
```
35+
36+
No adapter registration is needed — just provide the `apiUrl` and configure your sources.
37+
38+
### Direct Client Mode (without `apiUrl`)
39+
40+
When `apiUrl` is **omitted**, adapters run directly in the browser. No backend server is required.
41+
42+
**Use this mode when:**
43+
- You want a serverless setup with no backend dependency
44+
- You are prototyping or building a static site
45+
- You want full client-side control over adapter behavior
46+
47+
You **must register adapters** before the component renders. Call the registration function once at your app's entry point:
1848

1949
```typescript
20-
import { registerAllBuiltInAdapters } from '@edufeed-org/oer-finder-plugin-react/adapters';
50+
// Register all built-in adapters
51+
import { registerAllBuiltInAdapters } from '@edufeed-org/oer-finder-plugin/adapters';
2152
registerAllBuiltInAdapters();
2253
```
2354

55+
Or register only the adapters you need to reduce bundle size:
56+
57+
```typescript
58+
import { registerOpenverseAdapter } from '@edufeed-org/oer-finder-plugin/adapter/openverse';
59+
import { registerArasaacAdapter } from '@edufeed-org/oer-finder-plugin/adapter/arasaac';
60+
registerOpenverseAdapter();
61+
registerArasaacAdapter();
62+
```
63+
64+
Then render `OerSearch` without `apiUrl`:
65+
66+
```tsx
67+
<OerSearch
68+
sources={SOURCES}
69+
// ...event handlers (no apiUrl prop)
70+
>
71+
```
72+
73+
Only registered adapters will be available — unregistered source IDs in the `sources` config are silently skipped.
74+
2475
## Basic Usage
2576

26-
The recommended pattern is to slot `OerList` and `OerLoadMore` inside `OerSearch`:
77+
The recommended pattern is to slot `OerList` and `OerLoadMore` inside `OerSearch`. Below are complete examples for each mode.
78+
79+
### Server-Proxy Mode Example
2780

2881
```tsx
2982
import { useState, useCallback } from 'react';
@@ -123,7 +176,161 @@ function OerFinder() {
123176
}
124177
```
125178

126-
## React Props vs Web Component Attributes
179+
### Direct Client Mode Example
180+
181+
The component code is identical to the [server-proxy example above](#server-proxy-mode-example) with two differences: adapters must be registered at startup, and the `apiUrl` prop is omitted.
182+
183+
**1. Register adapters once at your app entry point (e.g., `main.tsx`):**
184+
185+
```tsx
186+
import { registerOpenverseAdapter } from '@edufeed-org/oer-finder-plugin/adapter/openverse';
187+
import { registerArasaacAdapter } from '@edufeed-org/oer-finder-plugin/adapter/arasaac';
188+
registerOpenverseAdapter();
189+
registerArasaacAdapter();
190+
```
191+
192+
**2. Render `OerSearch` without `apiUrl`:**
193+
194+
```tsx
195+
<OerSearch
196+
language="en"
197+
pageSize={20}
198+
sources={SOURCES}
199+
onSearchLoading={handleSearchLoading}
200+
onSearchResults={handleSearchResults}
201+
onSearchError={handleSearchError}
202+
onSearchCleared={handleSearchCleared}
203+
>
204+
<OerList oers={oers} loading={loading} error={error} language="en" onCardClick={handleCardClick} />
205+
<OerLoadMore metadata={metadata} loading={loading} language="en" />
206+
</OerSearch>
207+
```
208+
209+
All state management, event handlers, and child components remain the same.
210+
211+
## Slot Architecture and Event Bubbling
212+
213+
`OerSearch` acts as the orchestrator. When you render `OerList` and `OerLoadMore` as children of `OerSearch`, they are slotted into the underlying `<oer-search>` web component. This enables **automatic event bubbling**: the `load-more` event fired by `OerLoadMore` bubbles up through the DOM and is caught by the parent `OerSearch`, which then fetches the next page of results and emits a new `search-results` event. You do not need to wire up any load-more handler yourself — just place the components in the correct parent-child relationship:
214+
215+
```tsx
216+
<OerSearch /* handles pagination automatically */>
217+
<OerList /* displays results */ />
218+
<OerLoadMore /* fires load-more, caught by OerSearch */ />
219+
</OerSearch>
220+
```
221+
222+
If you render `OerLoadMore` outside of `OerSearch`, the event will not bubble to the search component and pagination will not work automatically. In that case you would need to handle the `onLoadMore` event yourself.
223+
224+
## Component Props Reference
225+
226+
### `OerSearch`
227+
228+
| Prop | Type | Required | Default | Description |
229+
|------|------|----------|---------|-------------|
230+
| `apiUrl` | `string` | No | — | Base URL of the OER Proxy API. When provided, activates server-proxy mode. When omitted, activates direct client mode (adapters must be registered). |
231+
| `sources` | `SourceConfig[]` | No | `[openverse, arasaac]` | Available sources shown in the UI. See [Source Configuration](./client-packages.md#source-configuration). |
232+
| `language` | `SupportedLanguage` | No | `'en'` | UI language (`'en'` or `'de'`). |
233+
| `pageSize` | `number` | No | `20` | Number of results per page. |
234+
| `lockedType` | `string` | No | — | Lock the type filter to a specific value (e.g., `'image'`). |
235+
| `showTypeFilter` | `boolean` | No | `true` | Show or hide the type filter dropdown. |
236+
| `lockedSource` | `string` | No | — | Lock the source filter to a specific value. |
237+
| `showSourceFilter` | `boolean` | No | `true` | Show or hide the source filter checkboxes. |
238+
239+
### `OerList`
240+
241+
| Prop | Type | Required | Default | Description |
242+
|------|------|----------|---------|-------------|
243+
| `oers` | `OerItem[]` | No | `[]` | Array of OER items to display. |
244+
| `loading` | `boolean` | No | `false` | When `true`, shows a loading skeleton. |
245+
| `error` | `string \| null` | No | `null` | Error message to display. Pass `null` to clear. |
246+
| `language` | `SupportedLanguage` | No | `'en'` | UI language (`'en'` or `'de'`). |
247+
248+
### `OerCard`
249+
250+
| Prop | Type | Required | Default | Description |
251+
|------|------|----------|---------|-------------|
252+
| `oer` | `OerItem \| null` | No | `null` | OER item data to render. |
253+
| `language` | `SupportedLanguage` | No | `'en'` | UI language (`'en'` or `'de'`). |
254+
255+
### `OerLoadMore`
256+
257+
| Prop | Type | Required | Default | Description |
258+
|------|------|----------|---------|-------------|
259+
| `metadata` | `LoadMoreMeta \| null` | No | `null` | Pagination metadata (`{ total, shown, hasMore }`). Controls the "Showing X of Y" indicator and whether the button is visible. |
260+
| `loading` | `boolean` | No | `false` | When `true`, disables the button and shows a loading state. |
261+
| `language` | `SupportedLanguage` | No | `'en'` | UI language (`'en'` or `'de'`). |
262+
263+
## Event Handlers
264+
265+
### `OerSearch` Events
266+
267+
| Prop | Callback Signature | Description |
268+
|------|-------------------|-------------|
269+
| `onSearchLoading` | `(event: CustomEvent<void>) => void` | Fired when a search request starts. Use this to set loading state. |
270+
| `onSearchResults` | `(event: OerSearchResultEvent) => void` | Fired when search completes. `event.detail` contains `{ data: OerItem[], meta: LoadMoreMeta }`. |
271+
| `onSearchError` | `(event: CustomEvent<{ error: string }>) => void` | Fired when a search fails. `event.detail.error` contains the error message. |
272+
| `onSearchCleared` | `(event: CustomEvent<void>) => void` | Fired when the user clears the search input. |
273+
274+
### `OerList` Events
275+
276+
| Prop | Callback Signature | Description |
277+
|------|-------------------|-------------|
278+
| `onCardClick` | `(event: OerCardClickEvent) => void` | Fired when a card is clicked. `event.detail.oer` contains the clicked `OerItem`. Bubbles up from child `OerCard` components. |
279+
280+
### `OerCard` Events
281+
282+
| Prop | Callback Signature | Description |
283+
|------|-------------------|-------------|
284+
| `onCardClick` | `(event: OerCardClickEvent) => void` | Fired when the card image is clicked. `event.detail.oer` contains the `OerItem`. |
285+
286+
### `OerLoadMore` Events
287+
288+
| Prop | Callback Signature | Description |
289+
|------|-------------------|-------------|
290+
| `onLoadMore` | `(event: CustomEvent<void>) => void` | Fired when the "Load more" button is clicked. When slotted inside `OerSearch`, this event bubbles up automatically to trigger the next page fetch — no manual handler needed. |
291+
292+
## Key Types
293+
294+
All types are importable from `@edufeed-org/oer-finder-plugin-react`:
295+
296+
```typescript
297+
import {
298+
// Components
299+
OerSearch,
300+
OerList,
301+
OerCard,
302+
OerLoadMore,
303+
304+
// Event types
305+
type OerSearchResultEvent, // CustomEvent<{ data: OerItem[], meta: LoadMoreMeta }>
306+
type OerSearchResultDetail, // { data: OerItem[], meta: LoadMoreMeta }
307+
type OerCardClickEvent, // CustomEvent<{ oer: OerItem }>
308+
type OerCardClickDetail, // { oer: OerItem }
309+
310+
// Data types
311+
type OerItem, // Normalized AMB metadata for a single resource
312+
type LoadMoreMeta, // { total: number, shown: number, hasMore: boolean }
313+
type SourceConfig, // { id: string, label: string, baseUrl?: string, checked?: boolean }
314+
type SupportedLanguage, // 'en' | 'de'
315+
316+
// API client types (re-exported from oer-finder-api-client)
317+
type OerMetadata,
318+
type OerListResponse,
319+
} from '@edufeed-org/oer-finder-plugin-react';
320+
```
321+
322+
### State Typing
323+
324+
When managing component state, use explicit generic parameters:
325+
326+
```typescript
327+
const [oers, setOers] = useState<OerItem[]>([]);
328+
const [loading, setLoading] = useState<boolean>(false);
329+
const [error, setError] = useState<string | null>(null);
330+
const [metadata, setMetadata] = useState<LoadMoreMeta | null>(null);
331+
```
332+
333+
## React Props to Web Component Attribute Mapping
127334

128335
The React wrapper uses camelCase props that map to web component attributes:
129336

0 commit comments

Comments
 (0)