diff --git a/docs/frontend/components.md b/docs/frontend/components.md
index 13d8771794..01037d339d 100644
--- a/docs/frontend/components.md
+++ b/docs/frontend/components.md
@@ -47,13 +47,13 @@ if (file_exists($script_assets)) {
For `Dokan Free`, we can import the components via `@/components`:
```js
-import { DataViews, useWindowDimensions } from '@/components';
+import { DataViews } from '@/components';
```
In `Dokan Pro`, components can be imported directly from `@dokan/components`:
```js
-import { DataViews, useWindowDimensions } from '@dokan/components';
+import { DataViews } from '@dokan/components';
```
For external `plugins`, we must include the `dokan-react-components` as scripts dependency and the `@dokan/components` should be introduced as an external resource configuration to resolve the path via `webpack`:
diff --git a/docs/frontend/hooks.md b/docs/frontend/hooks.md
new file mode 100644
index 0000000000..ef3220cd7c
--- /dev/null
+++ b/docs/frontend/hooks.md
@@ -0,0 +1,90 @@
+# Dokan Hooks
+
+## Overview
+
+`Dokan` provides a set of reusable `hooks` that can be used across both `Free` and `Pro` versions. This documentation explains how to properly set up and use `hooks` in your project.
+
+## Important Dependencies
+
+For both `Dokan Free and Pro` versions, we must register the `dokan-react-components` dependency when using `global` components.
+
+### Implementation Example
+
+```php
+// Register scripts with dokan-react-components dependency
+$script_assets = 'add_your_script_assets_path_here';
+
+if (file_exists($script_assets)) {
+ $vendor_asset = require $script_assets;
+ $version = $vendor_asset['version'] ?? '';
+
+ // Add dokan-react-components as a dependency
+ $component_handle = 'dokan-react-components';
+ $dependencies = $vendor_asset['dependencies'] ?? [];
+ $dependencies[] = $component_handle;
+
+ // Register Script
+ wp_register_script(
+ 'handler-name',
+ 'path_to_your_script_file',
+ $dependencies,
+ $version,
+ true
+ );
+
+ // Register Style
+ wp_register_style(
+ 'handler-name',
+ 'path_to_your_style_file',
+ [ $component_handle ],
+ $version
+ );
+}
+```
+
+## Component Access
+
+For `Dokan Free`, we can import the components via `@/hooks`:
+
+```js
+import { useWindowDimensions } from '@/hooks';
+```
+
+In `Dokan Pro`, components can be imported directly from `@dokan/components`:
+
+```js
+import { useWindowDimensions } from '@dokan/hooks';
+```
+
+For external `plugins`, we must include the `dokan-react-components` as scripts dependency and the `@dokan/hooks` should be introduced as an external resource configuration to resolve the path via `webpack`:
+
+```js
+externals: {
+ '@dokan/hooks': 'dokan.hooks',
+ ...
+},
+```
+
+## Adding Global Components
+
+### File Structure
+
+```
+|____ src/
+| |___ hooks/
+| | |___ index.tsx # Main export file
+| | |___ ViewportDimensions.tsx # Existing hook
+| | |___ YourHook # Your new hook
+| | |
+| | |___ Other Files
+| |
+| |___ Other Files
+|
+|____ Other Files
+```
+
+**Finally,** we need to export the new `hook` from the `src/hooks/index.tsx` file. Then, we can import the new component from `@dokan/hooks` in `dokan pro` version.
+
+```tsx
+export { default as useWindowDimensions } from '@/hooks/ViewportDimensions';
+```
diff --git a/includes/Assets.php b/includes/Assets.php
index 86ee765d08..cd2fe96b62 100644
--- a/includes/Assets.php
+++ b/includes/Assets.php
@@ -587,17 +587,25 @@ public function get_scripts() {
'src' => $asset_url . '/js/utilities.js',
'version' => filemtime( $asset_path . 'js/utilities.js' ),
],
+ 'dokan-hooks' => [
+ 'deps' => [],
+ 'src' => $asset_url . '/js/hooks.js',
+ 'version' => filemtime( $asset_path . 'js/hooks.js' ),
+ ],
];
- $components_asset_dir = DOKAN_DIR . '/assets/js/components.asset.php';
- if ( file_exists( $components_asset_dir ) ) {
- $components_asset = require $components_asset_dir;
- $components_asset['dependencies'][] = 'dokan-utilities';
+ $components_asset_file = DOKAN_DIR . '/assets/js/components.asset.php';
+ if ( file_exists( $components_asset_file ) ) {
+ $components_asset = require $components_asset_file;
+ // Register React components.
$scripts['dokan-react-components'] = [
- 'src' => $asset_url . '/js/components.js',
- 'deps' => $components_asset['dependencies'],
'version' => $components_asset['version'],
+ 'src' => $asset_url . '/js/components.js',
+ 'deps' => array_merge(
+ $components_asset['dependencies'],
+ [ 'dokan-utilities', 'dokan-hooks' ]
+ ),
];
}
diff --git a/includes/functions-dashboard-navigation.php b/includes/functions-dashboard-navigation.php
index 97301b5af5..ceb0f5d29a 100644
--- a/includes/functions-dashboard-navigation.php
+++ b/includes/functions-dashboard-navigation.php
@@ -259,10 +259,12 @@ function dokan_dashboard_nav( $active_menu = '' ) {
}
$submenu .= sprintf(
- '
',
+ /* translators: 1) submenu class, 2) submenu route, 3) submenu icon, 4) submenu title */
+ '',
$submenu_class,
- isset( $sub['url'] ) ? $sub['url'] : dokan_get_navigation_url( "{$key}/{$sub_key}" ),
- isset( $sub['icon'] ) ? $sub['icon'] : '',
+ $sub['react_route'] ?? '',
+ $sub['url'] ?? dokan_get_navigation_url( "{$key}/{$sub_key}" ),
+ $sub['icon'] ?? '',
apply_filters( 'dokan_vendor_dashboard_menu_title', $submenu_title, $sub )
);
@@ -278,11 +280,13 @@ function dokan_dashboard_nav( $active_menu = '' ) {
}
$menu .= sprintf(
- '%s %s%s',
+ /* translators: 1) menu class, 2) menu route, 3) menu url, 4) menu target, 5) menu icon, 6) menu title, 7) submenu */
+ '%5$s %6$s%7$s',
$class,
- isset( $item['url'] ) ? $item['url'] : dokan_get_navigation_url( $menu_slug ),
- isset( $item['target'] ) ? $item['target'] : '_self',
- isset( $item['icon'] ) ? $item['icon'] : '',
+ $item['react_route'] ?? '',
+ $item['url'] ?? dokan_get_navigation_url( $menu_slug ),
+ $item['target'] ?? '_self',
+ $item['icon'] ?? '',
apply_filters( 'dokan_vendor_dashboard_menu_title', $title, $item ),
$submenu
);
diff --git a/src/components/dataviews/DataViewTable.tsx b/src/components/dataviews/DataViewTable.tsx
index 672c4d3da9..bca4c0cd06 100644
--- a/src/components/dataviews/DataViewTable.tsx
+++ b/src/components/dataviews/DataViewTable.tsx
@@ -3,9 +3,8 @@ import { Slot } from "@wordpress/components";
import { ViewportDimensions } from '@/Hooks/ViewportDimensions';
import type { Action, Field, SupportedLayouts, View } from "@wordpress/dataviews/src/types";
import { kebabCase, snakeCase } from "@/utilities";
-import { useWindowDimensions } from "@/components";
import { useEffect } from "@wordpress/element";
-import type { ReactNode } from "react";
+import { useWindowDimensions } from "@/hooks";
import './style.scss';
type ItemWithId = { id: string };
@@ -31,7 +30,7 @@ type DataViewsProps< Item > = {
onChangeSelection?: ( items: string[] ) => void;
onClickItem?: ( item: Item ) => void;
isItemClickable?: ( item: Item ) => boolean;
- header?: ReactNode;
+ header?: JSX.Element;
} & ( Item extends ItemWithId
? { getItemId?: ( item: Item ) => string }
: { getItemId: ( item: Item ) => string } );
diff --git a/src/components/index.tsx b/src/components/index.tsx
index 0c86eab8a0..73eb471cf3 100644
--- a/src/components/index.tsx
+++ b/src/components/index.tsx
@@ -1,5 +1,4 @@
export { default as DataViews } from './dataviews/DataViewTable';
-export { default as useWindowDimensions } from '@/hooks/ViewportDimensions';
export {
DataForm,
diff --git a/src/hooks/ViewportDimensions.tsx b/src/hooks/ViewportDimensions.tsx
new file mode 100644
index 0000000000..ccfad55898
--- /dev/null
+++ b/src/hooks/ViewportDimensions.tsx
@@ -0,0 +1,46 @@
+import { useState, useEffect, useCallback } from '@wordpress/element';
+
+interface ViewportDimensions {
+ width: number | null;
+ height: number | null;
+}
+
+/**
+ * Hook to track viewport dimensions.
+ *
+ * @since DOKAN_PRO_SINCE
+ *
+ * @return {ViewportDimensions} The viewport dimensions.
+ */
+export default function useWindowDimensions() {
+ const getViewportDimensions = useCallback((): ViewportDimensions => ({
+ width: typeof window !== 'undefined' ? window.innerWidth : null,
+ height: typeof window !== 'undefined' ? window.innerHeight : null,
+ }), []);
+
+ const [viewport, setViewport] = useState(getViewportDimensions());
+
+ useEffect(() => {
+ if (typeof window === 'undefined') {
+ return;
+ }
+
+ const handleResize = () => {
+ // Use requestAnimationFrame to throttle updates
+ window.requestAnimationFrame(() => {
+ setViewport(getViewportDimensions());
+ });
+ };
+
+ window.addEventListener('resize', handleResize);
+
+ // Initial measurement after mount
+ handleResize();
+
+ return () => {
+ window.removeEventListener('resize', handleResize);
+ };
+ }, [getViewportDimensions]);
+
+ return viewport;
+};
diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx
new file mode 100644
index 0000000000..1f34f92ccb
--- /dev/null
+++ b/src/hooks/index.tsx
@@ -0,0 +1 @@
+export { default as useWindowDimensions } from '@/hooks/ViewportDimensions';
diff --git a/src/layout/index.tsx b/src/layout/index.tsx
index 0de5d0ed26..21b4d068a4 100644
--- a/src/layout/index.tsx
+++ b/src/layout/index.tsx
@@ -7,6 +7,7 @@ import {
} from '@wordpress/components';
import { PluginArea } from '@wordpress/plugins';
import { DokanToaster } from "@getdokan/dokan-ui";
+import { useLocation } from 'react-router-dom';
// Create a ThemeContext
const ThemeContext = createContext( null );
@@ -43,6 +44,34 @@ interface LayoutProps {
footerComponent?: JSX.Element|React.ReactNode;
}
+const handleMenuActiveStates = ( currentPath ) => {
+ const menuRoute = currentPath.replace( /^\//, '' ); // Remove leading slash.
+ const menuItem = document.querySelector( `.dokan-dashboard-menu li[data-react-route='${ menuRoute }']` ) || null;
+
+ // Return if menu item not found.
+ if ( ! menuItem ) {
+ return;
+ }
+
+ document.querySelectorAll( '.dokan-dashboard-menu li' ).forEach( item => {
+ item.classList.remove( 'active' );
+ item.querySelectorAll( '.navigation-submenu li' ).forEach( subItem => {
+ subItem.classList.remove( 'current' );
+ });
+ });
+
+ // Get parent menu item if this is a submenu item.
+ const parentMenuItem = menuItem.closest( '.dokan-dashboard-menu > li' );
+ if ( parentMenuItem ) { // Add `active` to parent menu.
+ parentMenuItem.classList.add( 'active' );
+ }
+
+ const subMenuItem = document.querySelector( `.navigation-submenu li[data-react-route='${ menuRoute }']` );
+ if ( subMenuItem ) { // Add `current` to submenu item.
+ subMenuItem.classList.add( 'current' );
+ }
+};
+
// Create a Layout component that uses the ThemeProvider
const Layout = ( {
children,
@@ -51,6 +80,9 @@ const Layout = ( {
headerComponent,
footerComponent,
}: LayoutProps ) => {
+ const location = useLocation(); // Use the location hook to get the current path.
+ handleMenuActiveStates( location?.pathname );
+
return (
diff --git a/webpack.config.js b/webpack.config.js
index c13fefa66a..ed05acfb39 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -15,6 +15,9 @@ const updatedConfig = {
'utilities': {
import: '@/utilities/index.ts',
},
+ 'hooks': {
+ import: '@/hooks/index.tsx',
+ },
},
output: {
path: path.resolve(__dirname, './assets/js'),