{"id":22131970,"url":"https://github.com/seaofvoices/react-roblox-studio-plugin","last_synced_at":"2025-07-25T19:32:33.398Z","repository":{"id":221347225,"uuid":"752807130","full_name":"seaofvoices/react-roblox-studio-plugin","owner":"seaofvoices","description":"React component library to create Roblox plugins","archived":false,"fork":false,"pushed_at":"2024-08-30T18:28:21.000Z","size":31,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-10-30T07:51:48.384Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/seaofvoices.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["jeparlefrancais"],"ko_fi":"seaofvoices"}},"created_at":"2024-02-04T21:10:08.000Z","updated_at":"2024-08-30T18:27:36.000Z","dependencies_parsed_at":"2024-02-07T14:20:16.418Z","dependency_job_id":"2aa20f60-c317-4246-9404-bf8df57036cc","html_url":"https://github.com/seaofvoices/react-roblox-studio-plugin","commit_stats":{"total_commits":7,"total_committers":2,"mean_commits":3.5,"dds":0.1428571428571429,"last_synced_commit":"c7c94b61a2668d6b2eed082b23c5b6458be0eb65"},"previous_names":["seaofvoices/react-roblox-studio-plugin"],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seaofvoices%2Freact-roblox-studio-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seaofvoices%2Freact-roblox-studio-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seaofvoices%2Freact-roblox-studio-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/seaofvoices%2Freact-roblox-studio-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/seaofvoices","download_url":"https://codeload.github.com/seaofvoices/react-roblox-studio-plugin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227613799,"owners_count":17793927,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-01T18:38:41.548Z","updated_at":"2024-12-01T18:38:42.203Z","avatar_url":"https://github.com/seaofvoices.png","language":"Lua","funding_links":["https://github.com/sponsors/jeparlefrancais","https://ko-fi.com/seaofvoices"],"categories":["Components","React"],"sub_categories":["React-Roblox Studio Plugin \u003cimg src=\"luau.svg\" width=\"18px\" /\u003e"],"readme":"\u003cdiv align=\"center\"\u003e\n\n[![checks](https://github.com/seaofvoices/react-roblox-studio-plugin/actions/workflows/test.yml/badge.svg)](https://github.com/seaofvoices/react-roblox-studio-plugin/actions/workflows/test.yml)\n![version](https://img.shields.io/github/package-json/v/seaofvoices/react-roblox-studio-plugin)\n[![GitHub top language](https://img.shields.io/github/languages/top/seaofvoices/react-roblox-studio-plugin)](https://github.com/luau-lang/luau)\n![license](https://img.shields.io/npm/l/@seaofvoices/react-roblox-studio-plugin)\n![npm](https://img.shields.io/npm/dt/@seaofvoices/react-roblox-studio-plugin)\n\n[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/seaofvoices)\n\n\u003c/div\u003e\n\n# react-roblox-studio-plugin\n\nA component library to create Roblox Studio plugins using [react-lua](https://github.com/jsdotlua/react-lua).\n\n- [Components](#components)\n  - [Action](#action)\n  - [Menu](#menu)\n  - [RobloxPlugin](#robloxplugin)\n  - [Toolbar](#toolbar)\n  - [ToolbarButton](#toolbarbutton)\n  - [Widget](#widget)\n- [Hooks](#hooks)\n  - [usePlugin](#useplugin)\n  - [useWidgetKeyDown](#usewidgetkeydown)\n  - [useWidgetFocus](#usewidgetfocus)\n  - [useWidgetFocusLost](#usewidgetfocuslost)\n\n## Installation\n\nAdd `@seaofvoices/react-roblox-studio-plugin` in your dependencies:\n\n```bash\nyarn add @seaofvoices/react-roblox-studio-plugin\n```\n\nOr if you are using `npm`:\n\n```bash\nnpm install @seaofvoices/react-roblox-studio-plugin\n```\n\n## Components\n\n- [Action](#action)\n- [Menu](#menu)\n- [RobloxPlugin](#robloxplugin)\n- [Toolbar](#toolbar)\n- [ToolbarButton](#toolbarbutton)\n- [Widget](#widget)\n\n### Action\n\nThe `Action` component creates and manages a [`PluginAction`](https://create.roblox.com/docs/reference/engine/classes/PluginAction). It can be used both under a [Menu](#menu) element and independently.\n\nWhen used under a [Menu](#menu), the action will be attached to the managed [`PluginMenu`](https://create.roblox.com/docs/reference/engine/classes/PluginMenu).\n\n#### Props\n\n```lua\ntype Props = {\n    triggered: () -\u003e (),\n    id: string,\n    label: string,\n    description: string,\n    icon: string?,\n    allowBinding: boolean?,\n}\n```\n\n- `triggered`: Callback function to be called when the action is triggered.\n- `id`: Unique identifier for the action.\n- `label`: The text displayed for the action in menus or toolbars.\n- `description`: A longer description of what the action does.\n- `icon`: Optional asset ID for the action's icon.\n- `allowBinding`: Optional boolean to allow key binding for the action (default: true).\n\n#### Example\n\n```lua\nlocal React = require('@pkg/@jsdotlua/react')\nlocal ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')\nlocal Action = ReactRobloxStudioPlugin.Action\n\nlocal function CustomAction()\n    return React.createElement(Action, {\n        id = \"MyAction\",\n        label = \"Action\",\n        description = \"Performs action\",\n        triggered = function()\n            print(\"Action triggered!\")\n        end\n    })\nend\n```\n\n### Menu\n\nThe `Menu` component creates and manages a [`PluginMenu`](https://create.roblox.com/docs/reference/engine/classes/PluginMenu). When `Action` elements are created under a menu, they automatically get attached to the closest `Menu`.\n\n#### Props\n\n```lua\ntype Props = {\n    id: string,\n    title: string,\n    icon: string?,\n    openMenu: Signal\u003c()\u003e,\n}\n```\n\n- `id`: Unique identifier for the menu.\n- `title`: The text displayed for the menu.\n- `icon`: Optional asset ID for the menu's icon.\n- `openMenu`: A Signal from [`luau-signal`](https://github.com/seaofvoices/luau-signal) that, when fired, will open the menu.\n\n#### Example\n\nThis example creates a menu with one action. The menu can be opened manually by firing the `openMenuSignal`, which is connected to toggling a generated BoolValue.\n\n```lua\nlocal Signal = require('@pkg/luau-signal')\nlocal useConstant = require('@pkg/@seaofvoices/react-lua-use-constant')\nlocal ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')\nlocal Menu = ReactRobloxStudioPlugin.Menu\nlocal Action = ReactRobloxStudioPlugin.Action\n\nlocal function MyPluginMenu()\n    local openMenuSignal = useConstant(function()\n        return Signal.new()\n    end)\n\n    React.useEffect(function()\n        local toggle = Instance.new(\"BoolValue\")\n        toggle.Name = \"TogglePluginMenu\"\n        toggle.Parent = workspace\n\n        local connection = toggle.Changed:Connect(function()\n            if toggle.Value then\n                toggle.Value = false\n                openMenuSignal:fire()\n            end\n        end)\n\n        return function()\n            connection:Disconnect()\n            toggle.Parent = nil\n        end\n    end, {openMenuSignal})\n\n    return React.createElement(\n        Menu,\n        {\n            id = \"MyPluginMenu\",\n            title = \"My Plugin\",\n            icon = \"rbxassetid://1234567\",\n            openMenu = openMenuSignal,\n        },\n        React.createElement(Action, {\n            id = \"Action\",\n            label = \"Do Something\",\n            description = \"Performs an action\",\n            triggered = function()\n                print(\"Action triggered!\")\n            end\n        })\n    )\nend\n```\n\n### RobloxPlugin\n\nThe `RobloxPlugin` component is a crucial wrapper component that provides the Roblox Studio plugin context to its children. It should be used at the root of your plugin's React element tree.\n\n#### Props\n\n```luau\ntype Props = {\n    plugin: Plugin,\n}\n```\n\n- `plugin`: The Roblox Studio [`Plugin`](https://create.roblox.com/docs/reference/engine/classes/Plugin) instance that your plugin code receives.\n\n#### Example\n\n```lua\nlocal React = require('@pkg/@jsdotlua/react')\nlocal ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')\nlocal RobloxPlugin = ReactRobloxStudioPlugin.RobloxPlugin\nlocal Toolbar = ReactRobloxStudioPlugin.Toolbar\n\nlocal function MyPluginContent()\n    return React.createElement(Toolbar, {\n        name = \"My Plugin Toolbar\"\n    }, {\n        -- Toolbar buttons and other content\n    })\nend\n\nlocal function InitializePlugin(plugin)\n    return React.createElement(\n        RobloxPlugin,\n        { plugin = plugin },\n        React.createElement(MyPluginContent)\n    )\nend\n\nreturn InitializePlugin\n```\n\n### Toolbar\n\nThe `Toolbar` component creates and manages a [`PluginToolbar`](https://create.roblox.com/docs/reference/engine/classes/PluginToolbar). It provides a context for its children to access the toolbar instance.\n\n#### Props\n\n```lua\ntype Props = {\n    name: string,\n}\n```\n\n- `name`: The name of the toolbar to be created.\n\n#### Example\n\nIn this example, `MyPluginToolbar` creates a toolbar named \"My Custom Toolbar\" and adds a button to it. The `ToolbarButton` component can access the toolbar instance through the context provided by `Toolbar`.\n\n```lua\nlocal React = require('@pkg/@jsdotlua/react')\nlocal ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')\nlocal Toolbar = ReactRobloxStudioPlugin.Toolbar\nlocal ToolbarButton = ReactRobloxStudioPlugin.ToolbarButton\n\nlocal function MyPluginToolbar()\n    return React.createElement(\n        Toolbar,\n        { name = \"My Custom Toolbar\" },\n        React.createElement(ToolbarButton, {\n            icon = \"rbxassetid://1234567\",\n            tooltip = \"Click me!\",\n            onClick = function()\n                print(\"Button clicked!\")\n            end\n        })\n    )\nend\n```\n\n### ToolbarButton\n\nThe `ToolbarButton` component creates and manages a [`PluginToolbarButton`](https://create.roblox.com/docs/reference/engine/classes/PluginToolbarButton) within a Roblox Studio plugin toolbar.\n\n#### Props\n\n```lua\ntype Props = {\n    onClick: (() -\u003e ())?,\n    id: string,\n    toolTip: string?,\n    iconAsset: string?,\n    text: string?,\n    active: boolean?,\n}\n```\n\n- `onClick`: Optional callback function to be called when the button is clicked. If not provided, the button is automatically disabled.\n- `id`: Unique identifier for the button.\n- `toolTip`: Optional tooltip text to display when hovering over the button.\n- `iconAsset`: Optional asset ID for the button's icon.\n- `text`: Optional text to display on the button.\n- `active`: Optional boolean to set the button's active state.\n\n#### Example\n\nIn this example, `MyPluginToolbar` creates a toolbar with a toggle button. The button's active state is managed by React state and updated when clicked.\n\n```lua\nlocal React = require('@pkg/@jsdotlua/react')\nlocal ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')\nlocal Toolbar = ReactRobloxStudioPlugin.Toolbar\nlocal ToolbarButton = ReactRobloxStudioPlugin.ToolbarButton\n\nlocal function MyPluginToolbar()\n    return React.createElement(\n        Toolbar,\n        { name = \"My Custom Toolbar\" },\n        React.createElement(ToolbarButton, {\n            id = \"welcome-button\",\n            toolTip = \"Welcome!\",\n            onClick = function()\n                print(\"Hello!\")\n            end\n        })\n    )\nend\n```\n\n### Widget\n\nThe `Widget` component creates and manages a [`DockWidgetPluginGui`](https://create.roblox.com/docs/reference/engine/classes/DockWidgetPluginGui). It provides a flexible way to create custom widget interfaces for your plugin.\n\n#### Props\n\n```lua\ntype Props = {\n    id: string,\n    title: string,\n    enabled: boolean?,\n    initialDockState: Enum.InitialDockState?,\n    minSize: Vector2?,\n    initialFloatSize: Vector2?,\n    zIndexBehavior: Enum.ZIndexBehavior?,\n    onClose: () -\u003e ()?,\n    onFocusLost: () -\u003e ()?,\n    onFocused: () -\u003e ()?,\n    onSizeChange: (size: Vector2) -\u003e ()?,\n}\n```\n\n- `id`: Unique identifier for the widget.\n- `title`: The title displayed on the widget's title bar.\n- `enabled`: Optional boolean to set the widget's visibility (default: `true`).\n- `initialDockState`: Optional initial docking state (default: `Enum.InitialDockState.Float`).\n- `minSize`: Optional minimum size of the widget.\n- `initialFloatSize`: Optional initial size when floating.\n- `zIndexBehavior`: Optional Z-index behavior (default: `Enum.ZIndexBehavior.Sibling`).\n- `onClose`: Optional callback function when the widget is closed.\n- `onFocusLost`: Optional callback function when the widget loses focus.\n- `onFocused`: Optional callback function when the widget gains focus.\n- `onSizeChange`: Optional callback function when the widget size changes.\n\n#### Example\n\nThis example creates a widget that docks to the left side of the Studio window, with a minimum size and custom behavior for closing and focusing. The widget content is a simple text label.\n\n```lua\nlocal React = require('@pkg/@jsdotlua/react')\nlocal ReactRobloxStudioPlugin = require('@pkg/@seaofvoices/react-roblox-studio-plugin')\nlocal Widget = ReactRobloxStudioPlugin.Widget\n\nlocal function MyPluginWidget()\n    return React.createElement(\n        Widget,\n        {\n            id = \"MyPluginWidget\",\n            title = \"My Plugin Widget\",\n            initialDockState = Enum.InitialDockState.Left,\n            minSize = Vector2.new(200, 300),\n            onFocused = function()\n                print(\"Widget focused\")\n            end,\n        },\n        React.createElement(\"TextLabel\", {\n            Text = \"Hello, Roblox Studio!\",\n            Size = UDim2.new(1, 0, 0, 30),\n        })\n    )\nend\n```\n\n## Hooks\n\n- [usePlugin](#useplugin)\n- [useWidgetKeyDown](#usewidgetkeydown)\n- [useWidgetFocus](#usewidgetfocus)\n- [useWidgetFocusLost](#usewidgetfocuslost)\n\n### usePlugin\n\n```luau\nfunction usePlugin(): Plugin\n```\n\nThe `usePlugin` hook provides access to the current Roblox Studio `plugin` variable. It returns the `Plugin` object, allowing you to interact with the plugin API.\n\n**Example:**\n\n```luau\nlocal plugin = usePlugin()\n\nplugin:PromptSaveSelection()\n```\n\n### useWidgetKeyDown\n\n```luau\nfunction useWidgetKeyDown(onKeyDown: (Enum.KeyCode) -\u003e ())\n```\n\nThe `useWidgetKeyDown` hook sets up a listener for key press events in the widget. It takes a callback function as an argument, which is called with the `Enum.KeyCode` of the pressed key.\n\nNote that this hook must be used under a Widget element.\n\n### useWidgetFocus\n\n```luau\nfunction useWidgetFocused(onFocused: () -\u003e ())\n```\n\nThe `useWidgetFocus` hook sets up a listener for when the widget gains focus. It takes a callback function as an argument, which is called when the widget becomes focused.\n\nNote that this hook must be used under a Widget element.\n\n### useWidgetFocusLost\n\n```luau\nfunction useWidgetFocusLost(onFocusLost: () -\u003e ())\n```\n\nThe `useWidgetFocusLost` hook sets up a listener for when the widget loses focus. It takes a callback function as an argument, which is called when the widget loses focus.\n\nNote that this hook must be used under a Widget element.\n\n**Example:**\n\n```luau\nuseWidgetFocusLost(function()\n    print(\"Widget lost focus!\")\nend)\n```\n\n## License\n\nThis project is available under the MIT license. See [LICENSE.txt](LICENSE.txt) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseaofvoices%2Freact-roblox-studio-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fseaofvoices%2Freact-roblox-studio-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fseaofvoices%2Freact-roblox-studio-plugin/lists"}