@salesforce/afv-skills 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +5 -4
- package/skills/accessing-webapp-data/SKILL.md +178 -0
- package/skills/building-webapp-data-visualization/SKILL.md +72 -0
- package/skills/building-webapp-data-visualization/implementation/bar-line-chart.md +316 -0
- package/skills/building-webapp-data-visualization/implementation/dashboard-layout.md +189 -0
- package/skills/building-webapp-data-visualization/implementation/donut-chart.md +181 -0
- package/skills/building-webapp-data-visualization/implementation/stat-card.md +150 -0
- package/skills/building-webapp-react-components/SKILL.md +96 -0
- package/skills/building-webapp-react-components/implementation/component.md +78 -0
- package/skills/building-webapp-react-components/implementation/header-footer.md +132 -0
- package/skills/building-webapp-react-components/implementation/page.md +93 -0
- package/skills/configuring-webapp-csp-trusted-sites/SKILL.md +90 -0
- package/skills/configuring-webapp-csp-trusted-sites/implementation/metadata-format.md +281 -0
- package/skills/configuring-webapp-metadata/SKILL.md +158 -0
- package/skills/creating-webapp/SKILL.md +141 -0
- package/skills/deploying-webapp-to-salesforce/SKILL.md +229 -0
- package/skills/exploring-webapp-graphql-schema/SKILL.md +149 -0
- package/skills/fetching-webapp-rest-api/SKILL.md +167 -0
- package/skills/{salesforce-custom-application → generating-custom-application}/SKILL.md +1 -2
- package/skills/{salesforce-custom-field → generating-custom-field}/SKILL.md +1 -1
- package/skills/{salesforce-custom-lightning-type → generating-custom-lightning-type}/SKILL.md +36 -2
- package/skills/{salesforce-custom-object → generating-custom-object}/SKILL.md +1 -1
- package/skills/{salesforce-custom-tab → generating-custom-tab}/SKILL.md +1 -1
- package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/SKILL.md +1 -1
- package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/docs/handle-ui-components.md +1 -1
- package/skills/generating-experience-react-site/SKILL.md +67 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-custom-site.md +41 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience-bundle.md +17 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience-config.md +21 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-digital-experience.md +38 -0
- package/skills/generating-experience-react-site/docs/configure-metadata-network.md +72 -0
- package/skills/{salesforce-flexipage → generating-flexipage}/SKILL.md +86 -9
- package/skills/{salesforce-flow → generating-flow}/SKILL.md +1 -1
- package/skills/{salesforce-fragment → generating-fragment}/SKILL.md +1 -1
- package/skills/generating-lightning-app/SKILL.md +423 -0
- package/skills/{salesforce-list-view → generating-list-view}/SKILL.md +1 -1
- package/skills/{generate-permission-set → generating-permission-set}/SKILL.md +1 -1
- package/skills/{salesforce-validation-rule → generating-validation-rule}/SKILL.md +1 -1
- package/skills/generating-webapp-graphql-mutation-query/SKILL.md +258 -0
- package/skills/generating-webapp-graphql-read-query/SKILL.md +253 -0
- package/skills/implementing-webapp-file-upload/SKILL.md +396 -0
- package/skills/installing-webapp-features/SKILL.md +210 -0
- package/skills/managing-webapp-agentforce-conversation-client/SKILL.md +186 -0
- package/skills/managing-webapp-agentforce-conversation-client/references/constraints.md +134 -0
- package/skills/managing-webapp-agentforce-conversation-client/references/examples.md +132 -0
- package/skills/managing-webapp-agentforce-conversation-client/references/style-tokens.md +101 -0
- package/skills/managing-webapp-agentforce-conversation-client/references/troubleshooting.md +57 -0
- package/skills/switching-org/SKILL.md +28 -0
- package/skills/using-webapp-graphql/SKILL.md +324 -0
- package/skills/using-webapp-graphql/shared-schema.graphqls +1150 -0
- package/skills/salesforce-lightning-app-build/SKILL.md +0 -346
- package/skills/salesforce-web-app-creating-records/SKILL.md +0 -84
- package/skills/salesforce-web-app-feature/SKILL.md +0 -70
- package/skills/salesforce-web-app-list-and-create-records/SKILL.md +0 -36
- package/skills/salesforce-web-application/SKILL.md +0 -34
- /package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/docs/bootstrap-template-byo-lwr.md +0 -0
- /package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/docs/configure-content-brandingSet.md +0 -0
- /package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/docs/configure-content-route.md +0 -0
- /package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/docs/configure-content-themeLayout.md +0 -0
- /package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/docs/configure-content-view.md +0 -0
- /package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/docs/configure-guest-sharing-rules.md +0 -0
- /package/skills/{salesforce-experience-lwr-site → generating-experience-lwr-site}/docs/handle-component-and-region-ids.md +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salesforce/afv-skills",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Salesforce skills for Agentforce Vibes",
|
|
5
5
|
"license": "CC-BY-NC-4.0",
|
|
6
6
|
"files": [
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
"registry": "https://registry.npmjs.org"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "
|
|
15
|
-
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "
|
|
14
|
+
"@salesforce/webapp-template-app-react-sample-b2e-experimental": "1.109.1",
|
|
15
|
+
"@salesforce/webapp-template-app-react-sample-b2x-experimental": "1.109.1",
|
|
16
16
|
"tsx": "^4.21.0"
|
|
17
17
|
},
|
|
18
18
|
"scripts": {
|
|
19
19
|
"validate:skills": "tsx scripts/validate-skills.ts",
|
|
20
20
|
"sync-react-b2e-sample": "node scripts/sync-react-b2e-sample.js",
|
|
21
|
-
"sync-react-b2x-sample": "node scripts/sync-react-b2x-sample.js"
|
|
21
|
+
"sync-react-b2x-sample": "node scripts/sync-react-b2x-sample.js",
|
|
22
|
+
"sync-webapp-skills": "node scripts/sync-webapp-skills.js"
|
|
22
23
|
}
|
|
23
24
|
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: accessing-webapp-data
|
|
3
|
+
description: Salesforce data access patterns. Use when adding or modifying any code that fetches data from Salesforce (records, Chatter, Connect API, etc.).
|
|
4
|
+
paths:
|
|
5
|
+
- "**/*.ts"
|
|
6
|
+
- "**/*.tsx"
|
|
7
|
+
- "**/*.graphql"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Salesforce Data Access
|
|
11
|
+
|
|
12
|
+
Guidance for accessing Salesforce data from web apps. **All Salesforce data fetches MUST use the Data SDK** (`@salesforce/sdk-data`). The SDK provides authentication, CSRF handling, and correct base URL resolution — direct `fetch` or `axios` calls bypass these and are not allowed.
|
|
13
|
+
|
|
14
|
+
## Mandatory: Use the Data SDK
|
|
15
|
+
|
|
16
|
+
> **Every Salesforce data fetch must go through the Data SDK.** Obtain it via `createDataSDK()`, then use `sdk.graphql?.()` or `sdk.fetch?.()`. Never call `fetch()` or `axios` directly for Salesforce endpoints.
|
|
17
|
+
|
|
18
|
+
## Optional Chaining and Graceful Handling
|
|
19
|
+
|
|
20
|
+
**Always use optional chaining** when calling `sdk.graphql` or `sdk.fetch` — these methods may be undefined in some surfaces (e.g., Salesforce ACC, MCP Apps). Handle the case where they are not available gracefully:
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
const sdk = await createDataSDK();
|
|
24
|
+
|
|
25
|
+
// ✅ Use optional chaining
|
|
26
|
+
const response = await sdk.graphql?.(query);
|
|
27
|
+
|
|
28
|
+
// ✅ Check before using fetch
|
|
29
|
+
if (!sdk.fetch) {
|
|
30
|
+
throw new Error("Data SDK fetch is not available in this context");
|
|
31
|
+
}
|
|
32
|
+
const res = await sdk.fetch(url);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For GraphQL, if `sdk.graphql` is undefined, the call returns `undefined` — handle that in your logic (e.g., throw a clear error or return a fallback). For `sdk.fetch`, check availability before calling when the operation is required.
|
|
36
|
+
|
|
37
|
+
## Preference: GraphQL First
|
|
38
|
+
|
|
39
|
+
**GraphQL is the preferred method** for querying and mutating Salesforce records. Use it when:
|
|
40
|
+
|
|
41
|
+
- Querying records (Account, Contact, Opportunity, custom objects)
|
|
42
|
+
- Creating, updating, or deleting records (when GraphQL supports the operation)
|
|
43
|
+
- Fetching related data, filters, sorting, pagination
|
|
44
|
+
|
|
45
|
+
**Use `sdk.fetch` only when GraphQL is not sufficient.** For REST API usage, invoke the `fetching-rest-api` skill, which documents:
|
|
46
|
+
|
|
47
|
+
- Chatter API (e.g., `/services/data/v65.0/chatter/users/me`)
|
|
48
|
+
- Connect REST API (e.g., `/services/data/v65.0/connect/file/upload/config`)
|
|
49
|
+
- Apex REST (e.g., `/services/apexrest/auth/login`)
|
|
50
|
+
- UI API REST (e.g., `/services/data/v65.0/ui-api/records/{recordId}`)
|
|
51
|
+
- Einstein LLM Gateway
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Getting the SDK
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { createDataSDK } from "@salesforce/sdk-data";
|
|
59
|
+
|
|
60
|
+
const sdk = await createDataSDK();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Example 1: GraphQL (Preferred)
|
|
66
|
+
|
|
67
|
+
For record queries and mutations, use GraphQL via the Data SDK. Invoke the `using-graphql` skill for the full workflow (schema exploration, query authoring, codegen, lint validate).
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { createDataSDK, gql } from "@salesforce/sdk-data";
|
|
71
|
+
import type { GetAccountsQuery } from "../graphql-operations-types";
|
|
72
|
+
|
|
73
|
+
const GET_ACCOUNTS = gql`
|
|
74
|
+
query GetAccounts {
|
|
75
|
+
uiapi {
|
|
76
|
+
query {
|
|
77
|
+
Account(first: 10) {
|
|
78
|
+
edges {
|
|
79
|
+
node {
|
|
80
|
+
Id
|
|
81
|
+
Name { value }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
export async function getAccounts() {
|
|
91
|
+
const sdk = await createDataSDK();
|
|
92
|
+
const response = await sdk.graphql?.<GetAccountsQuery>(GET_ACCOUNTS);
|
|
93
|
+
|
|
94
|
+
if (response?.errors?.length) {
|
|
95
|
+
throw new Error(response.errors.map((e) => e.message).join("; "));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return response?.data?.uiapi?.query?.Account?.edges?.map((e) => e?.node) ?? [];
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Example 2: Fetch (When GraphQL Is Not Sufficient)
|
|
105
|
+
|
|
106
|
+
For REST endpoints that have no GraphQL equivalent, use `sdk.fetch`. **Invoke the `fetching-rest-api` skill** for full documentation of Chatter, Connect REST, Apex REST, UI API REST, and Einstein LLM endpoints.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { createDataSDK } from "@salesforce/sdk-data";
|
|
110
|
+
|
|
111
|
+
declare const __SF_API_VERSION__: string;
|
|
112
|
+
const API_VERSION = typeof __SF_API_VERSION__ !== "undefined" ? __SF_API_VERSION__ : "65.0";
|
|
113
|
+
|
|
114
|
+
export async function getCurrentUser() {
|
|
115
|
+
const sdk = await createDataSDK();
|
|
116
|
+
const response = await sdk.fetch?.(`/services/data/v${API_VERSION}/chatter/users/me`);
|
|
117
|
+
|
|
118
|
+
if (!response?.ok) throw new Error(`HTTP ${response?.status}`);
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
return { id: data.id, name: data.name };
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Anti-Patterns (Forbidden)
|
|
127
|
+
|
|
128
|
+
### Direct fetch to Salesforce
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// ❌ FORBIDDEN — bypasses Data SDK auth and CSRF
|
|
132
|
+
const res = await fetch("/services/data/v65.0/chatter/users/me");
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Direct axios to Salesforce
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// ❌ FORBIDDEN — bypasses Data SDK
|
|
139
|
+
const res = await axios.get("/services/data/v65.0/chatter/users/me");
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Correct approach
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// ✅ CORRECT — use Data SDK
|
|
146
|
+
const sdk = await createDataSDK();
|
|
147
|
+
const res = await sdk.fetch?.("/services/data/v65.0/chatter/users/me");
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Clarifying Vague Data Requests
|
|
153
|
+
|
|
154
|
+
When a user asks about data and the request is vague, **clarify before implementing**. Ask which of the following they want:
|
|
155
|
+
|
|
156
|
+
- **Application code** — Add or modify code in a specific web app so the app performs the data interaction at runtime (e.g., GraphQL query in the React app)
|
|
157
|
+
- **Local SF CLI** — Run Salesforce CLI commands locally (e.g., `sf data query`, `sf data import tree`) to interact with the org from the terminal
|
|
158
|
+
- **Local example data** — Update or add local fixture/example data files (e.g., JSON in `data/`) for development or testing
|
|
159
|
+
- **Other** — Data export, report generation, setup script, etc.
|
|
160
|
+
|
|
161
|
+
Do not assume. A request like "fetch accounts" could mean: (1) add a GraphQL query to the app, (2) run `sf data query` in the terminal, or (3) update sample data files. Confirm the intent before proceeding.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Decision Flow
|
|
166
|
+
|
|
167
|
+
1. **Need to query or mutate Salesforce records?** → Use GraphQL via the Data SDK. Invoke the `using-graphql` skill.
|
|
168
|
+
2. **Need Chatter, Connect REST, Apex REST, UI API REST, or Einstein LLM?** → Use `sdk.fetch`. Invoke the `fetching-rest-api` skill.
|
|
169
|
+
3. **Never** use `fetch`, `axios`, or similar directly for Salesforce API calls.
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Reference
|
|
174
|
+
|
|
175
|
+
- GraphQL workflow: invoke the `using-graphql` skill (`.a4drules/skills/using-graphql/`)
|
|
176
|
+
- REST API via fetch: invoke the `fetching-rest-api` skill (`.a4drules/skills/fetching-rest-api/`)
|
|
177
|
+
- Data SDK package: `@salesforce/sdk-data` (`createDataSDK`, `gql`, `NodeOfConnection`)
|
|
178
|
+
- `createRecord` for UI API record creation: `@salesforce/webapp-experimental/api` (uses Data SDK internally)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: building-webapp-data-visualization
|
|
3
|
+
description: Adds data visualization components (charts, stat cards, KPI metrics) to React pages using Recharts. Use when the user asks to add a chart, graph, donut chart, pie chart, bar chart, stat card, KPI metric, dashboard visualization, or analytics component to the web application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Data Visualization
|
|
7
|
+
|
|
8
|
+
## When to Use
|
|
9
|
+
|
|
10
|
+
Use this skill when:
|
|
11
|
+
- Adding charts (donut, pie, bar, line, area) to a dashboard or analytics page
|
|
12
|
+
- Displaying KPI/metric stat cards with trend indicators
|
|
13
|
+
- Building a dashboard layout with mixed chart types and summary cards
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Step 1 — Determine the visualization type
|
|
18
|
+
|
|
19
|
+
Identify what the user needs:
|
|
20
|
+
|
|
21
|
+
- **Donut / pie chart** — categorical breakdown (e.g. issue types, status distribution)
|
|
22
|
+
- **Bar chart** — comparison across categories or time periods
|
|
23
|
+
- **Line / area chart** — trends over time
|
|
24
|
+
- **Stat card** — single KPI metric with optional trend indicator
|
|
25
|
+
- **Combined dashboard** — stat cards + one or more charts
|
|
26
|
+
|
|
27
|
+
If unclear, ask:
|
|
28
|
+
|
|
29
|
+
> "What data should the chart display, and would a donut chart, bar chart, line chart, or stat cards work best?"
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Step 2 — Install dependencies
|
|
34
|
+
|
|
35
|
+
All chart types in this skill use **recharts**. Install once from the web app directory:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install recharts
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Recharts is built on D3 and provides declarative React components. No additional CSS is needed.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Step 3 — Choose implementation path
|
|
46
|
+
|
|
47
|
+
Read the corresponding guide:
|
|
48
|
+
|
|
49
|
+
- **Bar chart** — read `implementation/bar-line-chart.md` (categorical data)
|
|
50
|
+
- **Line / area chart** — read `implementation/bar-line-chart.md` (time-series data)
|
|
51
|
+
- **Donut / pie chart** — read `implementation/donut-chart.md`
|
|
52
|
+
- **Stat card with trend** — read `implementation/stat-card.md`
|
|
53
|
+
- **Dashboard layout** — read `implementation/dashboard-layout.md`
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Verification
|
|
58
|
+
|
|
59
|
+
Before completing:
|
|
60
|
+
|
|
61
|
+
1. Chart renders with correct data and colors.
|
|
62
|
+
2. Chart is responsive (resizes with container).
|
|
63
|
+
3. Legend labels match the data categories.
|
|
64
|
+
4. Stat card trends display correct positive/negative indicators.
|
|
65
|
+
5. Run from the web app directory:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- **Lint:** MUST result in 0 errors.
|
|
72
|
+
- **Build:** MUST succeed.
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# Bar & Line / Area Chart — Implementation Guide
|
|
2
|
+
|
|
3
|
+
Requires **recharts** (install from the web app directory; see SKILL.md Step 2).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Data shapes
|
|
8
|
+
|
|
9
|
+
### Time-series (line / area chart)
|
|
10
|
+
|
|
11
|
+
Use when data represents a trend over time or ordered sequence.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
interface TimeSeriesDataPoint {
|
|
15
|
+
x: string; // date or label on the x-axis
|
|
16
|
+
y: number; // numeric value
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Map raw fields to this shape: e.g. `date` → `x`, `revenue` → `y`.
|
|
21
|
+
|
|
22
|
+
### Categorical (bar chart)
|
|
23
|
+
|
|
24
|
+
Use when data compares discrete categories.
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
interface CategoricalDataPoint {
|
|
28
|
+
name: string; // category label
|
|
29
|
+
value: number; // numeric value
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Map raw fields to this shape: e.g. `product` → `name`, `sales` → `value`.
|
|
34
|
+
|
|
35
|
+
### How to decide
|
|
36
|
+
|
|
37
|
+
| Signal | Type |
|
|
38
|
+
|--------|------|
|
|
39
|
+
| "over time", "trend", date-like keys | Time-series → line chart |
|
|
40
|
+
| "by category", "by X", label-like keys | Categorical → bar chart |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Theme colors
|
|
45
|
+
|
|
46
|
+
Pick a theme based on the data's sentiment:
|
|
47
|
+
|
|
48
|
+
| Theme | Stroke / Fill | When to use |
|
|
49
|
+
|-------|---------------|-------------|
|
|
50
|
+
| `green` | `#22c55e` | Growth, gain, positive trend |
|
|
51
|
+
| `red` | `#ef4444` | Decline, loss, negative trend |
|
|
52
|
+
| `neutral` | `#6366f1` | Default or mixed data |
|
|
53
|
+
|
|
54
|
+
Define colors as constants — do not use inline hex values.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const THEME_COLORS = {
|
|
58
|
+
red: "#ef4444",
|
|
59
|
+
green: "#22c55e",
|
|
60
|
+
neutral: "#6366f1",
|
|
61
|
+
} as const;
|
|
62
|
+
|
|
63
|
+
type ChartTheme = keyof typeof THEME_COLORS;
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Line chart component
|
|
69
|
+
|
|
70
|
+
Create at `components/LineChart.tsx` (or colocate with the page):
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
import React from "react";
|
|
74
|
+
import {
|
|
75
|
+
LineChart as RechartsLineChart,
|
|
76
|
+
Line,
|
|
77
|
+
XAxis,
|
|
78
|
+
YAxis,
|
|
79
|
+
CartesianGrid,
|
|
80
|
+
Tooltip,
|
|
81
|
+
Legend,
|
|
82
|
+
ResponsiveContainer,
|
|
83
|
+
} from "recharts";
|
|
84
|
+
|
|
85
|
+
const THEME_COLORS = {
|
|
86
|
+
red: "#ef4444",
|
|
87
|
+
green: "#22c55e",
|
|
88
|
+
neutral: "#6366f1",
|
|
89
|
+
} as const;
|
|
90
|
+
|
|
91
|
+
type ChartTheme = keyof typeof THEME_COLORS;
|
|
92
|
+
|
|
93
|
+
interface TimeSeriesDataPoint {
|
|
94
|
+
x: string;
|
|
95
|
+
y: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface TimeSeriesChartProps {
|
|
99
|
+
data: TimeSeriesDataPoint[];
|
|
100
|
+
theme?: ChartTheme;
|
|
101
|
+
title?: string;
|
|
102
|
+
className?: string;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function TimeSeriesChart({
|
|
106
|
+
data,
|
|
107
|
+
theme = "neutral",
|
|
108
|
+
title,
|
|
109
|
+
className = "",
|
|
110
|
+
}: TimeSeriesChartProps) {
|
|
111
|
+
if (data.length === 0) {
|
|
112
|
+
return <p className="text-muted-foreground text-center py-8">No data to display</p>;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const color = THEME_COLORS[theme];
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<div className={className}>
|
|
119
|
+
{title && (
|
|
120
|
+
<h3 className="text-sm font-medium text-primary mb-2 uppercase tracking-wide">
|
|
121
|
+
{title}
|
|
122
|
+
</h3>
|
|
123
|
+
)}
|
|
124
|
+
<ResponsiveContainer width="100%" height={300}>
|
|
125
|
+
<RechartsLineChart data={data}>
|
|
126
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
127
|
+
<XAxis dataKey="x" />
|
|
128
|
+
<YAxis />
|
|
129
|
+
<Tooltip />
|
|
130
|
+
<Legend />
|
|
131
|
+
<Line type="monotone" dataKey="y" stroke={color} strokeWidth={2} dot={false} />
|
|
132
|
+
</RechartsLineChart>
|
|
133
|
+
</ResponsiveContainer>
|
|
134
|
+
</div>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Bar chart component
|
|
142
|
+
|
|
143
|
+
Create at `components/BarChart.tsx` (or colocate with the page):
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import React from "react";
|
|
147
|
+
import {
|
|
148
|
+
BarChart as RechartsBarChart,
|
|
149
|
+
Bar,
|
|
150
|
+
XAxis,
|
|
151
|
+
YAxis,
|
|
152
|
+
CartesianGrid,
|
|
153
|
+
Tooltip,
|
|
154
|
+
Legend,
|
|
155
|
+
ResponsiveContainer,
|
|
156
|
+
} from "recharts";
|
|
157
|
+
|
|
158
|
+
const THEME_COLORS = {
|
|
159
|
+
red: "#ef4444",
|
|
160
|
+
green: "#22c55e",
|
|
161
|
+
neutral: "#6366f1",
|
|
162
|
+
} as const;
|
|
163
|
+
|
|
164
|
+
type ChartTheme = keyof typeof THEME_COLORS;
|
|
165
|
+
|
|
166
|
+
interface CategoricalDataPoint {
|
|
167
|
+
name: string;
|
|
168
|
+
value: number;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
interface CategoricalChartProps {
|
|
172
|
+
data: CategoricalDataPoint[];
|
|
173
|
+
theme?: ChartTheme;
|
|
174
|
+
title?: string;
|
|
175
|
+
className?: string;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function CategoricalChart({
|
|
179
|
+
data,
|
|
180
|
+
theme = "neutral",
|
|
181
|
+
title,
|
|
182
|
+
className = "",
|
|
183
|
+
}: CategoricalChartProps) {
|
|
184
|
+
if (data.length === 0) {
|
|
185
|
+
return <p className="text-muted-foreground text-center py-8">No data to display</p>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const color = THEME_COLORS[theme];
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div className={className}>
|
|
192
|
+
{title && (
|
|
193
|
+
<h3 className="text-sm font-medium text-primary mb-2 uppercase tracking-wide">
|
|
194
|
+
{title}
|
|
195
|
+
</h3>
|
|
196
|
+
)}
|
|
197
|
+
<ResponsiveContainer width="100%" height={300}>
|
|
198
|
+
<RechartsBarChart data={data}>
|
|
199
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
200
|
+
<XAxis dataKey="name" />
|
|
201
|
+
<YAxis />
|
|
202
|
+
<Tooltip />
|
|
203
|
+
<Legend />
|
|
204
|
+
<Bar dataKey="value" fill={color} radius={[4, 4, 0, 0]} />
|
|
205
|
+
</RechartsBarChart>
|
|
206
|
+
</ResponsiveContainer>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Area chart variant
|
|
215
|
+
|
|
216
|
+
For a filled area chart (useful for volume-over-time), swap `Line` for `Area`:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from "recharts";
|
|
220
|
+
|
|
221
|
+
<ResponsiveContainer width="100%" height={300}>
|
|
222
|
+
<AreaChart data={data}>
|
|
223
|
+
<CartesianGrid strokeDasharray="3 3" />
|
|
224
|
+
<XAxis dataKey="x" />
|
|
225
|
+
<YAxis />
|
|
226
|
+
<Tooltip />
|
|
227
|
+
<Area type="monotone" dataKey="y" stroke={color} fill={color} fillOpacity={0.2} />
|
|
228
|
+
</AreaChart>
|
|
229
|
+
</ResponsiveContainer>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Chart container wrapper
|
|
235
|
+
|
|
236
|
+
Wrap any chart in a styled card for consistent spacing:
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { Card } from "@/components/ui/card";
|
|
240
|
+
|
|
241
|
+
interface ChartContainerProps {
|
|
242
|
+
children: React.ReactNode;
|
|
243
|
+
className?: string;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function ChartContainer({ children, className = "" }: ChartContainerProps) {
|
|
247
|
+
return (
|
|
248
|
+
<Card className={`p-4 border-gray-200 shadow-sm ${className}`}>
|
|
249
|
+
{children}
|
|
250
|
+
</Card>
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Usage:
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
<ChartContainer>
|
|
259
|
+
<TimeSeriesChart data={monthlyData} theme="green" title="Monthly Revenue" />
|
|
260
|
+
</ChartContainer>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Preparing raw data
|
|
266
|
+
|
|
267
|
+
Map API responses to the expected shape before passing to the chart:
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
const timeSeriesData = useMemo(
|
|
271
|
+
() => apiRecords.map((r) => ({ x: r.date, y: r.revenue })),
|
|
272
|
+
[apiRecords],
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const categoricalData = useMemo(
|
|
276
|
+
() => apiRecords.map((r) => ({ name: r.product, value: r.sales })),
|
|
277
|
+
[apiRecords],
|
|
278
|
+
);
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## Key Recharts concepts
|
|
284
|
+
|
|
285
|
+
| Component | Purpose |
|
|
286
|
+
|-----------|---------|
|
|
287
|
+
| `ResponsiveContainer` | Wraps chart to fill parent width |
|
|
288
|
+
| `CartesianGrid` | Background grid lines |
|
|
289
|
+
| `XAxis` / `YAxis` | Axis labels; `dataKey` maps to the data field |
|
|
290
|
+
| `Tooltip` | Hover info |
|
|
291
|
+
| `Legend` | Series labels |
|
|
292
|
+
| `Line` | Line series; `type="monotone"` for smooth curves |
|
|
293
|
+
| `Bar` | Bar series; `radius` rounds top corners |
|
|
294
|
+
| `Area` | Filled area; `fillOpacity` controls transparency |
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Accessibility
|
|
299
|
+
|
|
300
|
+
- Always include a text legend (not just colors).
|
|
301
|
+
- Chart should be wrapped in a section with a visible heading.
|
|
302
|
+
- For critical data, provide a text summary or table alternative.
|
|
303
|
+
- Use sufficient color contrast between the chart stroke/fill and background.
|
|
304
|
+
- Consider `prefers-reduced-motion` for chart animations.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## Common mistakes
|
|
309
|
+
|
|
310
|
+
| Mistake | Fix |
|
|
311
|
+
|---------|-----|
|
|
312
|
+
| Missing `ResponsiveContainer` | Chart won't resize; always wrap |
|
|
313
|
+
| Fixed width/height on chart | Let `ResponsiveContainer` control sizing |
|
|
314
|
+
| No empty-data handling | Show "No data" message when `data.length === 0` |
|
|
315
|
+
| Inline colors | Extract to `THEME_COLORS` constant |
|
|
316
|
+
| Using raw Recharts for every chart type | Use `DonutChart` (see `donut-chart.md`) for pie/donut |
|