@micahgoodman/notes
v1.1.1
Published
A complete notes module SDK for React with backend API client, adapter system, and UI components
Maintainers
Readme
@micahgoodman/notes
A complete notes module SDK for React with backend API client, adapter system, and reusable UI components. Can be used as a library, standalone app, or embedded with federated authentication.
✨ Three Modes of Operation
📦 Library Mode (npm package)
Import into your React app as a module with full control over UI and auth.
🚀 Standalone Mode (self-contained app)
Deploy as a complete notes application with built-in authentication.
🔗 Embedded Mode (SDK + OAuth)
Import components and authenticate against this module's backend.
Features
- Complete API Client - CRUD operations for notes with Supabase backend support
- Module Adapter System - Flexible adapter pattern for integrating notes into any app
- React Components - Ready-to-use UI components for list, whiteboard, and detail views
- TypeScript - Full type safety throughout
- Realtime Updates - Optional Supabase subscriptions for live data
- Context-Aware - Support for associating notes with other entities
- Federated Auth - Uses @micahgoodman/auth for Keycloak integration and multi-app SSO
- Flexible Deployment - Works as library, standalone app, or embedded widget
Quick Start
Choose your mode:
Library Mode (npm package)
npm install @micahgoodman/notes react @supabase/supabase-jsStandalone Mode (complete app)
git clone https://github.com/micahgoodman/notes-module
cd notes-module
npm install
cp .env.example .env
# Edit .env with your credentials
npm run dev:standaloneEmbedded Mode (SDK with remote backend)
# In your parent app
npm install @micahgoodman/notes @micahgoodman/auth keycloak-js
# Wrap components with RemoteAuthProvider from @micahgoodman/authLibrary Mode Usage
1. Configure the API Client
import { createClient } from '@supabase/supabase-js';
import { configureNotesApi, createNotesAdapter } from '@micahgoodman/notes';
const supabase = createClient(
process.env.VITE_SUPABASE_URL,
process.env.VITE_SUPABASE_ANON_KEY
);
// Configure the notes API
configureNotesApi({
apiBase: process.env.VITE_API_BASE || '/api',
supabaseClient: supabase,
supabaseAnonKey: process.env.VITE_SUPABASE_ANON_KEY
});
// Create the notes adapter with realtime support
export const NoteAdapter = createNotesAdapter(supabase);2. Use the Adapter Hook
import { useModuleList } from '@micahgoodman/notes';
import { NoteAdapter } from './config';
function MyNotesComponent() {
const { items: notes, loading, error, refresh } = useModuleList(NoteAdapter);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
{notes.map(note => (
<div key={note.id}>{note.text}</div>
))}
</div>
);
}3. Use the UI Components
import { NoteListItem, WhiteboardNoteCard, NoteDetailView } from '@micahgoodman/notes';
// In a list view
<NoteListItem
item={note}
isSelected={selectedId === note.id}
onSelect={() => setSelectedId(note.id)}
index={0}
/>
// On a whiteboard
<WhiteboardNoteCard
note={note}
isSelected={selectedId === note.id}
onClick={() => setSelectedId(note.id)}
onCardClick={handleCardClick}
/>
// Detail view
<NoteDetailView
note={selectedNote}
onUpdated={refresh}
onDeleted={() => { setSelectedNote(null); refresh(); }}
onShowToast={(msg) => console.log(msg)}
/>API Reference
Configuration
configureNotesApi(config: ApiConfig)
Configure the notes API client before using any API functions.
configureNotesApi({
apiBase: '/api', // Your API base URL
supabaseClient: supabaseClient, // Optional: for auth headers
supabaseAnonKey: 'your-anon-key' // Optional: for auth headers
});API Functions
fetchNotes(): Promise<Note[]>
Fetch all notes.
fetchNotesByContext(contextType: string, contextId: string): Promise<Note[]>
Fetch notes associated with a specific context.
createNote(input: CreateNoteInput): Promise<{ id: string }>
Create a new note.
updateNote(id: string, input: UpdateNoteInput): Promise<{ ok: boolean }>
Update an existing note.
deleteNote(id: string): Promise<{ ok: boolean }>
Delete a note.
Adapter System
createNotesAdapter(supabaseClient?: SupabaseClient)
Create a notes adapter instance with optional realtime subscriptions.
const NoteAdapter = createNotesAdapter(supabase);useModuleList(adapter: ModuleAdapter, opts?: { context?: Context })
React hook for managing a list of items with the adapter.
// All notes
const { items, loading, error, refresh } = useModuleList(NoteAdapter);
// Notes for a specific context
const { items } = useModuleList(NoteAdapter, {
context: { type: 'project', id: '123' }
});Components
<NoteListItem />
Renders a note in a list view (for use with @micahgoodman/sidebar or custom lists).
Props:
item: Note- The note to displayisSelected: boolean- Whether the note is selectedonSelect: () => void- Callback when note is clickedindex: number- Index in the list
<WhiteboardNoteCard />
Renders a note as a card (for use with @micahgoodman/whiteboard or custom canvases).
Props:
note: Note- The note to displayisSelected: boolean- Whether the note is selectedonClick: () => void- Callback when card content is clickedonCardClick: (e: React.MouseEvent) => void- Callback for card wrapper clicks
<NoteDetailView />
Renders a detailed view/editor for a note.
Props:
note: Note | null- The note to displayonUpdated: () => void- Callback after successful updateonDeleted: () => void- Callback after successful deletiononShowToast: (message: string) => void- Callback for showing notificationscontextChain?: any[]- Optional context chain for embedded displayhideEmbedded?: boolean- Whether to hide embedded content
<CreateNoteModal />
Modal for creating a new note.
Props:
onClose: () => void- Callback to close the modalonCreate: () => void- Callback after successful creationcontext?: Context- Optional context to associate the note with
Types
type Note = {
id: string;
text: string;
createdAt: string;
updatedAt: string;
};
type Context = {
type: string;
id: string;
};
type CreateNoteInput = {
text: string;
context?: Context;
};
type UpdateNoteInput = {
text?: string;
};Complete Example: Notes App
import React, { useState } from 'react';
import { createClient } from '@supabase/supabase-js';
import {
configureNotesApi,
createNotesAdapter,
useModuleList,
NoteListItem,
NoteDetailView,
CreateNoteModal,
} from '@micahgoodman/notes';
// Configure
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
configureNotesApi({
apiBase: '/api',
supabaseClient: supabase,
supabaseAnonKey: SUPABASE_KEY
});
const NoteAdapter = createNotesAdapter(supabase);
// App
function NotesApp() {
const { items: notes, loading, error, refresh } = useModuleList(NoteAdapter);
const [selectedId, setSelectedId] = useState<string | null>(null);
const [showCreate, setShowCreate] = useState(false);
const selectedNote = notes.find(n => n.id === selectedId) || null;
return (
<div style={{ display: 'flex', height: '100vh' }}>
{/* Sidebar */}
<div style={{ width: '300px', borderRight: '1px solid #ccc', overflowY: 'auto' }}>
<button onClick={() => setShowCreate(true)}>+ New Note</button>
{notes.map((note, index) => (
<NoteListItem
key={note.id}
item={note}
isSelected={selectedId === note.id}
onSelect={() => setSelectedId(note.id)}
index={index}
/>
))}
</div>
{/* Detail */}
<div style={{ flex: 1, overflowY: 'auto' }}>
{selectedNote && (
<NoteDetailView
note={selectedNote}
onUpdated={refresh}
onDeleted={() => { setSelectedId(null); refresh(); }}
onShowToast={(msg) => alert(msg)}
/>
)}
</div>
{/* Create Modal */}
{showCreate && (
<CreateNoteModal
onClose={() => setShowCreate(false)}
onCreate={() => { setShowCreate(false); refresh(); }}
/>
)}
</div>
);
}Integration with Other Packages
This package is designed to work seamlessly with:
- @micahgoodman/whiteboard - For whiteboard/canvas layouts
- @micahgoodman/sidebar - For sidebar/list layouts
Example with both:
import { Whiteboard } from '@micahgoodman/whiteboard';
import { SidebarLayout, SidebarList } from '@micahgoodman/sidebar';
import { NoteListItem, WhiteboardNoteCard, NoteDetailView } from '@micahgoodman/notes';
// List View
<SidebarLayout
SidebarComponent={
<SidebarList
items={notes}
ItemComponent={NoteListItem}
// ... other props
/>
}
DetailComponent={<NoteDetailView note={selectedNote} />}
/>
// Whiteboard View
<Whiteboard
items={notes.map(n => ({ ...n, type: 'note' }))}
componentMap={{
note: {
CardComponent: (props) => <WhiteboardNoteCard note={props.item} ... />,
DetailComponent: (props) => <NoteDetailView note={props.item} ... />
}
}}
/>Backend Requirements
This package includes a complete Supabase backend:
- Database schema -
module_datatable with RLS policies - Edge Functions -
/functions/v1/conceptsAPI endpoint - Migrations - In
supabase/migrations/ - Realtime - Optional subscriptions for live updates
API Endpoints
GET /concepts?filter=note- List all notesGET /concepts?filter=note&contextType=X&contextId=Y- List notes by contextPOST /concepts?filter=note- Create notePATCH /concepts/:id- Update noteDELETE /concepts/:id- Delete note
Authentication
Library Mode
Consumer provides authentication (any auth system).
Standalone Mode
Built-in Supabase Auth with Keycloak OAuth provider.
Embedded SDK Mode
Parent app handles Keycloak OAuth, components authenticate silently against remote backend. User logs in once.
Documentation
- GETTING_STARTED.md - Quick start guide
- EMBEDDED_SDK_MODE.md - ⭐ Recommended embedded approach
- SINGLE_SIGN_ON.md - How SSO works (login once, use everywhere)
- MODE_COMPARISON.md - Help choosing the right mode
- SETUP_GUIDE.md - Complete setup for all modes
- USAGE_GUIDE.md - Detailed usage examples
- ARCHITECTURE.md - System design and architecture
- KEYCLOAK_INTEGRATION.md - Keycloak federation guide
Project Structure
notes-module/
├── src/
│ ├── api.ts # API client
│ ├── adapter.ts # Notes adapter
│ ├── types.ts # TypeScript types
│ ├── auth/ # Authentication (uses @micahgoodman/auth)
│ │ ├── AuthProvider.tsx # Mode-based auth wrapper
│ │ ├── types.ts # AppMode type
│ │ ├── utils.ts # Mode detection utils
│ │ └── index.ts # Re-exports from @micahgoodman/auth
│ ├── standalone/ # Standalone app
│ │ ├── App.tsx
│ │ └── pages/
│ └── components/ # Shared UI components
├── auth/ # @micahgoodman/auth package (local)
│ ├── src/ # Auth provider implementations
│ └── README.md # Auth package documentation
├── supabase/
│ ├── migrations/ # Database migrations
│ └── functions/ # Edge functions
├── standalone.html # Standalone entry point
└── SINGLE_SIGN_ON.md # SSO explanationContributing
Contributions welcome! Please read our contributing guidelines.
License
MIT
