mirror of
https://github.com/forcedotcom/afv-library.git
synced 2026-07-30 19:50:42 +08:00
* feat: removing old webapp skills * feat: adding sync of skills from webapps to afv * feat: adding the first iteration of skills * feat: pin template deps to latest npm versions and flatten skill folders - Add pin-template-deps.js to resolve "*" deps to exact npm versions - Integrate pinning into sync-template-skills npm script - Remove check-template-skills-versions.js (no longer needed) - Simplify workflow to single sync step - Flatten skill output: one folder per skill with cleaned names Made-with: Cursor * fix: resolve skill validation errors - Move .template-versions.json from skills/ to root - Shorten skill names to meet 64-char limit: - salesforce-webapp-feature-micro-frontend-generating-micro-frontend-lwc → salesforce-webapp-micro-frontend-lwc - salesforce-webapp-feature-react-agentforce-conversation-client-integrating-agentforce-conversation-client → salesforce-webapp-agentforce-conversation-client - salesforce-webapp-feature-react-file-upload-implementing-file-upload → salesforce-webapp-react-file-upload - Expand descriptions to meet 20-word minimum with trigger context * Add webapp skills from template, sync script updates - Rename skill folders from salesforce-webapp-* to *-webapp-* convention - Update sync-template-skills.js: set SKILL.md front matter name to dest folder - Remove sync-template-skills workflow and pin-template-deps script - Add .synced-template-skills.json manifest, deploying-webapp-to-salesforce skill - Replace salesforce-webapp-designing-webapp-ui-ux with designing-webapp-ui-ux Made-with: Cursor * Align SKILL.md front matter name with folder for all webapp skills Made-with: Cursor * Fix skill validation: description length and trigger context for configuring-webapp-metadata, creating-webapp Made-with: Cursor * Rename sync script to sync-webapp-skills, drop manifest file - Rename sync-template-skills.js to sync-webapp-skills.js - Update package.json script to sync-webapp-skills - Remove .synced-template-skills.json creation and add to .gitignore Made-with: Cursor * Revert sync-react-b2e-sample and sync-react-b2x-sample to upstream version Made-with: Cursor * Sync script: pin b2e and b2x to latest, sync skills from template - Pin both template packages to latest in sync-webapp-skills.js - Update package.json / package-lock.json (b2x 1.109.0) - Sync skills: managing-webapp-agentforce-conversation-client, bar-line-chart, remove building-webapp-analytics-charts and integrating-webapp-agentforce-conversation-client - Minor skill content updates Made-with: Cursor * Remove interactive map, weather widget, and Unsplash skills (no longer in template) Made-with: Cursor --------- Co-authored-by: Hemant Singh Bisht <hsinghbisht@salesforce.com>
5.1 KiB
5.1 KiB
Dashboard Layout — Implementation Guide
Anatomy of a dashboard page
A typical dashboard combines stat cards, charts, and data tables:
┌────────────────────────────────────────────────────┐
│ Search / global action bar │
├──────────┬──────────┬──────────────────────────────┤
│ Stat 1 │ Stat 2 │ Stat 3 │
├──────────┴──────────┴──────┬───────────────────────┤
│ │ │
│ Data table / list │ Donut chart │
│ (70% width) │ (30% width) │
│ │ │
└────────────────────────────┴───────────────────────┘
Layout implementation
import { PageContainer } from "@/components/layout/PageContainer";
import { StatCard } from "@/components/StatCard";
import { DonutChart } from "@/components/DonutChart";
export default function Dashboard() {
return (
<PageContainer>
<div className="max-w-7xl mx-auto space-y-6">
{/* Search bar */}
<div>{/* global search component */}</div>
{/* Main content: 70/30 split */}
<div className="grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6">
<div className="space-y-6">
{/* Stat cards row */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<StatCard title="Metric A" value={42} />
<StatCard title="Metric B" value={18} />
<StatCard title="Metric C" value={7} />
</div>
{/* Data table */}
<div>{/* table component */}</div>
</div>
{/* Sidebar chart */}
<div>
<DonutChart title="Distribution" data={chartData} />
</div>
</div>
</div>
</PageContainer>
);
}
Responsive behavior
| Breakpoint | Layout |
|---|---|
Mobile (< 768px) |
Single column, everything stacked |
Tablet (md) |
Stat cards in 3-col grid, rest stacked |
Desktop (lg) |
70/30 split for table + chart |
Key Tailwind classes:
grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6
grid grid-cols-1 md:grid-cols-3 gap-6
Loading state
Show a full-page loading state while dashboard data is being fetched:
if (loading) {
return (
<PageContainer>
<div className="flex items-center justify-center min-h-[400px]">
<p className="text-muted-foreground">Loading dashboard…</p>
</div>
</PageContainer>
);
}
Or use a skeleton layout:
if (loading) {
return (
<PageContainer>
<div className="max-w-7xl mx-auto space-y-6">
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[1, 2, 3].map((i) => (
<div key={i} className="h-28 animate-pulse rounded-xl bg-muted" />
))}
</div>
<div className="grid grid-cols-1 lg:grid-cols-[70%_30%] gap-6">
<div className="h-64 animate-pulse rounded-xl bg-muted" />
<div className="h-64 animate-pulse rounded-xl bg-muted" />
</div>
</div>
</PageContainer>
);
}
Data fetching pattern
Use useEffect with cancellation for dashboard metrics:
const [metrics, setMetrics] = useState<Metrics | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
(async () => {
try {
setLoading(true);
const data = await fetchDashboardMetrics();
if (!cancelled) setMetrics(data);
} catch (error) {
if (!cancelled) console.error("Error loading metrics:", error);
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => { cancelled = true; };
}, []);
Combining multiple data sources
Dashboards often aggregate data from several APIs. Load them in parallel:
const [metrics, setMetrics] = useState<Metrics | null>(null);
const [requests, setRequests] = useState<Request[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
Promise.all([fetchMetrics(), fetchRecentRequests()])
.then(([metricsData, requestsData]) => {
if (!cancelled) {
setMetrics(metricsData);
setRequests(requestsData);
}
})
.catch((err) => {
if (!cancelled) console.error(err);
})
.finally(() => {
if (!cancelled) setLoading(false);
});
return () => { cancelled = true; };
}, []);
PageContainer wrapper
A simple wrapper for consistent page padding:
interface PageContainerProps {
children: React.ReactNode;
}
export function PageContainer({ children }: PageContainerProps) {
return <div className="p-6">{children}</div>;
}