diff --git a/dev-docs.json b/dev-docs.json new file mode 100644 index 0000000000..379223a96b --- /dev/null +++ b/dev-docs.json @@ -0,0 +1,3 @@ +{ + "aiProvider": "aws" +} diff --git a/docusaurus/docs/cms/admin-panel-customization/widgets.md b/docusaurus/docs/cms/admin-panel-customization/widgets.md new file mode 100644 index 0000000000..3307e151e3 --- /dev/null +++ b/docusaurus/docs/cms/admin-panel-customization/widgets.md @@ -0,0 +1,254 @@ +--- +title: Widgets API +description: Learn how to use the Widgets API to add custom widgets to the Strapi admin panel. +displayed_sidebar: cmsSidebar +sidebar_label: Widgets +toc_max_heading_level: 4 +tags: +- admin panel +- admin panel customization +- widgets +--- + +# Widgets API + +The Widgets API allows you to add custom widgets to specific areas of the Strapi admin panel. Widgets are reusable UI components that can display information, provide shortcuts to specific sections, or enhance the admin panel's functionality. + +## Overview + +Widgets are registered through a centralized `Widgets` class which manages their registration and retrieval. Each widget consists of a React component along with metadata like title, icon, and optional navigation link. + +The Widgets API is particularly useful when: +- You want to display dynamic information on the admin panel +- You need to provide quick access to specific features of your application +- You want to enhance the admin experience with custom UI elements + +## Registering widgets + +Widgets can be registered during a plugin's lifecycle, typically in the [`register()`](/cms/plugins-development/admin-panel-api#register) function. + +### Registration methods + +The `register()` method from the Widgets API accepts either a single widget configuration object or an array of widget configurations: + +```js +// Register a single widget +app.registerWidget({ + id: 'my-widget', + icon: MyWidgetIcon, + title: { id: 'my-widget.title', defaultMessage: 'My Widget' }, + component: () => import('./components/MyWidget'), +}); + +// Register multiple widgets +app.registerWidget([ + { + id: 'first-widget', + icon: FirstIcon, + title: { id: 'first-widget.title', defaultMessage: 'First Widget' }, + component: () => import('./components/FirstWidget'), + }, + { + id: 'second-widget', + icon: SecondIcon, + title: { id: 'second-widget.title', defaultMessage: 'Second Widget' }, + component: () => import('./components/SecondWidget'), + } +]); +``` + +## Widget configuration + +Each widget is defined by a configuration object with the following properties: + +| Property | Type | Description | +| ------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| `id` | String | **Required.** Unique identifier for the widget | +| `icon` | React.ComponentType | **Required.** React component used to display the widget's icon | +| `title` | MessageDescriptor | **Required.** Localized title using the [React-Intl](https://formatjs.io/docs/react-intl/) convention with `id` and `defaultMessage`| +| `component` | Function | **Required.** Function returning a Promise that resolves to a React component (usually with dynamic import) | +| `link` | Object | **Optional.** Navigation link configuration with `label` (MessageDescriptor) and `href` (To from react-router-dom) | +| `pluginId` | String | **Optional.** ID of the plugin providing the widget. This is used to generate the widget's UID | +| `permissions` | Array\ | **Optional.** Permissions required to display the widget | + +### The MessageDescriptor format + +The `title` and `link.label` properties use the MessageDescriptor format: + +```js +{ + id: 'namespace.for.your.translation', + defaultMessage: 'Default message if translation is not found' +} +``` + +## Code examples + +### Basic widget + +Here's an example of registering a basic widget: + +```jsx +import WidgetIcon from './components/WidgetIcon'; +import pluginId from './pluginId'; + +export default { + register(app) { + app.registerPlugin({ + id: pluginId, + name: 'My Plugin', + }); + + app.registerWidget({ + id: 'my-dashboard-widget', + icon: WidgetIcon, + title: { + id: `${pluginId}.widget.dashboard.title`, + defaultMessage: 'Dashboard Overview', + }, + component: async () => { + const component = await import( + /* webpackChunkName: "dashboard-widget" */ './components/DashboardWidget' + ); + + return component; + }, + }); + }, + + bootstrap() {}, +}; +``` + +### Widget with navigation link + +You can add a navigation link to your widget: + +```jsx +app.registerWidget({ + id: 'analytics-widget', + icon: AnalyticsIcon, + title: { + id: 'analytics.widget.title', + defaultMessage: 'Analytics', + }, + link: { + label: { + id: 'analytics.widget.link', + defaultMessage: 'View full analytics', + }, + href: '/plugins/analytics/dashboard', + }, + component: () => import('./components/AnalyticsWidget'), +}); +``` + +### Widget with permissions + +You can restrict widget visibility based on user permissions: + +```jsx +app.registerWidget({ + id: 'restricted-widget', + icon: RestrictedIcon, + title: { + id: 'restricted.widget.title', + defaultMessage: 'Admin Stats', + }, + permissions: [ + { action: 'admin::stats.read', subject: null } + ], + component: () => import('./components/AdminStatsWidget'), +}); +``` + +## Accessing registered widgets + +Within your plugin code, you can access all registered widgets using the `getAll()` method: + +```jsx +const allWidgets = app.getWidgets().getAll(); +``` + +This returns an array of all registered widgets with their configurations, which can be useful if you want to render widgets in a custom layout or create a widget dashboard. + +## Creating a widget component + +When creating the actual widget component, you should follow these guidelines: + +1. Design your widget to be responsive and work well within the admin panel layout +2. Use the Strapi design system for UI components to maintain consistency +3. Handle loading states and potential error cases +4. Keep the widget focused on a specific task or piece of information + +Here's a simple example of a widget component: + +```jsx +// DashboardWidget.jsx +import React, { useState, useEffect } from 'react'; +import { Box, Typography, Loader } from '@strapi/design-system'; + +const DashboardWidget = () => { + const [data, setData] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchData = async () => { + try { + // Fetch your data here + const response = await fetch('/api/stats'); + const result = await response.json(); + setData(result); + } catch (error) { + console.error(error); + } finally { + setIsLoading(false); + } + }; + + fetchData(); + }, []); + + if (isLoading) { + return ( + + Loading content... + + ); + } + + return ( + + Dashboard Statistics + {/* Render your widget content using the data */} + + ); +}; + +export default DashboardWidget; +``` + +## TypeScript support + +If you're using TypeScript, you can leverage the provided types from the Widgets API: + +```tsx +import type { WidgetArgs } from '@strapi/admin/admin/src/core/apis/Widgets'; +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + register(app: StrapiApp) { + const myWidget: WidgetArgs = { + id: 'typescript-widget', + icon: TypescriptIcon, + title: { + id: 'typescript.widget.title', + defaultMessage: 'TypeScript Widget', + }, + component: () => import('./components/TypeScriptWidget'), + }; + + app.registerWidget(myWidget); + }, +}; +``` \ No newline at end of file diff --git a/docusaurus/docs/cms/configurations/features.md b/docusaurus/docs/cms/configurations/features.md index 7863ec8bc0..2475eae9bb 100644 --- a/docusaurus/docs/cms/configurations/features.md +++ b/docusaurus/docs/cms/configurations/features.md @@ -4,10 +4,10 @@ sidebar_label: Features description: Enable experimental Strapi features displayed_sidebar: cmsSidebar tags: -- additional configuration -- configuration -- features configuration -- future flag + - additional configuration + - configuration + - features configuration + - future flag --- # Features configuration @@ -107,10 +107,8 @@ Developers can use the following APIs to interact with future flags: ## Available future flags -There are currently no available future flags. This section will be updated once new experimental features are available for testing. - - +| `unstableWidgetsApi` | Widget system API for the admin panel | `STRAPI_FUTURE_UNSTABLE_WIDGETS_API` | diff --git a/docusaurus/docs/cms/plugins-development/admin-panel-api.md b/docusaurus/docs/cms/plugins-development/admin-panel-api.md index da734d82e6..c488993023 100644 --- a/docusaurus/docs/cms/plugins-development/admin-panel-api.md +++ b/docusaurus/docs/cms/plugins-development/admin-panel-api.md @@ -4,20 +4,20 @@ pagination_prev: cms/plugins-development/plugin-structure pagination_next: cms/plugins-development/content-manager-apis toc_max_heading_level: 4 tags: -- admin panel -- plugin APIs -- asynchronous function -- bootstrap function -- hooks API -- Injection Zones API -- lifecycle function -- menu -- settings -- plugins -- plugins development -- register function -- reducers API -- redux + - admin panel + - plugin APIs + - asynchronous function + - bootstrap function + - hooks API + - Injection Zones API + - lifecycle function + - menu + - settings + - plugins + - plugins development + - register function + - reducers API + - redux --- # Admin Panel API for plugins @@ -66,6 +66,7 @@ Within the register function, a plugin can: * [create a new settings section](#createsettingsection) * define [injection zones](#injection-zones-api) * [add reducers](#reducers-api) +* [register widgets](#widgets-api) #### registerPlugin() @@ -198,6 +199,7 @@ The Admin Panel API allows a plugin to take advantage of several small APIs to p | Declare an injection zone | [Injection Zones API](#injection-zones-api) | [`registerPlugin()`](#registerplugin) | [`register()`](#register) | | Add a reducer | [Reducers API](#reducers-api) | [`addReducers()`](#reducers-api) | [`register()`](#register) | | Create a hook | [Hooks API](#hooks-api) | [`createHook()`](#hooks-api) | [`register()`](#register) | +| Register widgets | [Widgets API](#widgets-api) | [`register()`](#widgets-api) | [`register()`](#register) | | Add a single link to a settings section | [Settings API](#settings-api) | [`addSettingsLink()`](#addsettingslink) | [`bootstrap()`](#bootstrap) | | Add multiple links to a settings section | [Settings API](#settings-api) | [`addSettingsLinks()`](#addsettingslinks) | [`bootstrap()`](#bootstrap) | | Inject a Component in an injection zone | [Injection Zones API](#injection-zones-api) | [`injectComponent()`](#injection-zones-api) | [`bootstrap()`](#register) | @@ -776,3 +778,123 @@ interface LayoutSettings extends Contracts.ContentTypes.Settings { :::note `EditViewLayout` and `ListViewLayout` are parts of the `useDocumentLayout` hook (see ). ::: + +### Widgets API + +The Widgets API allows plugins to register custom widget components that can be displayed on the dashboard or other areas of the admin panel. These widgets can display various types of data, provide quick actions, or serve as informative panels. + +To register a widget, use the `app.widgets.register()` method during the [register](#register) lifecycle. + +**Example:** + + + + +```jsx title="my-plugin/admin/src/index.js" +import WidgetIcon from './components/WidgetIcon'; + +export default { + register(app) { + app.widgets.register({ + // Unique identifier for the widget + id: 'my-widget', + // Icon to display + icon: WidgetIcon, + // Widget title + title: { + id: 'my-plugin.widget.title', + defaultMessage: 'My Widget', + }, + // Optional link displayed on the widget + link: { + label: { + id: 'my-plugin.widget.link.label', + defaultMessage: 'View Details', + }, + href: '/plugins/my-plugin/details', + }, + // The widget component (loaded asynchronously) + component: async () => { + const component = await import('./components/MyWidget'); + return component; + }, + // Optional permissions to restrict widget visibility + permissions: [], + }); + + // Or register multiple widgets at once + /* + app.widgets.register([ + { + id: 'widget-1', + // ... + }, + { + id: 'widget-2', + // ... + } + ]); + */ + } +}; +``` + + + + +```tsx title="my-plugin/admin/src/index.ts" +import WidgetIcon from './components/WidgetIcon'; +import type { StrapiApp } from '@strapi/admin/strapi-admin'; + +export default { + register(app: StrapiApp) { + app.widgets.register({ + // Unique identifier for the widget + id: 'my-widget', + // Icon to display + icon: WidgetIcon, + // Widget title + title: { + id: 'my-plugin.widget.title', + defaultMessage: 'My Widget', + }, + // Optional link displayed on the widget + link: { + label: { + id: 'my-plugin.widget.link.label', + defaultMessage: 'View Details', + }, + href: '/plugins/my-plugin/details', + }, + // The widget component (loaded asynchronously) + component: async () => { + const component = await import('./components/MyWidget'); + return component; + }, + // Optional permissions to restrict widget visibility + permissions: [], + }); + } +}; +``` + + + + +#### Widget Configuration + +The `register()` method accepts the following parameters: + +| Parameter | Type | Description | Required | +| ------------- | ------------------------ | -------------------------------------------------------------------- | -------- | +| `id` | String | Unique identifier for the widget | Yes | +| `icon` | React.ComponentType | Icon component displayed in the widget | Yes | +| `title` | MessageDescriptor | Title of the widget with internationalization support | Yes | +| `component` | Async Function | Async function that returns the widget component | Yes | +| `link` | Object | Optional link configuration with `label` and `href` properties | No | +| `permissions` | Array of Permission | Optional permissions to control widget visibility | No | +| `pluginId` | String | Plugin ID if the widget is part of a plugin (automatically handled) | No | + +:::note +When registering a widget, an internal UID is generated based on the plugin ID (if provided) or marked as a global widget. This ensures widgets have unique identifiers throughout the system. +:::