feat: Update plugin manager integration with lucide-react icons and improve RoomInput component logic
This commit is contained in:
@@ -105,6 +105,7 @@ interface PluginContext {
|
|||||||
pluginId: string;
|
pluginId: string;
|
||||||
matrixClient: MatrixClient;
|
matrixClient: MatrixClient;
|
||||||
React: typeof import('react');
|
React: typeof import('react');
|
||||||
|
lucide: typeof import('lucide-react');
|
||||||
|
|
||||||
commands: CommandsAPI;
|
commands: CommandsAPI;
|
||||||
messages: MessagesAPI;
|
messages: MessagesAPI;
|
||||||
@@ -416,6 +417,8 @@ interface UIButtonDefinition {
|
|||||||
location: UILocation;
|
location: UILocation;
|
||||||
label: string;
|
label: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
/** Lucide icon name, e.g. `ImagePlay`. Also supported via `icon: 'lucide:ImagePlay'`. */
|
||||||
|
lucideIcon?: string;
|
||||||
position?: UIButtonPosition;
|
position?: UIButtonPosition;
|
||||||
onClick?: () => void | Promise<void>;
|
onClick?: () => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|||||||
25
package-lock.json
generated
25
package-lock.json
generated
@@ -13,7 +13,7 @@
|
|||||||
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
|
"@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0",
|
||||||
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3",
|
||||||
"@fontsource/inter": "4.5.14",
|
"@fontsource/inter": "4.5.14",
|
||||||
"@paarrot/plugin-manager": "file:../../PluginManager",
|
"@paarrot/plugin-manager": "git+http://synbox.ruv.wtf:8418/litruv/plugin-manager.git",
|
||||||
"@tanstack/react-query": "5.24.1",
|
"@tanstack/react-query": "5.24.1",
|
||||||
"@tanstack/react-query-devtools": "5.24.1",
|
"@tanstack/react-query-devtools": "5.24.1",
|
||||||
"@tanstack/react-virtual": "3.2.0",
|
"@tanstack/react-virtual": "3.2.0",
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
"linkifyjs": "4.1.3",
|
"linkifyjs": "4.1.3",
|
||||||
"livekit-client": "2.17.0",
|
"livekit-client": "2.17.0",
|
||||||
"lottie-web": "5.13.0",
|
"lottie-web": "5.13.0",
|
||||||
|
"lucide-react": "0.511.0",
|
||||||
"matrix-js-sdk": "38.2.0",
|
"matrix-js-sdk": "38.2.0",
|
||||||
"millify": "6.1.0",
|
"millify": "6.1.0",
|
||||||
"pako": "2.1.0",
|
"pako": "2.1.0",
|
||||||
@@ -114,15 +115,6 @@
|
|||||||
"node": ">=16.0.0"
|
"node": ">=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"../../PluginManager": {
|
|
||||||
"name": "@paarrot/plugin-manager",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^25.6.0",
|
|
||||||
"rimraf": "^5.0.0",
|
|
||||||
"typescript": "^5.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@ampproject/remapping": {
|
"node_modules/@ampproject/remapping": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||||
@@ -2344,8 +2336,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@paarrot/plugin-manager": {
|
"node_modules/@paarrot/plugin-manager": {
|
||||||
"resolved": "../../PluginManager",
|
"version": "1.0.0",
|
||||||
"link": true
|
"resolved": "git+http://synbox.ruv.wtf:8418/litruv/plugin-manager.git#6c389381df3966568a40c39d8661f9a4300f7819"
|
||||||
},
|
},
|
||||||
"node_modules/@react-aria/breadcrumbs": {
|
"node_modules/@react-aria/breadcrumbs": {
|
||||||
"version": "3.5.20",
|
"version": "3.5.20",
|
||||||
@@ -8762,6 +8754,15 @@
|
|||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lucide-react": {
|
||||||
|
"version": "0.511.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz",
|
||||||
|
"integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==",
|
||||||
|
"license": "ISC",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.27.0",
|
"version": "0.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
|
||||||
|
|||||||
@@ -67,6 +67,7 @@
|
|||||||
"linkifyjs": "4.1.3",
|
"linkifyjs": "4.1.3",
|
||||||
"livekit-client": "2.17.0",
|
"livekit-client": "2.17.0",
|
||||||
"lottie-web": "5.13.0",
|
"lottie-web": "5.13.0",
|
||||||
|
"lucide-react": "0.511.0",
|
||||||
"matrix-js-sdk": "38.2.0",
|
"matrix-js-sdk": "38.2.0",
|
||||||
"millify": "6.1.0",
|
"millify": "6.1.0",
|
||||||
"pako": "2.1.0",
|
"pako": "2.1.0",
|
||||||
|
|||||||
@@ -107,7 +107,6 @@ import { Command, SHRUG, TABLEFLIP, UNFLIP, useCommands } from '../../hooks/useC
|
|||||||
import { pluginRegistry } from '../settings/plugins/PluginAPI';
|
import { pluginRegistry } from '../settings/plugins/PluginAPI';
|
||||||
import { PluginButtonSlot } from '../settings/plugins/PluginButtonSlot';
|
import { PluginButtonSlot } from '../settings/plugins/PluginButtonSlot';
|
||||||
import { mobileOrTablet } from '../../utils/user-agent';
|
import { mobileOrTablet } from '../../utils/user-agent';
|
||||||
import { useElementSizeObserver } from '../../hooks/useElementSizeObserver';
|
|
||||||
import { ReplyLayout, ThreadIndicator } from '../../components/message';
|
import { ReplyLayout, ThreadIndicator } from '../../components/message';
|
||||||
import { roomToParentsAtom } from '../../state/room/roomToParents';
|
import { roomToParentsAtom } from '../../state/room/roomToParents';
|
||||||
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
|
||||||
@@ -246,15 +245,9 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
const pickFile = useFilePicker(handleFiles, true);
|
const pickFile = useFilePicker(handleFiles, true);
|
||||||
const handlePaste = useFilePasteHandler(handleFiles);
|
const handlePaste = useFilePasteHandler(handleFiles);
|
||||||
const dropZoneVisible = useFileDropZone(fileDropContainerRef, handleFiles);
|
const dropZoneVisible = useFileDropZone(fileDropContainerRef, handleFiles);
|
||||||
const [hideStickerBtn, setHideStickerBtn] = useState(document.body.clientWidth < 500);
|
|
||||||
|
|
||||||
const isComposing = useComposingCheck();
|
const isComposing = useComposingCheck();
|
||||||
|
|
||||||
useElementSizeObserver(
|
|
||||||
useCallback(() => document.body, []),
|
|
||||||
useCallback((width) => setHideStickerBtn(width < 500), [])
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Transforms.insertFragment(editor, msgDraft);
|
Transforms.insertFragment(editor, msgDraft);
|
||||||
}, [editor, msgDraft]);
|
}, [editor, msgDraft]);
|
||||||
@@ -804,25 +797,9 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{!hideStickerBtn && (
|
|
||||||
<IconButton
|
|
||||||
aria-pressed={emojiBoardTab === EmojiBoardTab.Sticker}
|
|
||||||
onClick={() => setEmojiBoardTab(EmojiBoardTab.Sticker)}
|
|
||||||
variant="SurfaceVariant"
|
|
||||||
size="300"
|
|
||||||
radii="300"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
src={Icons.Sticker}
|
|
||||||
filled={emojiBoardTab === EmojiBoardTab.Sticker}
|
|
||||||
/>
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
<IconButton
|
<IconButton
|
||||||
ref={emojiBtnRef}
|
ref={emojiBtnRef}
|
||||||
aria-pressed={
|
aria-pressed={emojiBoardTab === EmojiBoardTab.Emoji}
|
||||||
hideStickerBtn ? !!emojiBoardTab : emojiBoardTab === EmojiBoardTab.Emoji
|
|
||||||
}
|
|
||||||
onClick={() => setEmojiBoardTab(EmojiBoardTab.Emoji)}
|
onClick={() => setEmojiBoardTab(EmojiBoardTab.Emoji)}
|
||||||
variant="SurfaceVariant"
|
variant="SurfaceVariant"
|
||||||
size="300"
|
size="300"
|
||||||
@@ -830,9 +807,7 @@ export const RoomInput = forwardRef<HTMLDivElement, RoomInputProps>(
|
|||||||
>
|
>
|
||||||
<Icon
|
<Icon
|
||||||
src={Icons.Smile}
|
src={Icons.Smile}
|
||||||
filled={
|
filled={emojiBoardTab === EmojiBoardTab.Emoji}
|
||||||
hideStickerBtn ? !!emojiBoardTab : emojiBoardTab === EmojiBoardTab.Emoji
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</IconButton>
|
</IconButton>
|
||||||
</PopOut>
|
</PopOut>
|
||||||
|
|||||||
@@ -149,6 +149,16 @@ interface MessageContext {
|
|||||||
|
|
||||||
### 🎨 UI API
|
### 🎨 UI API
|
||||||
|
|
||||||
|
Plugins also receive `ctx.lucide` — the full [lucide-react](https://lucide.dev/) icon set for custom renderers and UI.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { Link, Search, X } = ctx.lucide;
|
||||||
|
|
||||||
|
ctx.ui.registerRenderer('message', (msg, defaultRenderer) => {
|
||||||
|
return ctx.React.createElement(Link, { size: 16 });
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
Register custom renderers:
|
Register custom renderers:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useSyncExternalStore } from 'react';
|
import React, { useSyncExternalStore } from 'react';
|
||||||
import { IconButton } from 'folds';
|
import { IconButton } from 'folds';
|
||||||
|
import * as lucide from 'lucide-react';
|
||||||
import { UILocation } from '@paarrot/plugin-manager';
|
import { UILocation } from '@paarrot/plugin-manager';
|
||||||
import { pluginRegistry } from './PluginAPI';
|
import { pluginRegistry } from './PluginAPI';
|
||||||
|
|
||||||
@@ -38,6 +39,35 @@ interface PluginButtonSlotProps {
|
|||||||
location: UILocation;
|
location: UILocation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LucideIconComponent = React.ComponentType<{
|
||||||
|
size?: number | string;
|
||||||
|
strokeWidth?: number | string;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function renderPluginButtonIcon(button: {
|
||||||
|
icon?: string;
|
||||||
|
lucideIcon?: string;
|
||||||
|
}): React.ReactNode {
|
||||||
|
const lucideName =
|
||||||
|
button.lucideIcon ??
|
||||||
|
(typeof button.icon === 'string' && button.icon.startsWith('lucide:')
|
||||||
|
? button.icon.slice('lucide:'.length)
|
||||||
|
: undefined);
|
||||||
|
|
||||||
|
if (lucideName) {
|
||||||
|
const IconComponent = (lucide as Record<string, LucideIconComponent | undefined>)[lucideName];
|
||||||
|
if (IconComponent) {
|
||||||
|
return <IconComponent size={18} strokeWidth={2} aria-hidden />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span aria-hidden style={{ fontSize: '1.1em', lineHeight: 1 }}>
|
||||||
|
{button.icon ?? '🔌'}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders all plugin-registered buttons for the given UI location.
|
* Renders all plugin-registered buttons for the given UI location.
|
||||||
* Correctly handles components that mount before OR after plugins load,
|
* Correctly handles components that mount before OR after plugins load,
|
||||||
@@ -61,9 +91,7 @@ export function PluginButtonSlot({ location }: PluginButtonSlotProps): React.Rea
|
|||||||
title={button.label}
|
title={button.label}
|
||||||
onClick={() => button.onClick?.()}
|
onClick={() => button.onClick?.()}
|
||||||
>
|
>
|
||||||
<span aria-hidden style={{ fontSize: '1.1em', lineHeight: 1 }}>
|
{renderPluginButtonIcon(button)}
|
||||||
{button.icon ?? '🔌'}
|
|
||||||
</span>
|
|
||||||
</IconButton>
|
</IconButton>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { MatrixClient } from 'matrix-js-sdk';
|
import { MatrixClient } from 'matrix-js-sdk';
|
||||||
|
import * as lucide from 'lucide-react';
|
||||||
import { Plugin, createPluginContext } from '@paarrot/plugin-manager';
|
import { Plugin, createPluginContext } from '@paarrot/plugin-manager';
|
||||||
import { pluginMarketplaceManager, pluginRegistry } from './PluginAPI';
|
import { pluginMarketplaceManager, pluginRegistry } from './PluginAPI';
|
||||||
import { sendNotification } from '../../../utils/tauri';
|
import { sendNotification } from '../../../utils/tauri';
|
||||||
@@ -79,6 +80,7 @@ export function PluginLoader({ matrixClient, children }: PluginLoaderProps) {
|
|||||||
matrixClient.off(eventType as any, handler as any),
|
matrixClient.off(eventType as any, handler as any),
|
||||||
},
|
},
|
||||||
React,
|
React,
|
||||||
|
lucide,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[PluginLoader] compatContext.React:', !!compatContext.React);
|
console.log('[PluginLoader] compatContext.React:', !!compatContext.React);
|
||||||
|
|||||||
Reference in New Issue
Block a user