@schandlergarcia/sf-web-components 1.0.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.
Files changed (195) hide show
  1. package/.a4drules/skills/building-data-visualization/SKILL.md +72 -0
  2. package/.a4drules/skills/building-data-visualization/implementation/bar-line-chart.md +316 -0
  3. package/.a4drules/skills/building-data-visualization/implementation/dashboard-layout.md +189 -0
  4. package/.a4drules/skills/building-data-visualization/implementation/donut-chart.md +181 -0
  5. package/.a4drules/skills/building-data-visualization/implementation/stat-card.md +150 -0
  6. package/.a4drules/skills/building-react-components/SKILL.md +96 -0
  7. package/.a4drules/skills/building-react-components/implementation/component.md +78 -0
  8. package/.a4drules/skills/building-react-components/implementation/header-footer.md +132 -0
  9. package/.a4drules/skills/building-react-components/implementation/page.md +93 -0
  10. package/.a4drules/skills/configuring-csp-trusted-sites/SKILL.md +90 -0
  11. package/.a4drules/skills/configuring-csp-trusted-sites/implementation/metadata-format.md +281 -0
  12. package/.a4drules/skills/configuring-webapp-metadata/SKILL.md +158 -0
  13. package/.a4drules/skills/creating-webapp/SKILL.md +140 -0
  14. package/.a4drules/skills/deploying-to-salesforce/SKILL.md +226 -0
  15. package/.a4drules/skills/implementing-file-upload/SKILL.md +396 -0
  16. package/.a4drules/skills/installing-webapp-features/SKILL.md +210 -0
  17. package/.a4drules/skills/managing-agentforce-conversation-client/SKILL.md +186 -0
  18. package/.a4drules/skills/managing-agentforce-conversation-client/references/constraints.md +134 -0
  19. package/.a4drules/skills/managing-agentforce-conversation-client/references/examples.md +132 -0
  20. package/.a4drules/skills/managing-agentforce-conversation-client/references/style-tokens.md +101 -0
  21. package/.a4drules/skills/managing-agentforce-conversation-client/references/troubleshooting.md +57 -0
  22. package/.a4drules/skills/using-salesforce-data/SKILL.md +363 -0
  23. package/.a4drules/skills/using-salesforce-data/graphql-search.sh +139 -0
  24. package/.a4drules/webapp-data.md +353 -0
  25. package/.a4drules/webapp-ui.md +16 -0
  26. package/README.md +124 -0
  27. package/dist/components/library/cards/ActionList.js +27 -0
  28. package/dist/components/library/cards/ActionList.js.map +1 -0
  29. package/dist/components/library/cards/ActivityCard.js +40 -0
  30. package/dist/components/library/cards/ActivityCard.js.map +1 -0
  31. package/dist/components/library/cards/BaseCard.js +89 -0
  32. package/dist/components/library/cards/BaseCard.js.map +1 -0
  33. package/dist/components/library/cards/CalloutCard.js +28 -0
  34. package/dist/components/library/cards/CalloutCard.js.map +1 -0
  35. package/dist/components/library/cards/ChartCard.js +79 -0
  36. package/dist/components/library/cards/ChartCard.js.map +1 -0
  37. package/dist/components/library/cards/FeedPanel.js +38 -0
  38. package/dist/components/library/cards/FeedPanel.js.map +1 -0
  39. package/dist/components/library/cards/ListCard.js +112 -0
  40. package/dist/components/library/cards/ListCard.js.map +1 -0
  41. package/dist/components/library/cards/MetricCard.js +86 -0
  42. package/dist/components/library/cards/MetricCard.js.map +1 -0
  43. package/dist/components/library/cards/MetricsStrip.js +60 -0
  44. package/dist/components/library/cards/MetricsStrip.js.map +1 -0
  45. package/dist/components/library/cards/SectionCard.js +59 -0
  46. package/dist/components/library/cards/SectionCard.js.map +1 -0
  47. package/dist/components/library/cards/StatusCard.js +137 -0
  48. package/dist/components/library/cards/StatusCard.js.map +1 -0
  49. package/dist/components/library/cards/TableCard.js +244 -0
  50. package/dist/components/library/cards/TableCard.js.map +1 -0
  51. package/dist/components/library/cards/WidgetCard.js +60 -0
  52. package/dist/components/library/cards/WidgetCard.js.map +1 -0
  53. package/dist/components/library/charts/D3Chart.js +74 -0
  54. package/dist/components/library/charts/D3Chart.js.map +1 -0
  55. package/dist/components/library/charts/D3ChartTemplates.js +44 -0
  56. package/dist/components/library/charts/D3ChartTemplates.js.map +1 -0
  57. package/dist/components/library/charts/GeoMap.js +229 -0
  58. package/dist/components/library/charts/GeoMap.js.map +1 -0
  59. package/dist/components/library/chat/ChatBar.js +194 -0
  60. package/dist/components/library/chat/ChatBar.js.map +1 -0
  61. package/dist/components/library/chat/ChatInput.js +67 -0
  62. package/dist/components/library/chat/ChatInput.js.map +1 -0
  63. package/dist/components/library/chat/ChatMessage.js +112 -0
  64. package/dist/components/library/chat/ChatMessage.js.map +1 -0
  65. package/dist/components/library/chat/ChatMessageList.js +50 -0
  66. package/dist/components/library/chat/ChatMessageList.js.map +1 -0
  67. package/dist/components/library/chat/ChatPanel.js +77 -0
  68. package/dist/components/library/chat/ChatPanel.js.map +1 -0
  69. package/dist/components/library/chat/ChatSuggestions.js +22 -0
  70. package/dist/components/library/chat/ChatSuggestions.js.map +1 -0
  71. package/dist/components/library/chat/ChatToolCall.js +87 -0
  72. package/dist/components/library/chat/ChatToolCall.js.map +1 -0
  73. package/dist/components/library/chat/ChatTypingIndicator.js +20 -0
  74. package/dist/components/library/chat/ChatTypingIndicator.js.map +1 -0
  75. package/dist/components/library/chat/ChatWelcome.js +24 -0
  76. package/dist/components/library/chat/ChatWelcome.js.map +1 -0
  77. package/dist/components/library/chat/useChatState.js +77 -0
  78. package/dist/components/library/chat/useChatState.js.map +1 -0
  79. package/dist/components/library/data/DataModeProvider.js +47 -0
  80. package/dist/components/library/data/DataModeProvider.js.map +1 -0
  81. package/dist/components/library/data/DataModeToggle.js +28 -0
  82. package/dist/components/library/data/DataModeToggle.js.map +1 -0
  83. package/dist/components/library/data/filterUtils.js +71 -0
  84. package/dist/components/library/data/filterUtils.js.map +1 -0
  85. package/dist/components/library/data/useDataSource.js +13 -0
  86. package/dist/components/library/data/useDataSource.js.map +1 -0
  87. package/dist/components/library/data/usePageFilters.js +46 -0
  88. package/dist/components/library/data/usePageFilters.js.map +1 -0
  89. package/dist/components/library/filters/FilterBar.js +89 -0
  90. package/dist/components/library/filters/FilterBar.js.map +1 -0
  91. package/dist/components/library/filters/SearchFilter.js +44 -0
  92. package/dist/components/library/filters/SearchFilter.js.map +1 -0
  93. package/dist/components/library/filters/SelectFilter.js +44 -0
  94. package/dist/components/library/filters/SelectFilter.js.map +1 -0
  95. package/dist/components/library/filters/ToggleFilter.js +48 -0
  96. package/dist/components/library/filters/ToggleFilter.js.map +1 -0
  97. package/dist/components/library/forms/FormField.js +256 -0
  98. package/dist/components/library/forms/FormField.js.map +1 -0
  99. package/dist/components/library/forms/FormModal.js +161 -0
  100. package/dist/components/library/forms/FormModal.js.map +1 -0
  101. package/dist/components/library/forms/FormRenderer.js +32 -0
  102. package/dist/components/library/forms/FormRenderer.js.map +1 -0
  103. package/dist/components/library/forms/FormSection.js +49 -0
  104. package/dist/components/library/forms/FormSection.js.map +1 -0
  105. package/dist/components/library/forms/useFormState.js +85 -0
  106. package/dist/components/library/forms/useFormState.js.map +1 -0
  107. package/dist/components/library/heroui/Accordion.js +12 -0
  108. package/dist/components/library/heroui/Accordion.js.map +1 -0
  109. package/dist/components/library/heroui/Alert.js +12 -0
  110. package/dist/components/library/heroui/Alert.js.map +1 -0
  111. package/dist/components/library/heroui/Badge.js +12 -0
  112. package/dist/components/library/heroui/Badge.js.map +1 -0
  113. package/dist/components/library/heroui/Breadcrumbs.js +12 -0
  114. package/dist/components/library/heroui/Breadcrumbs.js.map +1 -0
  115. package/dist/components/library/heroui/Button.js +18 -0
  116. package/dist/components/library/heroui/Button.js.map +1 -0
  117. package/dist/components/library/heroui/Card.js +12 -0
  118. package/dist/components/library/heroui/Card.js.map +1 -0
  119. package/dist/components/library/heroui/Drawer.js +12 -0
  120. package/dist/components/library/heroui/Drawer.js.map +1 -0
  121. package/dist/components/library/heroui/Dropdown.js +12 -0
  122. package/dist/components/library/heroui/Dropdown.js.map +1 -0
  123. package/dist/components/library/heroui/Input.js +10 -0
  124. package/dist/components/library/heroui/Input.js.map +1 -0
  125. package/dist/components/library/heroui/Kbd.js +12 -0
  126. package/dist/components/library/heroui/Kbd.js.map +1 -0
  127. package/dist/components/library/heroui/Meter.js +12 -0
  128. package/dist/components/library/heroui/Meter.js.map +1 -0
  129. package/dist/components/library/heroui/Modal.js +12 -0
  130. package/dist/components/library/heroui/Modal.js.map +1 -0
  131. package/dist/components/library/heroui/Pagination.js +12 -0
  132. package/dist/components/library/heroui/Pagination.js.map +1 -0
  133. package/dist/components/library/heroui/ProgressBar.js +12 -0
  134. package/dist/components/library/heroui/ProgressBar.js.map +1 -0
  135. package/dist/components/library/heroui/ProgressCircle.js +12 -0
  136. package/dist/components/library/heroui/ProgressCircle.js.map +1 -0
  137. package/dist/components/library/heroui/ScrollShadow.js +12 -0
  138. package/dist/components/library/heroui/ScrollShadow.js.map +1 -0
  139. package/dist/components/library/heroui/Select.js +12 -0
  140. package/dist/components/library/heroui/Select.js.map +1 -0
  141. package/dist/components/library/heroui/Separator.js +12 -0
  142. package/dist/components/library/heroui/Separator.js.map +1 -0
  143. package/dist/components/library/heroui/Skeleton.js +12 -0
  144. package/dist/components/library/heroui/Skeleton.js.map +1 -0
  145. package/dist/components/library/heroui/Tabs.js +12 -0
  146. package/dist/components/library/heroui/Tabs.js.map +1 -0
  147. package/dist/components/library/heroui/Toast.js +13 -0
  148. package/dist/components/library/heroui/Toast.js.map +1 -0
  149. package/dist/components/library/heroui/Toggle.js +12 -0
  150. package/dist/components/library/heroui/Toggle.js.map +1 -0
  151. package/dist/components/library/heroui/Tooltip.js +12 -0
  152. package/dist/components/library/heroui/Tooltip.js.map +1 -0
  153. package/dist/components/library/layout/PageContainer.js +9 -0
  154. package/dist/components/library/layout/PageContainer.js.map +1 -0
  155. package/dist/components/library/skeletons/CardSkeleton.js +29 -0
  156. package/dist/components/library/skeletons/CardSkeleton.js.map +1 -0
  157. package/dist/components/library/theme/AppThemeProvider.js +55 -0
  158. package/dist/components/library/theme/AppThemeProvider.js.map +1 -0
  159. package/dist/components/library/theme/tokens.js +55 -0
  160. package/dist/components/library/theme/tokens.js.map +1 -0
  161. package/dist/components/library/ui/Avatar.js +33 -0
  162. package/dist/components/library/ui/Avatar.js.map +1 -0
  163. package/dist/components/library/ui/Button.js +50 -0
  164. package/dist/components/library/ui/Button.js.map +1 -0
  165. package/dist/components/library/ui/Card.js +21 -0
  166. package/dist/components/library/ui/Card.js.map +1 -0
  167. package/dist/components/library/ui/Chip.js +31 -0
  168. package/dist/components/library/ui/Chip.js.map +1 -0
  169. package/dist/components/library/ui/Container.js +42 -0
  170. package/dist/components/library/ui/Container.js.map +1 -0
  171. package/dist/components/library/ui/EmptyState.js +27 -0
  172. package/dist/components/library/ui/EmptyState.js.map +1 -0
  173. package/dist/components/library/ui/Input.js +22 -0
  174. package/dist/components/library/ui/Input.js.map +1 -0
  175. package/dist/components/library/ui/Spinner.js +64 -0
  176. package/dist/components/library/ui/Spinner.js.map +1 -0
  177. package/dist/components/library/ui/Text.js +43 -0
  178. package/dist/components/library/ui/Text.js.map +1 -0
  179. package/dist/components/library/ui/Toggle.js +47 -0
  180. package/dist/components/library/ui/Toggle.js.map +1 -0
  181. package/dist/components/workspace/ComponentRegistry.js +231 -0
  182. package/dist/components/workspace/ComponentRegistry.js.map +1 -0
  183. package/dist/index.js +185 -0
  184. package/dist/index.js.map +1 -0
  185. package/dist/lib/utils.js +9 -0
  186. package/dist/lib/utils.js.map +1 -0
  187. package/dist/node_modules/clsx/dist/clsx.js +17 -0
  188. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  189. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +2925 -0
  190. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  191. package/package.json +81 -0
  192. package/scripts/get-graphql-schema.mjs +68 -0
  193. package/scripts/reset-command-center.sh +358 -0
  194. package/scripts/rewrite-e2e-assets.mjs +23 -0
  195. package/scripts/validate-dashboard.sh +290 -0
@@ -0,0 +1,181 @@
1
+ # Donut / Pie Chart — Implementation Guide
2
+
3
+ Requires **recharts** (install from the web app directory; see SKILL.md Step 2).
4
+
5
+ ---
6
+
7
+ ## Data structure
8
+
9
+ Charts expect an array of objects with `name`, `value`, and `color`:
10
+
11
+ ```ts
12
+ interface ChartData {
13
+ name: string;
14
+ value: number;
15
+ color: string;
16
+ }
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Donut chart component
22
+
23
+ Create at `components/DonutChart.tsx`:
24
+
25
+ ```tsx
26
+ import React from "react";
27
+ import { PieChart, Pie, Cell, ResponsiveContainer } from "recharts";
28
+ import { Card } from "@/components/ui/card";
29
+
30
+ interface ChartData {
31
+ name: string;
32
+ value: number;
33
+ color: string;
34
+ }
35
+
36
+ interface DonutChartProps {
37
+ title: string;
38
+ data: ChartData[];
39
+ }
40
+
41
+ export const DonutChart: React.FC<DonutChartProps> = ({ title, data }) => {
42
+ const total = data.reduce((sum, item) => sum + item.value, 0);
43
+ const mainPercentage = total > 0 ? Math.round((data[0]?.value / total) * 100) : 0;
44
+
45
+ return (
46
+ <Card className="p-4 border-gray-200 shadow-sm flex flex-col">
47
+ <h3 className="text-sm font-medium text-primary mb-2 uppercase tracking-wide">
48
+ {title}
49
+ </h3>
50
+
51
+ <div className="relative flex items-center justify-center">
52
+ <ResponsiveContainer width="100%" height={300}>
53
+ <PieChart>
54
+ <Pie
55
+ data={data}
56
+ cx="50%"
57
+ cy="50%"
58
+ innerRadius={70}
59
+ outerRadius={110}
60
+ paddingAngle={2}
61
+ dataKey="value"
62
+ >
63
+ {data.map((entry, index) => (
64
+ <Cell key={`cell-${index}`} fill={entry.color} />
65
+ ))}
66
+ </Pie>
67
+ </PieChart>
68
+ </ResponsiveContainer>
69
+
70
+ {/* Center label */}
71
+ <div className="absolute inset-0 flex items-center justify-center">
72
+ <div className="text-center">
73
+ <div className="text-5xl font-bold text-primary">{mainPercentage}%</div>
74
+ </div>
75
+ </div>
76
+ </div>
77
+
78
+ {/* Legend */}
79
+ <div className="mt-6 grid grid-cols-2 gap-3">
80
+ {data.map((item, index) => (
81
+ <div key={index} className="flex items-center gap-2">
82
+ <div className="w-3 h-3 rounded-full" style={{ backgroundColor: item.color }} />
83
+ <span className="text-sm text-gray-700">{item.name}</span>
84
+ </div>
85
+ ))}
86
+ </div>
87
+ </Card>
88
+ );
89
+ };
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Key Recharts concepts
95
+
96
+ | Component | Purpose |
97
+ |-----------|---------|
98
+ | `ResponsiveContainer` | Wraps chart to make it fill its parent's width |
99
+ | `PieChart` | Chart container for pie/donut |
100
+ | `Pie` | The data ring; `innerRadius` > 0 makes it a donut |
101
+ | `Cell` | Individual segment; accepts `fill` color |
102
+ | `paddingAngle` | Gap between segments (degrees) |
103
+
104
+ ### Donut vs Pie
105
+
106
+ | Property | Donut | Pie |
107
+ |----------|-------|-----|
108
+ | `innerRadius` | `> 0` (e.g. `70`) | `0` |
109
+ | Center label | Yes, positioned absolutely | Not typical |
110
+
111
+ ---
112
+
113
+ ## Preparing chart data from raw records
114
+
115
+ Transform API data into the `ChartData[]` format before passing to the chart:
116
+
117
+ ```tsx
118
+ const CATEGORIES = ["Plumbing", "HVAC", "Electrical"] as const;
119
+ const OTHER_LABEL = "Other";
120
+ const COLORS = ["#7C3AED", "#EC4899", "#14B8A6", "#06B6D4"];
121
+
122
+ const chartData = useMemo(() => {
123
+ const counts: Record<string, number> = {};
124
+ CATEGORIES.forEach((c) => (counts[c] = 0));
125
+ counts[OTHER_LABEL] = 0;
126
+
127
+ records.forEach((record) => {
128
+ const type = record.category;
129
+ if (CATEGORIES.includes(type as (typeof CATEGORIES)[number])) {
130
+ counts[type]++;
131
+ } else {
132
+ counts[OTHER_LABEL]++;
133
+ }
134
+ });
135
+
136
+ return [
137
+ ...CATEGORIES.map((name, i) => ({ name, value: counts[name], color: COLORS[i] })),
138
+ { name: OTHER_LABEL, value: counts[OTHER_LABEL], color: COLORS[CATEGORIES.length] },
139
+ ];
140
+ }, [records]);
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Color palette recommendations
146
+
147
+ | Use case | Colors |
148
+ |----------|--------|
149
+ | Categorical (4 items) | `#7C3AED` `#EC4899` `#14B8A6` `#06B6D4` |
150
+ | Status (3 items) | `#22C55E` `#F59E0B` `#EF4444` (green/amber/red) |
151
+ | Sequential | Use opacity variants of one hue: `#7C3AED` at 100%, 75%, 50%, 25% |
152
+
153
+ Keep chart colors consistent with the app's design system. Define them as constants, not inline values.
154
+
155
+ ---
156
+
157
+ ## Other chart types
158
+
159
+ For **bar charts** and **line / area charts**, see `bar-line-chart.md` in this directory.
160
+
161
+ ---
162
+
163
+ ## Accessibility
164
+
165
+ - Always include a text legend (not just colors).
166
+ - Chart should be wrapped in a section with a visible heading.
167
+ - For critical data, provide a text summary or table alternative.
168
+ - Use sufficient color contrast between segments.
169
+ - Consider `prefers-reduced-motion` for chart animations.
170
+
171
+ ---
172
+
173
+ ## Common mistakes
174
+
175
+ | Mistake | Fix |
176
+ |---------|-----|
177
+ | Missing `ResponsiveContainer` | Chart won't resize; always wrap in `ResponsiveContainer` |
178
+ | Fixed width/height on `PieChart` | Let `ResponsiveContainer` control sizing |
179
+ | No legend | Add a grid legend below the chart |
180
+ | Inline colors | Extract to constants for consistency |
181
+ | No fallback for empty data | Show "No data" message when `data` is empty |
@@ -0,0 +1,150 @@
1
+ # Stat Card — Implementation Guide
2
+
3
+ ## What is a stat card
4
+
5
+ A stat card displays a single KPI metric with an optional trend indicator. Used on dashboards to show at-a-glance numbers like "Total Properties: 42 (+10%)".
6
+
7
+ ---
8
+
9
+ ## Component interface
10
+
11
+ ```ts
12
+ interface StatCardProps {
13
+ title: string;
14
+ value: number | string;
15
+ trend?: {
16
+ value: number;
17
+ isPositive: boolean;
18
+ };
19
+ subtitle?: string;
20
+ onClick?: () => void;
21
+ }
22
+ ```
23
+
24
+ ---
25
+
26
+ ## StatCard component
27
+
28
+ Create at `components/StatCard.tsx`:
29
+
30
+ ```tsx
31
+ import React from "react";
32
+ import { Card } from "@/components/ui/card";
33
+ import { TrendingUp, TrendingDown } from "lucide-react";
34
+
35
+ interface StatCardProps {
36
+ title: string;
37
+ value: number | string;
38
+ trend?: {
39
+ value: number;
40
+ isPositive: boolean;
41
+ };
42
+ subtitle?: string;
43
+ onClick?: () => void;
44
+ }
45
+
46
+ export const StatCard: React.FC<StatCardProps> = ({ title, value, trend, subtitle, onClick }) => {
47
+ return (
48
+ <Card
49
+ className={`p-4 border-gray-200 shadow-sm relative ${
50
+ onClick ? "cursor-pointer hover:shadow-lg transition-shadow" : ""
51
+ }`}
52
+ onClick={onClick}
53
+ >
54
+ <div className="space-y-1">
55
+ <p className="text-sm font-medium text-muted-foreground uppercase tracking-wide">{title}</p>
56
+ <div className="flex items-baseline gap-3">
57
+ <p className="text-4xl font-bold text-primary">{value}</p>
58
+ {trend && (
59
+ <span
60
+ className={`inline-flex items-center gap-1 px-2.5 py-0.5 rounded-full text-sm font-medium ${
61
+ trend.isPositive
62
+ ? "bg-emerald-100 text-emerald-800"
63
+ : "bg-pink-100 text-pink-800"
64
+ }`}
65
+ >
66
+ {trend.isPositive ? (
67
+ <TrendingUp className="w-4 h-4" />
68
+ ) : (
69
+ <TrendingDown className="w-4 h-4" />
70
+ )}
71
+ {Math.abs(trend.value)}%
72
+ </span>
73
+ )}
74
+ </div>
75
+ {subtitle && <p className="text-sm text-muted-foreground mt-1">{subtitle}</p>}
76
+ </div>
77
+ </Card>
78
+ );
79
+ };
80
+ ```
81
+
82
+ This version uses Lucide icons (`TrendingUp`/`TrendingDown`) instead of custom SVGs for portability across projects.
83
+
84
+ ---
85
+
86
+ ## Layout: stat card grid
87
+
88
+ Display stat cards in a responsive grid:
89
+
90
+ ```tsx
91
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
92
+ <StatCard
93
+ title="Total Properties"
94
+ value={metrics.totalProperties}
95
+ trend={{ value: 10, isPositive: true }}
96
+ subtitle="Last month total 38"
97
+ />
98
+ <StatCard
99
+ title="Units Available"
100
+ value={metrics.unitsAvailable}
101
+ trend={{ value: 5, isPositive: false }}
102
+ subtitle="Last month total 12/42"
103
+ />
104
+ <StatCard
105
+ title="Occupied Units"
106
+ value={metrics.occupiedUnits}
107
+ trend={{ value: 8, isPositive: true }}
108
+ subtitle="Last month total 27"
109
+ />
110
+ </div>
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Computing trend values
116
+
117
+ Calculate trends from current vs previous period:
118
+
119
+ ```ts
120
+ const trends = useMemo(() => {
121
+ const previousTotal = metrics.totalProperties - Math.round(metrics.totalProperties * 0.1);
122
+ const trendPercent = previousTotal > 0
123
+ ? Math.round(((metrics.totalProperties - previousTotal) / previousTotal) * 100)
124
+ : 0;
125
+
126
+ return {
127
+ value: Math.abs(trendPercent),
128
+ isPositive: trendPercent >= 0,
129
+ };
130
+ }, [metrics]);
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Trend badge color conventions
136
+
137
+ | Trend | Background | Text | Meaning |
138
+ |-------|------------|------|---------|
139
+ | Positive (up) | `bg-emerald-100` | `text-emerald-800` | Growth, improvement |
140
+ | Negative (down) | `bg-pink-100` | `text-pink-800` | Decline, concern |
141
+ | Neutral | `bg-gray-100` | `text-gray-600` | No change |
142
+
143
+ ---
144
+
145
+ ## Accessibility
146
+
147
+ - Card uses `cursor-pointer` and `hover:shadow-lg` only when `onClick` is provided.
148
+ - Trend icons have implicit meaning from color + direction icon.
149
+ - Stat values use large, bold text for visibility.
150
+ - Title uses `uppercase tracking-wide` for visual hierarchy without heading tags (appropriate in a card grid).
@@ -0,0 +1,96 @@
1
+ ---
2
+ name: building-react-components
3
+ description: Use when editing any React code in the web application — creating or modifying components, pages, layout, headers, footers, or any TSX/JSX files. Follow this skill for add component, add page, header/footer, and general React UI implementation patterns (shadcn UI and Tailwind CSS).
4
+ ---
5
+
6
+ # React Web App (Components, Pages, Layout)
7
+
8
+ Use this skill whenever you are editing React/TSX code in the web app (creating or modifying components, pages, header/footer, or layout).
9
+
10
+ ## Step 1 — Identify the type of component
11
+
12
+ Determine which of these three categories the request falls into, then follow the corresponding section below:
13
+
14
+ - **Page** — user wants a new routed page (e.g. "add a contacts page", "create a dashboard page", "add a settings section")
15
+ - **Header / Footer** — user wants a site-wide header, footer, nav bar, or page footer that appears on every page
16
+ - **Component** — everything else: a widget, card, table, form, dialog, or other UI element placed within an existing page
17
+
18
+ If it is not immediately clear from the user's message, ask:
19
+
20
+ > "Are you looking to add a new page, a site-wide header or footer, or a component within an existing page?"
21
+
22
+ Then follow the matching section.
23
+
24
+ ---
25
+
26
+ ## Clarifying Questions
27
+
28
+ Ask **one question at a time** and wait for the response before asking the next. Stop when you have enough to build accurately — do not guess or assume.
29
+
30
+ ### For a Page
31
+
32
+ 1. **What is the name and purpose of the page?** (e.g., Contacts, Dashboard, Settings)
33
+ 2. **What URL path should it use?** (e.g., `/contacts`, `/dashboard`) — or derive from the page name?
34
+ 3. **Should the page appear in the navigation menu?**
35
+ 4. **Who can access it?** Public, authenticated users only (`PrivateRoute`), or unauthenticated only (e.g., login — `AuthenticationRoute`)?
36
+ 5. **What content or sections should the page include?** (list, form, table, detail view, etc.)
37
+ 6. **Does it need to fetch any data?** If so, from where?
38
+
39
+ ### For a Header / Footer
40
+
41
+ 1. **Header, footer, or both?**
42
+ 2. **What should the header contain?** (logo/app name, nav links, user avatar, CTA button, etc.)
43
+ 3. **What should the footer contain?** (copyright text, links, social icons, etc.)
44
+ 4. **Should the header be sticky (fixed to top while scrolling)?**
45
+ 5. **Is there a logo or brand name to display?** (or placeholder?)
46
+ 6. **Any specific color scheme or style direction?** (dark background, branded primary color, minimal, etc.)
47
+ 7. **Should navigation links appear in the header?** If so, which pages?
48
+
49
+ ### For a Component
50
+
51
+ 1. **What should the component do?** (display data, accept input, trigger an action, etc.)
52
+ 2. **What page or location should it appear on?**
53
+ 3. **Is this shared/reusable across pages, or specific to one feature?** (determines file location)
54
+ 4. **What data or props does it need?** (static content, props, fetched data)
55
+ 5. **Does it need internal state?** (loading, toggle, form state, etc.)
56
+ 6. **Are there any specific shadcn components to use?** (Card, Table, Dialog, Form, etc.)
57
+ 7. **Should it appear in a specific layout position?** (full-width, sidebar, inline, etc.)
58
+
59
+ ---
60
+
61
+ ## Implementation
62
+
63
+ Once you have identified the type and gathered answers to the clarifying questions, read and follow the corresponding implementation guide:
64
+
65
+ - **Page** — read `implementation/page.md` and follow the instructions there.
66
+ - **Header / Footer** — read `implementation/header-footer.md` and follow the instructions there.
67
+ - **Component** — read `implementation/component.md` and follow the instructions there.
68
+
69
+ ---
70
+
71
+ ## TypeScript Standards
72
+
73
+ - **Never use `any`** — use proper types, generics, or `unknown` with type guards.
74
+ - **Event handlers:** `(event: React.FormEvent<HTMLFormElement>): void`
75
+ - **State:** `useState<User | null>(null)` — always provide the type parameter.
76
+ - **No unsafe assertions** (`obj as User`). Use type guards:
77
+ ```typescript
78
+ function isUser(obj: unknown): obj is User {
79
+ return typeof obj === 'object' && obj !== null && typeof (obj as User).id === 'string';
80
+ }
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Verification (MANDATORY)
86
+
87
+ Before completing, run from the web app directory `force-app/main/default/webapplications/<appName>/`:
88
+
89
+ ```bash
90
+ cd force-app/main/default/webapplications/<appName> && npm run lint && npm run build
91
+ ```
92
+
93
+ - **Lint:** MUST result in 0 errors.
94
+ - **Build:** MUST succeed (includes TypeScript check).
95
+
96
+ If either fails, fix the errors and re-run. Do not leave the session with failing quality gates.
@@ -0,0 +1,78 @@
1
+ # Implementation — Component
2
+
3
+ ### Rules
4
+
5
+ 1. **Always use shadcn components** from `@/components/ui` — never build raw HTML equivalents for buttons, inputs, cards, alerts, tabs, tables, or labels.
6
+ 2. **All styling via Tailwind** — utility classes only. No inline `style={{}}`, CSS Modules, or other styling systems.
7
+ 3. **Use design tokens** — prefer `bg-background`, `text-foreground`, `text-muted-foreground`, `border`, `bg-primary`, `text-destructive`, `rounded-lg` over hardcoded colors.
8
+ 4. **Use `cn()`** from `@/lib/utils` for conditional or composable class names.
9
+ 5. **TypeScript** — functional components with typed props interface; always accept `className?: string`.
10
+
11
+ ### File Location — Component
12
+
13
+ | Component type | Location | Export |
14
+ | ---------------------------------------------- | ---------------------------------------- | ---------------------------------------- |
15
+ | Shared UI primitive (reusable across features) | `src/components/ui/` — add to `index.ts` | Named export |
16
+ | Feature-specific (e.g., dashboard widget) | `src/components/<feature>/` | Named export, import directly where used |
17
+ | Page-level layout element | `src/components/layout/` | Named export |
18
+
19
+ ### Component Structure
20
+
21
+ ```tsx
22
+ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui";
23
+ import { cn } from "@/lib/utils";
24
+
25
+ interface MyComponentProps {
26
+ title: string;
27
+ value: string;
28
+ className?: string;
29
+ }
30
+
31
+ export function MyComponent({ title, value, className }: MyComponentProps) {
32
+ return (
33
+ <Card className={cn("border", className)}>
34
+ <CardHeader>
35
+ <CardTitle className="text-sm font-medium">{title}</CardTitle>
36
+ </CardHeader>
37
+ <CardContent>
38
+ <p className="text-2xl font-semibold text-foreground">{value}</p>
39
+ </CardContent>
40
+ </Card>
41
+ );
42
+ }
43
+ ```
44
+
45
+ ### State and Hooks
46
+
47
+ - **Local state only:** keep `useState`, `useReducer`, `useRef` inside the component.
48
+ - **Shared or complex state:** extract to a custom hook in `src/hooks/` (prefix with `use`, e.g. `useFormData`). Do this when more than one component needs the state, or when multiple hooks are composed together.
49
+
50
+ ### Adding the Component to a Page
51
+
52
+ ```tsx
53
+ // In the target page file, e.g. src/pages/HomePage.tsx
54
+ import { MyComponent } from "@/components/<feature>/MyComponent";
55
+
56
+ export default function HomePage() {
57
+ return (
58
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
59
+ <MyComponent title="Status" value="Active" />
60
+ </div>
61
+ );
62
+ }
63
+ ```
64
+
65
+ ### Useful Patterns — Component
66
+
67
+ - **Programmatic navigation:** use `useNavigate` from `react-router`; call `navigate(path)` — consistent with GlobalSearchInput, SearchResultCard, MaintenanceTable, and other components in the web application.
68
+ - **Page container:** `max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12`
69
+ - **Icons:** `lucide-react`; add `aria-hidden="true"` on decorative icons
70
+ - **Focus styles:** use `focus-visible:` variants
71
+ - **Multiple visual variants:** use CVA (`cva`) and `VariantProps`
72
+ - **shadcn import barrel:** `import { Button, Card, Input } from "@/components/ui"`
73
+
74
+ ### Confirm — Component
75
+
76
+ - Imports use path aliases (`@/`, not deep relative paths)
77
+ - No raw `<button>`, `<input>`, or styled `<div>` where shadcn equivalents exist
78
+ - No inline `style={{}}` — Tailwind only
@@ -0,0 +1,132 @@
1
+ # Implementation — Header / Footer
2
+
3
+ ### Rules
4
+
5
+ 1. **Edit `appLayout.tsx` only** — header and footer are layout-level concerns. Never add them to individual page files.
6
+ 2. **Never modify `routes.tsx` or `app.tsx`** — the router setup must remain intact.
7
+ 3. **Create component files in `src/components/layout/`** — the designated location for layout-level components.
8
+ 4. **Use the full-height flex column pattern** — wrap layout in `min-h-screen flex flex-col` so footer stays at bottom.
9
+ 5. **Use shadcn and Tailwind** — compose from `@/components/ui`; style with Tailwind utility classes and design tokens.
10
+ 6. **Use path aliases** — import with `@/components/layout/...` and `@/components/ui`; no deep relative paths.
11
+ 7. **Preserve existing content** — if `appLayout.tsx` already has a `<NavigationMenu />` or other shell elements, keep them in place.
12
+
13
+ ### Step 1 — Create the header component (if requested)
14
+
15
+ Create `src/components/layout/AppHeader.tsx`:
16
+
17
+ ```tsx
18
+ import { cn } from "@/lib/utils";
19
+
20
+ interface AppHeaderProps {
21
+ className?: string;
22
+ }
23
+
24
+ export function AppHeader({ className }: AppHeaderProps) {
25
+ return (
26
+ <header
27
+ className={cn(
28
+ "w-full border-b bg-background px-4 sm:px-6 lg:px-8 py-4",
29
+ className,
30
+ )}
31
+ >
32
+ <div className="max-w-7xl mx-auto flex items-center justify-between">
33
+ <span className="text-lg font-semibold text-foreground">My App</span>
34
+ </div>
35
+ </header>
36
+ );
37
+ }
38
+ ```
39
+
40
+ ### Step 2 — Create the footer component (if requested)
41
+
42
+ Create `src/components/layout/AppFooter.tsx`:
43
+
44
+ ```tsx
45
+ import { cn } from "@/lib/utils";
46
+
47
+ interface AppFooterProps {
48
+ className?: string;
49
+ }
50
+
51
+ export function AppFooter({ className }: AppFooterProps) {
52
+ return (
53
+ <footer
54
+ className={cn(
55
+ "w-full border-t bg-background px-4 sm:px-6 lg:px-8 py-4",
56
+ className,
57
+ )}
58
+ >
59
+ <div className="max-w-7xl mx-auto text-center text-sm text-muted-foreground">
60
+ &copy; {new Date().getFullYear()} My App. All rights reserved.
61
+ </div>
62
+ </footer>
63
+ );
64
+ }
65
+ ```
66
+
67
+ ### Step 3 — Edit `appLayout.tsx`
68
+
69
+ Open `src/appLayout.tsx` — this is the **only file to modify** for layout-level additions. Wrap existing content in a flex column and add header above and footer below `<Outlet />`:
70
+
71
+ ```tsx
72
+ import { Outlet } from "react-router";
73
+ import { AppHeader } from "@/components/layout/AppHeader";
74
+ import { AppFooter } from "@/components/layout/AppFooter";
75
+ // Keep all existing imports unchanged
76
+
77
+ export default function AppLayout() {
78
+ return (
79
+ <div className="min-h-screen flex flex-col bg-background">
80
+ <AppHeader />
81
+ {/* Keep any existing NavigationMenu or other shell elements here */}
82
+ <main className="flex-1">
83
+ <Outlet />
84
+ </main>
85
+ <AppFooter />
86
+ </div>
87
+ );
88
+ }
89
+ ```
90
+
91
+ ### File Locations — Header / Footer
92
+
93
+ | Component | File | Export |
94
+ | ------------ | ------------------------------------- | ------------------------------ |
95
+ | Header | `src/components/layout/AppHeader.tsx` | Named export |
96
+ | Footer | `src/components/layout/AppFooter.tsx` | Named export |
97
+ | Layout shell | `src/appLayout.tsx` | Default export (edit in place) |
98
+
99
+ ### Why `appLayout.tsx` — Not Pages or Routes
100
+
101
+ `AppLayout` is the single shell rendered at the root route. Every page is a child rendered via `<Outlet />`. Placing the header and footer here ensures they appear on every page without touching individual pages or the route registry.
102
+
103
+ ```
104
+ AppLayout (appLayout.tsx)
105
+ ├── AppHeader ← renders on every page
106
+ ├── NavigationMenu ← keep if already present
107
+ ├── <Outlet /> ← active page renders here
108
+ └── AppFooter ← renders on every page
109
+ ```
110
+
111
+ ### Useful Patterns — Header / Footer
112
+
113
+ - **Sticky header:** add `sticky top-0 z-50` to the `<header>` element
114
+ - **Separator:** use `<Separator />` from `@/components/ui` instead of `border-b`/`border-t` if a visible divider is preferred
115
+ - **Nav links in header:** use `<Button variant="ghost" asChild>` wrapping a React Router `<Link>`
116
+ - **Icons:** `lucide-react`; add `aria-hidden="true"` on decorative icons
117
+ - **Design tokens:** `bg-background`, `text-foreground`, `text-muted-foreground`, `border`, `bg-primary`
118
+
119
+ ### Mobile hamburger / Menu icon — Must be functional
120
+
121
+ If the header includes a hamburger or `Menu` icon for mobile:
122
+
123
+ - **Do not** add a Menu/hamburger icon that does nothing. It must toggle a visible mobile menu.
124
+ - **Required:** (1) State: `const [isOpen, setIsOpen] = useState(false)`. (2) Button: `onClick={() => setIsOpen(!isOpen)}`, `aria-label="Toggle menu"`. (3) Conditional panel: `{isOpen && ( <div>...nav links...</div> )}` with responsive visibility (e.g. `md:hidden`). (4) Close on navigate: each link in the panel should `onClick={() => setIsOpen(false)}`.
125
+ - Implement in `appLayout.tsx` (or the component that owns the header). Use the `Menu` icon from `lucide-react`.
126
+
127
+ ### Confirm — Header / Footer
128
+
129
+ - Header and footer appear on every page (navigate to at least two routes)
130
+ - Imports use path aliases (`@/components/layout/...`)
131
+ - No inline `style={{}}` — Tailwind only
132
+ - `src/routes.tsx` and `src/app.tsx` are unchanged