@salesforce/ui-bundle-template-app-react-template-b2e 12.3.8 → 12.3.10

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/dist/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [12.3.10](https://github.com/salesforce-experience-platform-emu/webapps/compare/v12.3.9...v12.3.10) (2026-09-02)
7
+
8
+ **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
9
+
10
+
11
+
12
+
13
+
14
+ ## [12.3.9](https://github.com/salesforce-experience-platform-emu/webapps/compare/v12.3.8...v12.3.9) (2026-09-02)
15
+
16
+ **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
17
+
18
+
19
+
20
+
21
+
6
22
  ## [12.3.8](https://github.com/salesforce-experience-platform-emu/webapps/compare/v12.3.7...v12.3.8) (2026-09-01)
7
23
 
8
24
  **Note:** Version bump only for package @salesforce/ui-bundle-template-base-sfdx-project
@@ -19,8 +19,8 @@
19
19
  "graphql:schema": "node scripts/get-graphql-schema.mjs"
20
20
  },
21
21
  "dependencies": {
22
- "@salesforce/platform-sdk": "^12.3.8",
23
- "@salesforce/ui-bundle": "^12.3.8",
22
+ "@salesforce/platform-sdk": "^12.3.10",
23
+ "@salesforce/ui-bundle": "^12.3.10",
24
24
  "@tailwindcss/vite": "^4.1.17",
25
25
  "class-variance-authority": "^0.7.1",
26
26
  "clsx": "^2.1.1",
@@ -46,8 +46,8 @@
46
46
  "@graphql-eslint/eslint-plugin": "^4.1.0",
47
47
  "@graphql-tools/utils": "^11.0.0",
48
48
  "@playwright/test": "^1.49.0",
49
- "@salesforce/graphiti": "^12.3.8",
50
- "@salesforce/vite-plugin-ui-bundle": "^12.3.8",
49
+ "@salesforce/graphiti": "^12.3.10",
50
+ "@salesforce/vite-plugin-ui-bundle": "^12.3.10",
51
51
  "@testing-library/jest-dom": "^6.6.3",
52
52
  "@testing-library/react": "^16.1.0",
53
53
  "@testing-library/user-event": "^14.5.2",
@@ -1,11 +1,26 @@
1
1
  import { AgentforceConversationClient } from "./components/AgentforceConversationClient";
2
2
  import { Outlet, Link, useLocation } from "react-router";
3
3
  import { getAllRoutes } from "./router-utils";
4
- import { useState } from "react";
4
+ import { useEffect, useId, useRef, useState } from "react";
5
5
 
6
6
  export default function AppLayout() {
7
7
  const [isOpen, setIsOpen] = useState(false);
8
8
  const location = useLocation();
9
+ const navId = useId();
10
+ const outletWrapperRef = useRef<HTMLDivElement>(null);
11
+ const previousPathname = useRef<string | null>(null);
12
+
13
+ useEffect(() => {
14
+ if (previousPathname.current !== null && previousPathname.current !== location.pathname) {
15
+ const wrapper = outletWrapperRef.current;
16
+ const target = wrapper?.querySelector<HTMLElement>("h1, main, [role='main']") ?? wrapper;
17
+ if (target) {
18
+ if (!target.hasAttribute("tabindex")) target.setAttribute("tabindex", "-1");
19
+ target.focus();
20
+ }
21
+ }
22
+ previousPathname.current = location.pathname;
23
+ }, [location.pathname]);
9
24
 
10
25
  const isActive = (path: string) => location.pathname === path;
11
26
 
@@ -28,6 +43,20 @@ export default function AppLayout() {
28
43
 
29
44
  return (
30
45
  <>
46
+ <a
47
+ href="#main-content"
48
+ onClick={(e) => {
49
+ e.preventDefault();
50
+ const content = outletWrapperRef.current;
51
+ if (content) {
52
+ if (!content.hasAttribute("tabindex")) content.setAttribute("tabindex", "-1");
53
+ content.focus();
54
+ }
55
+ }}
56
+ className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:top-2 focus:left-2 focus:rounded-md focus:bg-white focus:px-4 focus:py-2 focus:text-gray-900 focus:shadow focus:ring-2 focus:ring-gray-900"
57
+ >
58
+ Skip to main content
59
+ </a>
31
60
  <nav className="bg-white border-b border-gray-200">
32
61
  <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
33
62
  <div className="flex justify-between items-center h-16">
@@ -38,8 +67,10 @@ export default function AppLayout() {
38
67
  onClick={toggleMenu}
39
68
  className="p-2 rounded-md text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
40
69
  aria-label="Toggle menu"
70
+ aria-expanded={isOpen}
71
+ aria-controls={navId}
41
72
  >
42
- <div className="w-6 h-6 flex flex-col justify-center space-y-1.5">
73
+ <div aria-hidden="true" className="w-6 h-6 flex flex-col justify-center space-y-1.5">
43
74
  <span
44
75
  className={`block h-0.5 w-6 bg-current transition-all ${
45
76
  isOpen ? "rotate-45 translate-y-2" : ""
@@ -56,14 +87,15 @@ export default function AppLayout() {
56
87
  </div>
57
88
  </button>
58
89
  </div>
59
- {isOpen && (
60
- <div className="pb-4">
90
+ <div id={navId} className="pb-4" hidden={!isOpen}>
91
+ {isOpen && (
61
92
  <div className="flex flex-col space-y-2">
62
93
  {navigationRoutes.map((item) => (
63
94
  <Link
64
95
  key={item.path}
65
96
  to={item.path}
66
97
  onClick={() => setIsOpen(false)}
98
+ aria-current={isActive(item.path) ? "page" : undefined}
67
99
  className={`px-3 py-2 rounded-md text-sm font-medium transition-colors ${
68
100
  isActive(item.path)
69
101
  ? "bg-blue-100 text-blue-700"
@@ -74,11 +106,13 @@ export default function AppLayout() {
74
106
  </Link>
75
107
  ))}
76
108
  </div>
77
- </div>
78
- )}
109
+ )}
110
+ </div>
79
111
  </div>
80
112
  </nav>
81
- <Outlet />
113
+ <div id="main-content" ref={outletWrapperRef}>
114
+ <Outlet />
115
+ </div>
82
116
  <AgentforceConversationClient agentId="<USER_AGENT_ID_18_CHAR_0Xx...>" />
83
117
  </>
84
118
  );
@@ -1,4 +1,4 @@
1
- import { useState } from "react";
1
+ import { useEffect, useState } from "react";
2
2
  import { useParams, useNavigate } from "react-router";
3
3
  import { createDataSDK } from "@salesforce/platform-sdk";
4
4
  import { AlertCircle, ChevronDown, ChevronRight, FileQuestion } from "lucide-react";
@@ -83,8 +83,20 @@ export default function AccountObjectDetail() {
83
83
  error,
84
84
  } = useAsyncData(() => fetchAccountDetail(recordId!), [recordId]);
85
85
 
86
+ const pageTitle = loading
87
+ ? "Loading Account | MyApp"
88
+ : error
89
+ ? "Error | MyApp"
90
+ : account
91
+ ? `${fieldValue(account.Name) ?? "Account"} | MyApp`
92
+ : "Account Not Found | MyApp";
93
+
94
+ useEffect(() => {
95
+ document.title = pageTitle;
96
+ }, [pageTitle]);
97
+
86
98
  return (
87
- <div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
99
+ <main className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
88
100
  {/* Loading state */}
89
101
  {loading && <AccountDetailSkeleton />}
90
102
 
@@ -95,8 +107,8 @@ export default function AccountObjectDetail() {
95
107
  {!loading && !error && !account && <AccountDetailNotFound onBack={() => navigate(-1)} />}
96
108
 
97
109
  {/* Content */}
98
- {account && <AccountDetailContent account={account} />}
99
- </div>
110
+ {!loading && !error && account && <AccountDetailContent account={account} />}
111
+ </main>
100
112
  );
101
113
  }
102
114
 
@@ -229,10 +241,16 @@ function Section({ title, children }: { title: string; children: React.ReactNode
229
241
  const [open, setOpen] = useState(true);
230
242
  return (
231
243
  <Collapsible open={open} onOpenChange={setOpen}>
232
- <CollapsibleTrigger className="flex items-center gap-2 cursor-pointer text-lg font-semibold py-2">
233
- {open ? <ChevronDown className="size-5" /> : <ChevronRight className="size-5" />}
234
- {title}
235
- </CollapsibleTrigger>
244
+ <h2>
245
+ <CollapsibleTrigger className="flex items-center gap-2 cursor-pointer text-lg font-semibold py-2">
246
+ {open ? (
247
+ <ChevronDown aria-hidden="true" className="size-5" />
248
+ ) : (
249
+ <ChevronRight aria-hidden="true" className="size-5" />
250
+ )}
251
+ {title}
252
+ </CollapsibleTrigger>
253
+ </h2>
236
254
  <CollapsibleContent>
237
255
  <div className="mt-2 space-y-4">{children}</div>
238
256
  </CollapsibleContent>
@@ -244,9 +262,9 @@ function AccountDetailError({ onBack }: { onBack: () => void }) {
244
262
  return (
245
263
  <>
246
264
  <Alert variant="destructive" role="alert">
247
- <AlertCircle />
265
+ <AlertCircle aria-hidden="true" />
248
266
  <AlertTitle>
249
- <h2>Failed to load account</h2>
267
+ <h1>Failed to load account</h1>
250
268
  </AlertTitle>
251
269
  <AlertDescription>
252
270
  Something went wrong while loading this account. Please try again later.
@@ -254,7 +272,7 @@ function AccountDetailError({ onBack }: { onBack: () => void }) {
254
272
  </Alert>
255
273
  <div className="mt-4 flex gap-3">
256
274
  <Button variant="outline" onClick={onBack}>
257
- ← Back
275
+ <span aria-hidden="true">←</span> Back
258
276
  </Button>
259
277
  <Button variant="outline" onClick={() => window.location.reload()}>
260
278
  Retry
@@ -266,15 +284,15 @@ function AccountDetailError({ onBack }: { onBack: () => void }) {
266
284
 
267
285
  function AccountDetailNotFound({ onBack }: { onBack: () => void }) {
268
286
  return (
269
- <Card>
287
+ <Card role="status">
270
288
  <CardContent className="flex flex-col items-center justify-center py-16 text-center">
271
- <FileQuestion className="size-12 text-muted-foreground mb-4" />
272
- <h2 className="text-lg font-semibold mb-1">Account not found</h2>
289
+ <FileQuestion aria-hidden="true" className="size-12 text-muted-foreground mb-4" />
290
+ <h1 className="text-lg font-semibold mb-1">Account not found</h1>
273
291
  <p className="text-sm text-muted-foreground mb-6">
274
292
  The account you're looking for doesn't exist or may have been deleted.
275
293
  </p>
276
294
  <Button variant="outline" onClick={onBack}>
277
- ← Go back
295
+ <span aria-hidden="true">←</span> Go back
278
296
  </Button>
279
297
  </CardContent>
280
298
  </Card>
@@ -283,7 +301,8 @@ function AccountDetailNotFound({ onBack }: { onBack: () => void }) {
283
301
 
284
302
  function AccountDetailSkeleton() {
285
303
  return (
286
- <>
304
+ <div role="status" aria-live="polite">
305
+ <span className="sr-only">Loading account…</span>
287
306
  <Skeleton className="h-8 w-56 mb-4" />
288
307
 
289
308
  <Card>
@@ -332,7 +351,7 @@ function AccountDetailSkeleton() {
332
351
  </div>
333
352
  </CardContent>
334
353
  </Card>
335
- </>
354
+ </div>
336
355
  );
337
356
  }
338
357
 
@@ -1,10 +1,15 @@
1
+ import { useEffect } from "react";
1
2
  import { GlobalSearchBox } from "../features/search";
2
3
 
3
4
  export default function HomePage() {
5
+ useEffect(() => {
6
+ document.title = "Home | MyApp";
7
+ }, []);
8
+
4
9
  return (
5
- <div className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
10
+ <main className="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
6
11
  <h1 className="text-2xl font-bold mb-6">Home</h1>
7
12
  <GlobalSearchBox placeholder="Search accounts, contacts, opportunities..." />
8
- </div>
13
+ </main>
9
14
  );
10
15
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "12.3.8",
3
+ "version": "12.3.10",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
9
- "version": "12.3.8",
9
+ "version": "12.3.10",
10
10
  "license": "SEE LICENSE IN LICENSE.txt",
11
11
  "dependencies": {
12
12
  "fast-xml-parser": "^5.9.3",
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-base-sfdx-project",
3
- "version": "12.3.8",
3
+ "version": "12.3.10",
4
4
  "description": "Base SFDX project template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "publishConfig": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/ui-bundle-template-app-react-template-b2e",
3
- "version": "12.3.8",
3
+ "version": "12.3.10",
4
4
  "description": "Salesforce React internal app template",
5
5
  "license": "SEE LICENSE IN LICENSE.txt",
6
6
  "author": "",