feat(globalStateStore): implement global state management with persistence
- refactor state structure to include app, layout, navigation, owner, program, session, and user - add slices for managing program, session, owner, user, layout, navigation, and app states - create context provider for global state with automatic fetching and throttling - implement persistence using IndexedDB with localStorage fallback - add comprehensive README documentation for usage and API
This commit is contained in:
102
mcp/README.md
Normal file
102
mcp/README.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Oranguru MCP Server
|
||||
|
||||
Model Context Protocol server for Oranguru component library documentation and code generation.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @warkypublic/oranguru
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Add to your Claude Code MCP settings (`~/.claude/mcp_settings.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"oranguru-docs": {
|
||||
"command": "node",
|
||||
"args": ["./node_modules/@warkypublic/oranguru/mcp/server.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Or use npx:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"oranguru-docs": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "@warkypublic/oranguru", "mcp"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Available Tools
|
||||
|
||||
### `list_components`
|
||||
List all available Oranguru components
|
||||
|
||||
**Returns:** JSON array of components with name, description, and exports
|
||||
|
||||
### `get_component_docs`
|
||||
Get detailed documentation for a specific component
|
||||
|
||||
**Parameters:**
|
||||
- `component` (required): Component name (MantineBetterMenu, Gridler, Former, etc.)
|
||||
|
||||
**Returns:** JSON object with component details, exports, and usage information
|
||||
|
||||
### `get_component_example`
|
||||
Get code examples for a specific component
|
||||
|
||||
**Parameters:**
|
||||
- `component` (required): Component name
|
||||
- `variant` (optional): Example variant ('basic', 'local', 'server', etc.)
|
||||
|
||||
**Returns:** Code example string
|
||||
|
||||
## Available Resources
|
||||
|
||||
### `oranguru://docs/readme`
|
||||
Full README documentation
|
||||
|
||||
**MIME Type:** text/markdown
|
||||
|
||||
### `oranguru://docs/components`
|
||||
Component list in JSON format
|
||||
|
||||
**MIME Type:** application/json
|
||||
|
||||
## Components
|
||||
|
||||
- **MantineBetterMenu** - Enhanced context menus
|
||||
- **Gridler** - Data grid component
|
||||
- **Former** - Form component with React Hook Form
|
||||
- **FormerControllers** - Form input controls
|
||||
- **Boxer** - Advanced combobox/select
|
||||
- **ErrorBoundary** - Error boundary components
|
||||
- **GlobalStateStore** - Global state management
|
||||
|
||||
## Usage in Claude Code
|
||||
|
||||
Once configured, you can ask Claude Code:
|
||||
|
||||
- "Show me examples of the Gridler component"
|
||||
- "Get documentation for the Former component"
|
||||
- "List all Oranguru components"
|
||||
- "Generate a code example for Boxer with server-side data"
|
||||
|
||||
## Running Locally
|
||||
|
||||
```bash
|
||||
npm run mcp
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
See main package LICENSE file
|
||||
953
mcp/server.js
Executable file
953
mcp/server.js
Executable file
@@ -0,0 +1,953 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Oranguru MCP Server
|
||||
* Provides documentation and code generation for Oranguru components
|
||||
*/
|
||||
|
||||
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
||||
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListResourcesRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
ReadResourceRequestSchema,
|
||||
} from '@modelcontextprotocol/sdk/types.js';
|
||||
import { readFileSync } from 'fs';
|
||||
import { dirname, join } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
// Component documentation data
|
||||
const COMPONENTS = {
|
||||
Boxer: {
|
||||
component: 'Boxer',
|
||||
description: 'Advanced combobox/select with virtualization and server-side data support',
|
||||
examples: {
|
||||
basic: `import { Boxer } from '@warkypublic/oranguru';
|
||||
import { useState } from 'react';
|
||||
|
||||
const sampleData = [
|
||||
{ label: 'Apple', value: 'apple' },
|
||||
{ label: 'Banana', value: 'banana' },
|
||||
{ label: 'Cherry', value: 'cherry' }
|
||||
];
|
||||
|
||||
function MyComponent() {
|
||||
const [value, setValue] = useState(null);
|
||||
|
||||
return (
|
||||
<Boxer
|
||||
data={sampleData}
|
||||
dataSource="local"
|
||||
label="Favorite Fruit"
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
searchable
|
||||
clearable
|
||||
placeholder="Select a fruit"
|
||||
/>
|
||||
);
|
||||
}`,
|
||||
multiSelect: `import { Boxer } from '@warkypublic/oranguru';
|
||||
import { useState } from 'react';
|
||||
|
||||
function MultiSelectExample() {
|
||||
const [values, setValues] = useState([]);
|
||||
|
||||
return (
|
||||
<Boxer
|
||||
data={sampleData}
|
||||
dataSource="local"
|
||||
label="Favorite Fruits"
|
||||
multiSelect
|
||||
value={values}
|
||||
onChange={setValues}
|
||||
searchable
|
||||
clearable
|
||||
/>
|
||||
);
|
||||
}`,
|
||||
server: `import { Boxer } from '@warkypublic/oranguru';
|
||||
|
||||
function ServerSideExample() {
|
||||
const [value, setValue] = useState(null);
|
||||
|
||||
const handleAPICall = async ({ page, pageSize, search }) => {
|
||||
const response = await fetch(\`/api/items?page=\${page}&size=\${pageSize}&search=\${search}\`);
|
||||
const result = await response.json();
|
||||
|
||||
return {
|
||||
data: result.items,
|
||||
total: result.total
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<Boxer
|
||||
dataSource="server"
|
||||
label="Server-side Data"
|
||||
onAPICall={handleAPICall}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
pageSize={10}
|
||||
searchable
|
||||
/>
|
||||
);
|
||||
}`
|
||||
},
|
||||
exports: ['Boxer', 'BoxerProvider', 'useBoxerStore'],
|
||||
hook: 'useBoxerStore()',
|
||||
name: 'Boxer',
|
||||
provider: 'BoxerProvider'
|
||||
},
|
||||
ErrorBoundary: {
|
||||
components: ['ReactErrorBoundary', 'ReactBasicErrorBoundary'],
|
||||
description: 'React error boundary components for graceful error handling',
|
||||
examples: {
|
||||
basic: `import { ReactBasicErrorBoundary } from '@warkypublic/oranguru';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<ReactBasicErrorBoundary>
|
||||
<MyComponent />
|
||||
</ReactBasicErrorBoundary>
|
||||
);
|
||||
}`,
|
||||
full: `import { ReactErrorBoundary } from '@warkypublic/oranguru';
|
||||
|
||||
function App() {
|
||||
const handleReportError = () => {
|
||||
console.log('Report error to support');
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
console.log('Reset application state');
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
const handleRetry = () => {
|
||||
console.log('Retry failed operation');
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactErrorBoundary
|
||||
namespace="main-app"
|
||||
reportAPI="/api/errors/report"
|
||||
onReportClick={handleReportError}
|
||||
onResetClick={handleReset}
|
||||
onRetryClick={handleRetry}
|
||||
>
|
||||
<MyApp />
|
||||
</ReactErrorBoundary>
|
||||
);
|
||||
}`,
|
||||
nested: `import { ReactErrorBoundary } from '@warkypublic/oranguru';
|
||||
|
||||
// Multiple error boundaries for granular error handling
|
||||
function App() {
|
||||
return (
|
||||
<ReactErrorBoundary namespace="app">
|
||||
<Header />
|
||||
<ReactErrorBoundary namespace="sidebar">
|
||||
<Sidebar />
|
||||
</ReactErrorBoundary>
|
||||
<ReactErrorBoundary namespace="main-content">
|
||||
<MainContent />
|
||||
</ReactErrorBoundary>
|
||||
</ReactErrorBoundary>
|
||||
);
|
||||
}`,
|
||||
globalConfig: `import { SetErrorBoundaryOptions } from '@warkypublic/oranguru';
|
||||
|
||||
// Configure error boundary globally
|
||||
SetErrorBoundaryOptions({
|
||||
disabled: false, // Set to true to pass through errors (dev mode)
|
||||
onError: (error, errorInfo) => {
|
||||
console.error('Global error handler:', error);
|
||||
// Send to analytics service
|
||||
analytics.trackError(error, errorInfo);
|
||||
}
|
||||
});`
|
||||
},
|
||||
exports: ['ReactErrorBoundary', 'ReactBasicErrorBoundary', 'SetErrorBoundaryOptions', 'GetErrorBoundaryOptions'],
|
||||
name: 'ErrorBoundary'
|
||||
},
|
||||
Former: {
|
||||
component: 'Former',
|
||||
description: 'Form component with React Hook Form integration and validation',
|
||||
examples: {
|
||||
basic: `import { Former } from '@warkypublic/oranguru';
|
||||
import { useRef } from 'react';
|
||||
import { Controller } from 'react-hook-form';
|
||||
|
||||
function BasicForm() {
|
||||
const formRef = useRef(null);
|
||||
|
||||
return (
|
||||
<Former
|
||||
ref={formRef}
|
||||
primeData={{ name: '', email: '' }}
|
||||
onSave={async (data) => {
|
||||
console.log('Saving:', data);
|
||||
await fetch('/api/save', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Controller
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<input {...field} placeholder="Name" />
|
||||
)}
|
||||
rules={{ required: 'Name is required' }}
|
||||
/>
|
||||
<Controller
|
||||
name="email"
|
||||
render={({ field }) => (
|
||||
<input {...field} type="email" placeholder="Email" />
|
||||
)}
|
||||
rules={{ required: 'Email is required' }}
|
||||
/>
|
||||
<button type="submit">Save</button>
|
||||
</Former>
|
||||
);
|
||||
}`,
|
||||
withWrapper: `import { Former, FormerModel } from '@warkypublic/oranguru';
|
||||
import { useState } from 'react';
|
||||
|
||||
function ModalForm() {
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => setOpened(true)}>Open Form</button>
|
||||
|
||||
<FormerModel
|
||||
former={{ request: 'insert' }}
|
||||
opened={opened}
|
||||
onClose={() => setOpened(false)}
|
||||
>
|
||||
<Controller
|
||||
name="title"
|
||||
render={({ field }) => <input {...field} />}
|
||||
/>
|
||||
</FormerModel>
|
||||
</>
|
||||
);
|
||||
}`,
|
||||
withAPI: `import { Former, FormerRestHeadSpecAPI } from '@warkypublic/oranguru';
|
||||
|
||||
function APIForm() {
|
||||
return (
|
||||
<Former
|
||||
request="update"
|
||||
uniqueKeyField="id"
|
||||
primeData={{ id: 123 }}
|
||||
onAPICall={FormerRestHeadSpecAPI({
|
||||
url: 'https://api.example.com/items',
|
||||
authToken: 'your-token'
|
||||
})}
|
||||
>
|
||||
{/* Form fields */}
|
||||
</Former>
|
||||
);
|
||||
}`,
|
||||
customLayout: `import { Former } from '@warkypublic/oranguru';
|
||||
|
||||
function CustomLayoutForm() {
|
||||
return (
|
||||
<Former
|
||||
layout={{
|
||||
title: 'Edit User Profile',
|
||||
buttonArea: 'bottom',
|
||||
buttonAreaGroupProps: { justify: 'space-between' }
|
||||
}}
|
||||
primeData={{ username: '', bio: '' }}
|
||||
>
|
||||
{/* Form fields */}
|
||||
</Former>
|
||||
);
|
||||
}`,
|
||||
refMethods: `import { Former } from '@warkypublic/oranguru';
|
||||
import { useRef } from 'react';
|
||||
|
||||
function FormWithRef() {
|
||||
const formRef = useRef(null);
|
||||
|
||||
const handleValidate = async () => {
|
||||
const isValid = await formRef.current?.validate();
|
||||
console.log('Form valid:', isValid);
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
const result = await formRef.current?.save();
|
||||
console.log('Save result:', result);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
formRef.current?.reset();
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Former ref={formRef} primeData={{}}>
|
||||
{/* Form fields */}
|
||||
</Former>
|
||||
<button onClick={handleValidate}>Validate</button>
|
||||
<button onClick={handleSave}>Save</button>
|
||||
<button onClick={handleReset}>Reset</button>
|
||||
</div>
|
||||
);
|
||||
}`
|
||||
},
|
||||
exports: ['Former', 'FormerDialog', 'FormerModel', 'FormerPopover', 'FormerRestHeadSpecAPI'],
|
||||
name: 'Former',
|
||||
wrappers: ['FormerDialog', 'FormerModel', 'FormerPopover']
|
||||
},
|
||||
FormerControllers: {
|
||||
controls: ['TextInputCtrl', 'PasswordInputCtrl', 'NativeSelectCtrl', 'TextAreaCtrl', 'SwitchCtrl', 'ButtonCtrl', 'IconButtonCtrl'],
|
||||
description: 'Pre-built form input controls for use with Former',
|
||||
examples: {
|
||||
basic: `import { TextInputCtrl, PasswordInputCtrl, NativeSelectCtrl, ButtonCtrl } from '@warkypublic/oranguru';
|
||||
|
||||
<Former>
|
||||
<TextInputCtrl name="username" label="Username" />
|
||||
<PasswordInputCtrl name="password" label="Password" />
|
||||
<NativeSelectCtrl name="role" data={['Admin', 'User']} />
|
||||
<ButtonCtrl type="submit">Save</ButtonCtrl>
|
||||
</Former>`
|
||||
},
|
||||
exports: ['TextInputCtrl', 'PasswordInputCtrl', 'NativeSelectCtrl', 'TextAreaCtrl', 'SwitchCtrl', 'ButtonCtrl', 'IconButtonCtrl'],
|
||||
name: 'FormerControllers'
|
||||
},
|
||||
GlobalStateStore: {
|
||||
description: 'Zustand-based global state management with automatic persistence',
|
||||
examples: {
|
||||
basic: `import { useGlobalStateStore } from '@warkypublic/oranguru';
|
||||
|
||||
function MyComponent() {
|
||||
const state = useGlobalStateStore();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{state.program.name}</h1>
|
||||
<p>User: {state.user.username}</p>
|
||||
<p>Email: {state.user.email}</p>
|
||||
<p>Connected: {state.session.connected ? 'Yes' : 'No'}</p>
|
||||
</div>
|
||||
);
|
||||
}`,
|
||||
provider: `import { GlobalStateStoreProvider } from '@warkypublic/oranguru';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<GlobalStateStoreProvider
|
||||
apiURL="https://api.example.com"
|
||||
fetchOnMount={true}
|
||||
throttleMs={5000}
|
||||
>
|
||||
<MyApp />
|
||||
</GlobalStateStoreProvider>
|
||||
);
|
||||
}`,
|
||||
stateUpdates: `import { useGlobalStateStore } from '@warkypublic/oranguru';
|
||||
|
||||
function StateControls() {
|
||||
const state = useGlobalStateStore();
|
||||
|
||||
const handleUpdateProgram = () => {
|
||||
state.setProgram({
|
||||
name: 'My App',
|
||||
slug: 'my-app',
|
||||
description: 'A great application'
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateUser = () => {
|
||||
state.setUser({
|
||||
username: 'john_doe',
|
||||
email: 'john@example.com',
|
||||
fullNames: 'John Doe'
|
||||
});
|
||||
};
|
||||
|
||||
const handleToggleTheme = () => {
|
||||
state.setUser({
|
||||
theme: {
|
||||
...state.user.theme,
|
||||
darkMode: !state.user.theme?.darkMode
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={handleUpdateProgram}>Update Program</button>
|
||||
<button onClick={handleUpdateUser}>Update User</button>
|
||||
<button onClick={handleToggleTheme}>Toggle Dark Mode</button>
|
||||
</div>
|
||||
);
|
||||
}`,
|
||||
layout: `import { useGlobalStateStore } from '@warkypublic/oranguru';
|
||||
|
||||
function LayoutControls() {
|
||||
const state = useGlobalStateStore();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => state.setLeftBar({ open: !state.layout.leftBar.open })}>
|
||||
Toggle Left Bar
|
||||
</button>
|
||||
<button onClick={() => state.setRightBar({ open: !state.layout.rightBar.open })}>
|
||||
Toggle Right Bar
|
||||
</button>
|
||||
<button onClick={() => state.setLeftBar({ pinned: true, size: 250 })}>
|
||||
Pin & Resize Left Bar
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}`,
|
||||
outsideReact: `import { GlobalStateStore } from '@warkypublic/oranguru';
|
||||
|
||||
// Access state outside React components
|
||||
const currentState = GlobalStateStore.getState();
|
||||
console.log('API URL:', currentState.session.apiURL);
|
||||
|
||||
// Update state outside React
|
||||
GlobalStateStore.getState().setAuthToken('new-token');
|
||||
GlobalStateStore.getState().setApiURL('https://new-api.com');
|
||||
|
||||
// Subscribe to changes
|
||||
const unsubscribe = GlobalStateStore.subscribe(
|
||||
(state) => state.session.connected,
|
||||
(connected) => console.log('Connected:', connected)
|
||||
);`
|
||||
},
|
||||
exports: ['GlobalStateStore', 'GlobalStateStoreProvider', 'useGlobalStateStore', 'useGlobalStateStoreContext'],
|
||||
hook: 'useGlobalStateStore()',
|
||||
name: 'GlobalStateStore',
|
||||
provider: 'GlobalStateStoreProvider',
|
||||
store: 'GlobalStateStore'
|
||||
},
|
||||
Gridler: {
|
||||
adaptors: ['LocalDataAdaptor', 'APIAdaptorForGoLangv2', 'FormAdaptor'],
|
||||
component: 'Gridler',
|
||||
description: 'Powerful data grid component with sorting, filtering, and pagination',
|
||||
examples: {
|
||||
basic: `import { Gridler } from '@warkypublic/oranguru';
|
||||
|
||||
const columns = [
|
||||
{ id: 'name', title: 'Name', width: 200 },
|
||||
{ id: 'email', title: 'Email', width: 250 },
|
||||
{ id: 'role', title: 'Role', width: 150 }
|
||||
];
|
||||
|
||||
const data = [
|
||||
{ name: 'John Doe', email: 'john@example.com', role: 'Admin' },
|
||||
{ name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
|
||||
];
|
||||
|
||||
function GridExample() {
|
||||
return (
|
||||
<Gridler columns={columns} uniqueid="my-grid">
|
||||
<Gridler.LocalDataAdaptor data={data} />
|
||||
</Gridler>
|
||||
);
|
||||
}`,
|
||||
customCell: `// Custom cell rendering with icons
|
||||
const columns = [
|
||||
{
|
||||
id: 'status',
|
||||
title: 'Status',
|
||||
width: 100,
|
||||
Cell: (row) => {
|
||||
const icon = row?.active ? '✅' : '❌';
|
||||
return {
|
||||
data: \`\${icon} \${row?.status}\`,
|
||||
displayData: \`\${icon} \${row?.status}\`
|
||||
};
|
||||
}
|
||||
},
|
||||
{ id: 'name', title: 'Name', width: 200 }
|
||||
];`,
|
||||
api: `import { Gridler, GlidlerAPIAdaptorForGoLangv2 } from '@warkypublic/oranguru';
|
||||
import { useRef } from 'react';
|
||||
|
||||
function APIGridExample() {
|
||||
const gridRef = useRef(null);
|
||||
|
||||
const columns = [
|
||||
{ id: 'id', title: 'ID', width: 100 },
|
||||
{ id: 'name', title: 'Name', width: 200 },
|
||||
{ id: 'status', title: 'Status', width: 150 }
|
||||
];
|
||||
|
||||
return (
|
||||
<Gridler
|
||||
columns={columns}
|
||||
uniqueid="api-grid"
|
||||
ref={gridRef}
|
||||
selectMode="row"
|
||||
searchStr={searchTerm}
|
||||
onChange={(values) => console.log('Selected:', values)}
|
||||
>
|
||||
<GlidlerAPIAdaptorForGoLangv2
|
||||
url="https://api.example.com/data"
|
||||
authtoken="your-api-key"
|
||||
/>
|
||||
</Gridler>
|
||||
);
|
||||
}`,
|
||||
withForm: `// Gridler with Former integration for inline editing
|
||||
import { Gridler, GlidlerAPIAdaptorForGoLangv2 } from '@warkypublic/oranguru';
|
||||
import { FormerDialog } from '@warkypublic/oranguru';
|
||||
import { TextInputCtrl, NativeSelectCtrl } from '@warkypublic/oranguru';
|
||||
import { useState, useRef } from 'react';
|
||||
|
||||
function EditableGrid() {
|
||||
const gridRef = useRef(null);
|
||||
const [formProps, setFormProps] = useState({
|
||||
opened: false,
|
||||
request: null,
|
||||
values: null,
|
||||
onClose: () => setFormProps(prev => ({
|
||||
...prev,
|
||||
opened: false,
|
||||
request: null,
|
||||
values: null
|
||||
})),
|
||||
onChange: (request, data) => {
|
||||
gridRef.current?.refresh({ value: data });
|
||||
}
|
||||
});
|
||||
|
||||
const columns = [
|
||||
{ id: 'id', title: 'ID', width: 100 },
|
||||
{ id: 'name', title: 'Name', width: 200 },
|
||||
{ id: 'type', title: 'Type', width: 150 }
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gridler
|
||||
ref={gridRef}
|
||||
columns={columns}
|
||||
uniqueid="editable-grid"
|
||||
selectMode="row"
|
||||
>
|
||||
<GlidlerAPIAdaptorForGoLangv2
|
||||
url="https://api.example.com/items"
|
||||
authtoken="your-token"
|
||||
/>
|
||||
<Gridler.FormAdaptor
|
||||
changeOnActiveClick={true}
|
||||
descriptionField="name"
|
||||
onRequestForm={(request, data) => {
|
||||
setFormProps(prev => ({
|
||||
...prev,
|
||||
opened: true,
|
||||
request: request,
|
||||
values: data
|
||||
}));
|
||||
}}
|
||||
/>
|
||||
</Gridler>
|
||||
|
||||
<FormerDialog
|
||||
former={{
|
||||
request: formProps.request ?? "insert",
|
||||
values: formProps.values
|
||||
}}
|
||||
opened={formProps.opened}
|
||||
onClose={formProps.onClose}
|
||||
title="Edit Item"
|
||||
>
|
||||
<TextInputCtrl label="Name" name="name" />
|
||||
<TextInputCtrl label="Description" name="description" />
|
||||
<NativeSelectCtrl
|
||||
label="Type"
|
||||
name="type"
|
||||
data={["Type A", "Type B", "Type C"]}
|
||||
/>
|
||||
</FormerDialog>
|
||||
</>
|
||||
);
|
||||
}`,
|
||||
refMethods: `// Using Gridler ref methods for programmatic control
|
||||
import { Gridler } from '@warkypublic/oranguru';
|
||||
import { useRef } from 'react';
|
||||
|
||||
function GridWithControls() {
|
||||
const gridRef = useRef(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Gridler ref={gridRef} columns={columns} uniqueid="controlled-grid">
|
||||
<Gridler.LocalDataAdaptor data={data} />
|
||||
</Gridler>
|
||||
|
||||
<div>
|
||||
<button onClick={() => gridRef.current?.refresh()}>
|
||||
Refresh Grid
|
||||
</button>
|
||||
<button onClick={() => gridRef.current?.selectRow(123)}>
|
||||
Select Row 123
|
||||
</button>
|
||||
<button onClick={() => gridRef.current?.scrollToRow(456)}>
|
||||
Scroll to Row 456
|
||||
</button>
|
||||
<button onClick={() => gridRef.current?.reloadRow(789)}>
|
||||
Reload Row 789
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}`,
|
||||
sections: `// Gridler with custom side sections
|
||||
import { Gridler } from '@warkypublic/oranguru';
|
||||
import { useState } from 'react';
|
||||
|
||||
function GridWithSections() {
|
||||
const [sections, setSections] = useState({
|
||||
top: <div style={{ backgroundColor: 'purple', height: '20px' }}>Top Bar</div>,
|
||||
bottom: <div style={{ backgroundColor: 'teal', height: '25px' }}>Bottom Bar</div>,
|
||||
left: <div style={{ backgroundColor: 'orange', width: '20px' }}>L</div>,
|
||||
right: <div style={{ backgroundColor: 'green', width: '20px' }}>R</div>
|
||||
});
|
||||
|
||||
return (
|
||||
<Gridler
|
||||
columns={columns}
|
||||
uniqueid="sections-grid"
|
||||
sections={{ ...sections, rightElementDisabled: false }}
|
||||
>
|
||||
<Gridler.LocalDataAdaptor data={data} />
|
||||
</Gridler>
|
||||
);
|
||||
}`
|
||||
},
|
||||
exports: ['Gridler', 'GlidlerLocalDataAdaptor', 'GlidlerAPIAdaptorForGoLangv2', 'GlidlerFormAdaptor'],
|
||||
hook: 'useGridlerStore()',
|
||||
name: 'Gridler'
|
||||
},
|
||||
MantineBetterMenu: {
|
||||
description: 'Enhanced context menus with better positioning and visibility control',
|
||||
examples: {
|
||||
provider: `import { MantineBetterMenusProvider } from '@warkypublic/oranguru';
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<MantineProvider>
|
||||
<MantineBetterMenusProvider providerID="main">
|
||||
<YourApp />
|
||||
</MantineBetterMenusProvider>
|
||||
</MantineProvider>
|
||||
);
|
||||
}`,
|
||||
contextMenu: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
|
||||
|
||||
function MyComponent() {
|
||||
const { show, hide } = useMantineBetterMenus();
|
||||
|
||||
const handleContextMenu = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
show('context-menu', {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
items: [
|
||||
{
|
||||
label: 'Edit',
|
||||
onClick: () => console.log('Edit clicked')
|
||||
},
|
||||
{
|
||||
label: 'Copy',
|
||||
onClick: () => console.log('Copy clicked')
|
||||
},
|
||||
{
|
||||
isDivider: true
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
onClick: () => console.log('Delete clicked'),
|
||||
color: 'red'
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div onContextMenu={handleContextMenu}>
|
||||
Right-click me for a context menu
|
||||
</div>
|
||||
);
|
||||
}`,
|
||||
asyncActions: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
|
||||
|
||||
function AsyncMenuExample() {
|
||||
const { show } = useMantineBetterMenus();
|
||||
|
||||
const handleClick = (e) => {
|
||||
show('async-menu', {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
items: [
|
||||
{
|
||||
label: 'Save',
|
||||
onClickAsync: async () => {
|
||||
await fetch('/api/save', { method: 'POST' });
|
||||
console.log('Saved successfully');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Load Data',
|
||||
onClickAsync: async () => {
|
||||
const data = await fetch('/api/data').then(r => r.json());
|
||||
console.log('Data loaded:', data);
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
return <button onClick={handleClick}>Show Menu</button>;
|
||||
}`,
|
||||
customRenderer: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
|
||||
|
||||
function CustomMenuExample() {
|
||||
const { show } = useMantineBetterMenus();
|
||||
|
||||
const handleClick = (e) => {
|
||||
show('custom-menu', {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
items: [
|
||||
{
|
||||
renderer: ({ loading }) => (
|
||||
<div style={{ padding: '8px 12px', color: 'blue' }}>
|
||||
{loading ? 'Processing...' : 'Custom Item'}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
|
||||
return <button onClick={handleClick}>Custom Menu</button>;
|
||||
}`
|
||||
},
|
||||
exports: ['MantineBetterMenusProvider', 'useMantineBetterMenus'],
|
||||
hook: 'useMantineBetterMenus()',
|
||||
name: 'MantineBetterMenu',
|
||||
provider: 'MantineBetterMenusProvider'
|
||||
}
|
||||
};
|
||||
|
||||
class OranguruMCPServer {
|
||||
constructor() {
|
||||
this.server = new Server(
|
||||
{
|
||||
name: 'oranguru-docs',
|
||||
version: '0.0.31',
|
||||
},
|
||||
{
|
||||
capabilities: {
|
||||
resources: {},
|
||||
tools: {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
this.setupHandlers();
|
||||
this.server.onerror = (error) => console.error('[MCP Error]', error);
|
||||
process.on('SIGINT', async () => {
|
||||
await this.server.close();
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
async run() {
|
||||
const transport = new StdioServerTransport();
|
||||
await this.server.connect(transport);
|
||||
console.error('Oranguru MCP server running on stdio');
|
||||
}
|
||||
|
||||
setupHandlers() {
|
||||
// List available tools
|
||||
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [
|
||||
{
|
||||
description: 'Get documentation for a specific Oranguru component',
|
||||
inputSchema: {
|
||||
properties: {
|
||||
component: {
|
||||
description: 'The component name',
|
||||
enum: Object.keys(COMPONENTS),
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['component'],
|
||||
type: 'object',
|
||||
},
|
||||
name: 'get_component_docs',
|
||||
},
|
||||
{
|
||||
description: 'Generate code example for a specific Oranguru component',
|
||||
inputSchema: {
|
||||
properties: {
|
||||
component: {
|
||||
description: 'The component name',
|
||||
enum: Object.keys(COMPONENTS),
|
||||
type: 'string',
|
||||
},
|
||||
variant: {
|
||||
description: "Example variant (e.g., 'basic', 'local', 'server')",
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
required: ['component'],
|
||||
type: 'object',
|
||||
},
|
||||
name: 'get_component_example',
|
||||
},
|
||||
{
|
||||
description: 'List all available Oranguru components',
|
||||
inputSchema: {
|
||||
properties: {},
|
||||
type: 'object',
|
||||
},
|
||||
name: 'list_components',
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
// Handle tool calls
|
||||
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const { arguments: args, name } = request.params;
|
||||
|
||||
switch (name) {
|
||||
case 'get_component_docs':
|
||||
if (!args.component || !COMPONENTS[args.component]) {
|
||||
throw new Error(`Component ${args.component} not found`);
|
||||
}
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
text: JSON.stringify(COMPONENTS[args.component], null, 2),
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
case 'get_component_example':
|
||||
if (!args.component || !COMPONENTS[args.component]) {
|
||||
throw new Error(`Component ${args.component} not found`);
|
||||
}
|
||||
const component = COMPONENTS[args.component];
|
||||
const variant = args.variant || Object.keys(component.examples)[0];
|
||||
const example = component.examples[variant];
|
||||
|
||||
if (!example) {
|
||||
throw new Error(`Variant ${variant} not found for ${args.component}`);
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
text: example,
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
case 'list_components':
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
text: JSON.stringify(
|
||||
Object.entries(COMPONENTS).map(([key, comp]) => ({
|
||||
description: comp.description,
|
||||
exports: comp.exports,
|
||||
name: key,
|
||||
})),
|
||||
null,
|
||||
2
|
||||
),
|
||||
type: 'text',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown tool: ${name}`);
|
||||
}
|
||||
});
|
||||
|
||||
// List resources
|
||||
this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
||||
resources: [
|
||||
{
|
||||
description: 'Main documentation for the Oranguru library',
|
||||
mimeType: 'text/markdown',
|
||||
name: 'Oranguru Documentation',
|
||||
uri: 'oranguru://docs/readme',
|
||||
},
|
||||
{
|
||||
description: 'List of all available components',
|
||||
mimeType: 'application/json',
|
||||
name: 'Component List',
|
||||
uri: 'oranguru://docs/components',
|
||||
},
|
||||
],
|
||||
}));
|
||||
|
||||
// Read resources
|
||||
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
const { uri } = request.params;
|
||||
|
||||
if (uri === 'oranguru://docs/readme') {
|
||||
const readmePath = join(__dirname, '..', 'README.md');
|
||||
const readme = readFileSync(readmePath, 'utf-8');
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
mimeType: 'text/markdown',
|
||||
text: readme,
|
||||
uri,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (uri === 'oranguru://docs/components') {
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
mimeType: 'application/json',
|
||||
text: JSON.stringify(
|
||||
Object.entries(COMPONENTS).map(([key, comp]) => ({
|
||||
description: comp.description,
|
||||
exports: comp.exports,
|
||||
name: key,
|
||||
})),
|
||||
null,
|
||||
2
|
||||
),
|
||||
uri,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error(`Resource not found: ${uri}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const server = new OranguruMCPServer();
|
||||
server.run().catch(console.error);
|
||||
Reference in New Issue
Block a user