From 1f71fd7a5116014636ccaf4d431e2ed42736c8c4 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer Date: Mon, 6 Jul 2026 01:30:25 +1000 Subject: [PATCH] feat: Update plugin manager integration with lucide-react icons and improve RoomInput component logic --- docs/PLUGIN_API.md | 3 ++ package-lock.json | 25 +++++++------- package.json | 1 + src/app/features/room/RoomInput.tsx | 29 ++-------------- .../settings/plugins/PLUGIN_DEVELOPMENT.md | 10 ++++++ .../settings/plugins/PluginButtonSlot.tsx | 34 +++++++++++++++++-- .../settings/plugins/PluginLoader.tsx | 2 ++ 7 files changed, 62 insertions(+), 42 deletions(-) diff --git a/docs/PLUGIN_API.md b/docs/PLUGIN_API.md index b1d89c4..7c8764b 100644 --- a/docs/PLUGIN_API.md +++ b/docs/PLUGIN_API.md @@ -105,6 +105,7 @@ interface PluginContext { pluginId: string; matrixClient: MatrixClient; React: typeof import('react'); + lucide: typeof import('lucide-react'); commands: CommandsAPI; messages: MessagesAPI; @@ -416,6 +417,8 @@ interface UIButtonDefinition { location: UILocation; label: string; icon?: string; + /** Lucide icon name, e.g. `ImagePlay`. Also supported via `icon: 'lucide:ImagePlay'`. */ + lucideIcon?: string; position?: UIButtonPosition; onClick?: () => void | Promise; } diff --git a/package-lock.json b/package-lock.json index df80177..53ccc4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@atlaskit/pragmatic-drag-and-drop-auto-scroll": "1.3.0", "@atlaskit/pragmatic-drag-and-drop-hitbox": "1.0.3", "@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-devtools": "5.24.1", "@tanstack/react-virtual": "3.2.0", @@ -56,6 +56,7 @@ "linkifyjs": "4.1.3", "livekit-client": "2.17.0", "lottie-web": "5.13.0", + "lucide-react": "0.511.0", "matrix-js-sdk": "38.2.0", "millify": "6.1.0", "pako": "2.1.0", @@ -114,15 +115,6 @@ "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": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", @@ -2344,8 +2336,8 @@ } }, "node_modules/@paarrot/plugin-manager": { - "resolved": "../../PluginManager", - "link": true + "version": "1.0.0", + "resolved": "git+http://synbox.ruv.wtf:8418/litruv/plugin-manager.git#6c389381df3966568a40c39d8661f9a4300f7819" }, "node_modules/@react-aria/breadcrumbs": { "version": "3.5.20", @@ -8762,6 +8754,15 @@ "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": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", diff --git a/package.json b/package.json index ce876f3..4022ee1 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "linkifyjs": "4.1.3", "livekit-client": "2.17.0", "lottie-web": "5.13.0", + "lucide-react": "0.511.0", "matrix-js-sdk": "38.2.0", "millify": "6.1.0", "pako": "2.1.0", diff --git a/src/app/features/room/RoomInput.tsx b/src/app/features/room/RoomInput.tsx index 4df0af9..d58dd6e 100644 --- a/src/app/features/room/RoomInput.tsx +++ b/src/app/features/room/RoomInput.tsx @@ -107,7 +107,6 @@ import { Command, SHRUG, TABLEFLIP, UNFLIP, useCommands } from '../../hooks/useC import { pluginRegistry } from '../settings/plugins/PluginAPI'; import { PluginButtonSlot } from '../settings/plugins/PluginButtonSlot'; import { mobileOrTablet } from '../../utils/user-agent'; -import { useElementSizeObserver } from '../../hooks/useElementSizeObserver'; import { ReplyLayout, ThreadIndicator } from '../../components/message'; import { roomToParentsAtom } from '../../state/room/roomToParents'; import { useMediaAuthentication } from '../../hooks/useMediaAuthentication'; @@ -246,15 +245,9 @@ export const RoomInput = forwardRef( const pickFile = useFilePicker(handleFiles, true); const handlePaste = useFilePasteHandler(handleFiles); const dropZoneVisible = useFileDropZone(fileDropContainerRef, handleFiles); - const [hideStickerBtn, setHideStickerBtn] = useState(document.body.clientWidth < 500); const isComposing = useComposingCheck(); - useElementSizeObserver( - useCallback(() => document.body, []), - useCallback((width) => setHideStickerBtn(width < 500), []) - ); - useEffect(() => { Transforms.insertFragment(editor, msgDraft); }, [editor, msgDraft]); @@ -804,25 +797,9 @@ export const RoomInput = forwardRef( /> } > - {!hideStickerBtn && ( - setEmojiBoardTab(EmojiBoardTab.Sticker)} - variant="SurfaceVariant" - size="300" - radii="300" - > - - - )} setEmojiBoardTab(EmojiBoardTab.Emoji)} variant="SurfaceVariant" size="300" @@ -830,9 +807,7 @@ export const RoomInput = forwardRef( > diff --git a/src/app/features/settings/plugins/PLUGIN_DEVELOPMENT.md b/src/app/features/settings/plugins/PLUGIN_DEVELOPMENT.md index 18ff2bf..5bc983a 100644 --- a/src/app/features/settings/plugins/PLUGIN_DEVELOPMENT.md +++ b/src/app/features/settings/plugins/PLUGIN_DEVELOPMENT.md @@ -149,6 +149,16 @@ interface MessageContext { ### 🎨 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: ```javascript diff --git a/src/app/features/settings/plugins/PluginButtonSlot.tsx b/src/app/features/settings/plugins/PluginButtonSlot.tsx index 0c3757a..28a3425 100644 --- a/src/app/features/settings/plugins/PluginButtonSlot.tsx +++ b/src/app/features/settings/plugins/PluginButtonSlot.tsx @@ -1,5 +1,6 @@ import React, { useSyncExternalStore } from 'react'; import { IconButton } from 'folds'; +import * as lucide from 'lucide-react'; import { UILocation } from '@paarrot/plugin-manager'; import { pluginRegistry } from './PluginAPI'; @@ -38,6 +39,35 @@ interface PluginButtonSlotProps { 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)[lucideName]; + if (IconComponent) { + return ; + } + } + + return ( + + {button.icon ?? '🔌'} + + ); +} + /** * Renders all plugin-registered buttons for the given UI location. * Correctly handles components that mount before OR after plugins load, @@ -61,9 +91,7 @@ export function PluginButtonSlot({ location }: PluginButtonSlotProps): React.Rea title={button.label} onClick={() => button.onClick?.()} > - - {button.icon ?? '🔌'} - + {renderPluginButtonIcon(button)} ))} diff --git a/src/app/features/settings/plugins/PluginLoader.tsx b/src/app/features/settings/plugins/PluginLoader.tsx index cce2dd3..9564da2 100644 --- a/src/app/features/settings/plugins/PluginLoader.tsx +++ b/src/app/features/settings/plugins/PluginLoader.tsx @@ -1,5 +1,6 @@ import React, { useEffect } from 'react'; import { MatrixClient } from 'matrix-js-sdk'; +import * as lucide from 'lucide-react'; import { Plugin, createPluginContext } from '@paarrot/plugin-manager'; import { pluginMarketplaceManager, pluginRegistry } from './PluginAPI'; import { sendNotification } from '../../../utils/tauri'; @@ -79,6 +80,7 @@ export function PluginLoader({ matrixClient, children }: PluginLoaderProps) { matrixClient.off(eventType as any, handler as any), }, React, + lucide, }); console.log('[PluginLoader] compatContext.React:', !!compatContext.React);