Widgets
Build dashboard components with stats, charts, and data visualizations in your Hewcode application.
Getting Started
Create widgets to display on your dashboard:
use Hewcode\Hewcode\Props;
use Hewcode\Hewcode\Widgets;
use Hewcode\Hewcode\Lists;
class DashboardController extends Controller
{
public function index(): Response
{
return Inertia::render('dashboard/index', Props\Props::for($this)->components(['widgets']));
}
#[Widgets\Expose]
public function widgets(): Widgets\Widgets
{
return Widgets\Widgets::make([
Widgets\StatsWidget::make('total_users')
->label('Total Users')
->value(User::count())
->icon('lucide-users'),
])->columns(2)->visible(auth()->check());
}
}DANGER
Always use visibility checks to ensure widgets are only shown to authorized users. Authorization middleware alone is not sufficient.
On the frontend, render the widgets:
import Widgets from '@hewcode/react/components/widgets/Widgets';
import { usePage } from '@inertiajs/react';
export default function Dashboard() {
const { widgets } = usePage().props;
return (
<div>
<Widgets {...widgets} />
</div>
);
}Widget Types
Hewcode provides five widget types for different use cases.
Stats Widget
Display key metrics with optional trends and icons:
use Hewcode\Hewcode\Support\Enums\Color;
Widgets\StatsWidget::make('revenue')
->label('Monthly Revenue')
->value(45230)
->format('currency')
->icon('lucide-dollar-sign')
->color(Color::SUCCESS)
->description('Total revenue this month')
->trend([
'value' => 12,
'direction' => 'up',
'label' => 'from last month',
]);Formats:
currency- Formats as currency (e.g., $45,230.00)number- Formats with thousand separators (e.g., 45,230)percentage- Formats as percentage (e.g., 45.23%)
List Widget
Display a simple list of items:
Widgets\ListWidget::make('recent_activity')
->label('Recent Activity')
->items([
'New user registered: John Doe',
'Post published: Getting Started',
'Comment added on: Laravel Tips',
])
->icon('lucide-activity');Card Widget
Display custom HTML content with optional actions:
Widgets\CardWidget::make('welcome')
->label('Welcome Back')
->content('<p>You have 5 new notifications.</p>')
->actions([
Actions\Action::make('view_all')
->label('View All')
->url(route('notifications.index')),
]);Listing Widget
Embed a data table within a widget using the existing Lists component:
Widgets\ListingWidget::make('recent_posts')
->label('Recent Posts')
->listing(fn (Lists\Listing $listing) => $listing
->query(Post::query()->latest()->take(5))
->columns([
Lists\Schema\TextColumn::make('title'),
Lists\Schema\TextColumn::make('created_at')
->formatStateUsing(fn ($state) => $state->diffForHumans()),
])
->filters([])
->bulkActions([])
)
->compact(true);The compact() method reduces padding for a tighter layout within the widget.
Chart Widget
Visualize data with interactive charts powered by Recharts:
Widgets\ChartWidget::make('sales_chart')
->label('Sales Overview')
->chartType('area') // line, bar, area, pie
->data([
['name' => 'Jan', 'sales' => 4000],
['name' => 'Feb', 'sales' => 3000],
['name' => 'Mar', 'sales' => 5000],
['name' => 'Apr', 'sales' => 4500],
])
->height(300)
->options([
'xAxisKey' => 'name',
'areas' => [
['dataKey' => 'sales', 'name' => 'Sales'],
],
]);Chart Types:
Line Chart:
->chartType('line')
->options([
'xAxisKey' => 'month',
'lines' => [
['dataKey' => 'revenue', 'name' => 'Revenue'],
['dataKey' => 'expenses', 'name' => 'Expenses'],
],
])Bar Chart:
->chartType('bar')
->options([
'xAxisKey' => 'month',
'bars' => [
['dataKey' => 'sales', 'name' => 'Sales'],
],
])Area Chart:
->chartType('area')
->options([
'xAxisKey' => 'date',
'areas' => [
['dataKey' => 'users', 'name' => 'Active Users'],
],
])Pie Chart:
->chartType('pie')
->data([
['name' => 'Product A', 'value' => 400],
['name' => 'Product B', 'value' => 300],
['name' => 'Product C', 'value' => 200],
])
->options([
'dataKey' => 'value',
])Layout
Control widget grid layout with the columns() method:
Widgets\Widgets::make([
$this->statsWidget1(),
$this->statsWidget2(),
$this->chartWidget(),
$this->listingWidget(),
])->columns(2); // 1, 2, 3, or 4 columnsThe grid is responsive and automatically adjusts for mobile screens.
Common Patterns
Dynamic Values
Use closures to compute values dynamically:
Widgets\StatsWidget::make('active_users')
->label('Active Users')
->valueUsing(fn () => User::where('last_login', '>', now()->subHour())->count());Conditional Visibility
Show widgets based on permissions or conditions:
Widgets\StatsWidget::make('admin_stats')
->label('Admin Statistics')
->value(100)
->visible(auth()->user()?->isAdmin() ?? false);Refresh Intervals
Auto-refresh widget data at specified intervals:
Widgets\StatsWidget::make('live_visitors')
->label('Live Visitors')
->valueUsing(fn () => Cache::get('active_visitors', 0))
->refreshInterval(30); // secondsWhen refreshInterval() is set, the widget will automatically poll the backend at the specified interval and update its data without refreshing the page. This is perfect for real-time dashboards showing live metrics.
How it works:
- The frontend polls the
/_hewcodeendpoint with agetWidgetcall - The backend re-evaluates the widget's data (closures are re-executed)
- Fresh data is merged into the widget without a full page reload
- Polling only occurs when the widget has a
refreshIntervalset
Best practices:
- Use closures with
->valueUsing()for dynamic data that changes - Set reasonable intervals (e.g., 30 seconds or more) to avoid excessive requests
- Consider caching expensive queries that are polled frequently
Custom Widgets
Create custom widgets with your own React components for specialized visualizations or functionality.
Creating a Custom Widget
Step 1: Create the backend widget class extending CustomWidget:
// app/Widgets/MyCustomWidget.php
namespace App\Widgets;
use Hewcode\Hewcode\Widgets\CustomWidget;
class MyCustomWidget extends CustomWidget
{
protected string $customData = '';
public function customData(string $data): static
{
$this->customData = $data;
return $this;
}
public function toData(): array
{
return array_merge(parent::toData(), [
'customData' => $this->customData,
]);
}
}Step 2: Create the React component at resources/js/hewcode/widgets/{ClassName}.jsx:
// resources/js/hewcode/widgets/MyCustomWidget.jsx
import React from 'react';
export default function MyCustomWidget({ customData, label, seal }) {
return (
<div className="bg-box border-box-border rounded-lg border shadow-sm p-6">
{label && (
<div className="px-6 py-4 border-b border-box-border -mx-6 -mt-6 mb-6">
<h3 className="text-lg font-semibold text-foreground">{label}</h3>
</div>
)}
<div>
{/* Your custom widget UI */}
<p>{customData}</p>
</div>
</div>
);
}Step 3: Use it in your controller:
use App\Widgets\MyCustomWidget;
#[Widgets\Expose]
public function widgets(): Widgets\Widgets
{
return Widgets\Widgets::make([
MyCustomWidget::make('my_custom')
->label('My Custom Widget')
->customData('Hello, world!'),
]);
}How It Works
- Custom widgets extend
CustomWidgetwhich automatically sets the type to the class name - The frontend looks for a matching React component at
resources/js/hewcode/widgets/{ClassName}.jsxor.tsx - Components are lazy-loaded for optimal performance
- All base widget props (label, seal, refreshInterval) are automatically available
Props Available
Your custom React component receives:
- All custom props from
toData() label- The widget labelseal- Component seal for making authenticated requestsname- The widget namerefreshInterval- Optional polling interval
TypeScript Support
You can also use TypeScript:
// resources/js/hewcode/widgets/MyCustomWidget.tsx
interface MyCustomWidgetProps {
customData: string;
label?: string;
seal?: any;
}
export default function MyCustomWidget({ customData, label, seal }: MyCustomWidgetProps) {
// ...
}Theming
Widget colors automatically adapt to your application's light and dark themes using CSS variables. Chart colors use the following variables:
--chart-data-1through--chart-data-5for data series colors- Standard theme variables (
--border,--foreground, etc.) for UI elements
You can customize these in your globals.css file:
:root {
--chart-data-1: oklch(0.6 0.12 220);
--chart-data-2: oklch(0.65 0.1 180);
/* ... */
}