Skip to content

Commit 0a0fa8c

Browse files
feat(ontario): add react-ontario package, showcase integration, and Experience Cloud site metadata
- Add Ontario Design System React package (@sfgps-ds/react-ontario) with 33 components, OntarioProvider/OntarioContext, safeHtml/sanitizeHref utilities, and full test suite - Add ontario-lab Vite dev app for component-level development - Integrate Ontario routes into sfGpsDsUsGovShowcase UIBundle (vite.config woff2-only font strategy, stripped CSS alias, copy-uswds-assets rewrite) - Add 278+ Playwright E2E specs for Ontario components (local + live configurations) - Set UIBundle target=Experience for Experience Cloud site linking - Add Digital Experience site metadata for SfGpsDsUsGovShowcase and SfGpsDsUsGovReact (Network, CustomSite, DigitalExperienceConfig, DigitalExperienceBundle) in showcase/ - Retrieve and store pronto_app and Government_of_the_Future reference site metadata - Fix ComboBox and DatePicker bugs; expand Popover tests - Update docs: ARCHITECTURE, QUICKSTART, ADDING_A_DESIGN_SYSTEM process guide - Update Playwright configs with ontario-live-chromium project and live URL support
1 parent 3e0de91 commit 0a0fa8c

449 files changed

Lines changed: 12618 additions & 195 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pages.yml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,30 @@ jobs:
3131
- name: Install dependencies
3232
run: npm ci
3333

34-
- name: Build packages (react-uswds + gov-components)
34+
- name: Build packages (react-uswds + gov-components + react-ontario)
3535
run: npm run build
3636

3737
- name: Build component-lab
3838
env:
39-
# Sub-path that GitHub Pages serves this repo under.
40-
# Change to / if using a custom domain or org/user Pages site.
4139
COMPONENT_LAB_BASE: /gps-design-systems-react/
4240
run: npm run build --workspace=apps/component-lab
4341

42+
- name: Build ontario-lab
43+
env:
44+
ONTARIO_LAB_BASE: /gps-design-systems-react/ontario-lab/
45+
run: npm run build --workspace=apps/ontario-lab
46+
47+
- name: Merge sites into a single artifact
48+
run: |
49+
mkdir -p _site
50+
cp -r apps/component-lab/dist/. _site/
51+
mkdir -p _site/ontario-lab
52+
cp -r apps/ontario-lab/dist/. _site/ontario-lab/
53+
4454
- name: Upload Pages artifact
4555
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
4656
with:
47-
path: apps/component-lab/dist
57+
path: _site
4858

4959
deploy:
5060
name: Deploy to GitHub Pages

AGENTS.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ packages/
3636
src/
3737
components/ 83 gov-specific React components
3838
39+
react-ontario/ ← Ontario Design System package (@sfgps-ds/react-ontario)
40+
src/
41+
context/ OntarioContext.tsx (OntarioProvider — calls setAssetPath)
42+
utils/ shared utilities
43+
components/ 33 ODS wrappers (Stencil web components)
44+
3945
docs/ All documentation — platform guides and design-system guides
4046
e2e/ Playwright tests against local Vite fixture (no org required)
4147
showcase/ UIBundle showcase app for Salesforce React Experience Cloud
@@ -67,6 +73,7 @@ npm run test --workspaces --if-present # all packages
6773
npm run test --workspace=packages/react-uswds # USWDS unit tests
6874
npm run test --workspace=packages/react-bridge # bridge unit tests
6975
npm run test --workspace=packages/gov-components # gov-components unit tests
76+
npm run test --workspace=packages/react-ontario # Ontario unit tests
7077
npm run test:coverage --workspace=packages/react-uswds # with coverage thresholds
7178
```
7279

@@ -108,7 +115,7 @@ sf org open --target-org BUILD
108115
### Import rules
109116
- Import `@salesforce/agentforce-conversation-client` only from `src/agentforce.ts`, never from `src/index.ts`
110117
- Never import `@salesforce/apex/*`, `@salesforce/label/*`, `@salesforce/schema/*`, `@salesforce/user/*`, `@salesforce/client/*`, `@salesforce/featureFlag/*`, `@salesforce/customPermission/*`, `@salesforce/resourceUrl/*`, or `@salesforce/messageChannel/*` in any React source file — these are LWC compile-time virtual modules and cannot be resolved by npm or any bundler. ESLint rule SB-1 enforces this as a build error.
111-
- Never import `@sfgps-ds/react-uswds` or `@gov-ido/gov-components` from any file in `packages/react-bridge/`. ESLint rule PB-1 enforces this.
118+
- Never import `@sfgps-ds/react-uswds`, `@gov-ido/gov-components`, or `@sfgps-ds/react-ontario` from any file in `packages/react-bridge/`. ESLint rule PB-1 enforces this.
112119
- Never use `JSON.parse(JSON.stringify(` in `packages/react-bridge/`. Use `stripProxy()` instead. ESLint rule SER-2 enforces this.
113120

114121
---
@@ -121,13 +128,14 @@ sf org open --target-org BUILD
121128
- Export Agentforce-specific components from `packages/react-uswds/src/agentforce.ts` (not the main barrel)
122129
- Add platform-agnostic infrastructure (gateways, stores, hooks) to `packages/react-bridge/src/`
123130
- Add gov-specific components under `packages/gov-components/src/components/<ComponentName>/`
131+
- Add Ontario Design System components under `packages/react-ontario/src/components/<ComponentName>/`
124132
- Write Vitest tests alongside source in `src/**/__tests__/`
125133
- Write E2E tests in `e2e/tests/`
126134

127135
### Do not
128136
- Import `@salesforce/agentforce-conversation-client` from the main barrel (`index.ts`) — it belongs in `agentforce.ts` only
129137
- Import `@salesforce/` LWC virtual modules (`@salesforce/apex/*`, `@salesforce/label/*`, etc.) in any React file — LWC compile-time modules; use the gateway abstraction instead (ESLint SB-1)
130-
- Import `@sfgps-ds/react-uswds` or `@gov-ido/gov-components` from `packages/react-bridge/` — bridge is design-system-agnostic (ESLint PB-1)
138+
- Import `@sfgps-ds/react-uswds`, `@gov-ido/gov-components`, or `@sfgps-ds/react-ontario` from `packages/react-bridge/` — bridge is design-system-agnostic (ESLint PB-1)
131139
- Use `JSON.parse(JSON.stringify(` for LWC→React data crossing — use `stripProxy()` instead (ESLint SER-2)
132140
- Call `DOMPurify(window)` at module evaluation time
133141
- Use `document.activeElement` for focus management in React components
@@ -137,6 +145,19 @@ sf org open --target-org BUILD
137145

138146
## Known pre-existing issues
139147
- `safeHtml.test.ts` — all tests pass. If test `[SEC-1D]` regresses, the fix is the `afterSanitizeAttributes` hook in `getPurifier()` inside `src/utils/safeHtml.ts`.
148+
- `OntarioSummaryList` — upstream `@ongov/ontario-design-system-component-library-react` does not yet export `OntarioSummaryList`; 2 import tests in `packages/react-ontario` skip until the upstream ships the symbol.
149+
150+
---
151+
152+
## Ontario Design System (ODS) package architecture
153+
154+
The `packages/react-ontario/` package wraps Stencil web components from `@ongov/ontario-design-system-component-library-react`.
155+
156+
Key constraints:
157+
- **Shadow DOM rendering** — ODS components render inside shadow DOM. Playwright axe tests must use `disableRules` for shadow DOM label/list issues (upstream ODS behavior, not a local bug).
158+
- **OntarioProvider** — must wrap the component tree before any ODS component renders. It calls `setAssetPath` from `@stencil/core/loader` so Stencil can resolve icon and font assets at runtime. Never render ODS components outside an `OntarioProvider`.
159+
- **JSON prop sanitization** — ODS components accept complex props as JSON strings (arrays, objects). Use `useMemo` keyed on individual primitive values, not on the full props object, to avoid unnecessary re-serializations and infinite render loops.
160+
- **Package boundary**`@sfgps-ds/react-ontario` is a design-system package; it must not be imported from `packages/react-bridge/` (ESLint PB-1).
140161

141162
---
142163

README.md

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# @sfgps-ds/react-uswds
1+
# Salesforce GPS Accelerators — React Design System Libraries
22

3-
**USWDS 3.13 React component library for Salesforce Experience Cloud.**
3+
**USWDS 3.13 and Ontario Government Design System React component libraries for Salesforce Experience Cloud.**
44

5-
Built by Salesforce GPS Accelerators, this library brings the full U.S. Web Design System to Salesforce Experience Cloud's native React developer experience. Components deploy directly on Salesforce React Experience Cloud — no LWC bridge required.
5+
Built by Salesforce GPS Accelerators, these libraries bring U.S. and Canadian government design systems to Salesforce Experience Cloud's native React developer experience. Components deploy directly on Salesforce React Experience Cloud — no LWC bridge required.
66

7-
**1,100+ tests** — unit tests across 70+ files
7+
**4,700+ tests** — unit tests and Playwright e2e tests across all packages
88

99
---
1010

@@ -13,13 +13,14 @@ Built by Salesforce GPS Accelerators, this library brings the full U.S. Web Desi
1313
| Layer | What it does |
1414
|---|---|
1515
| **React component library** (`packages/react-uswds/src/`) | 54 pure-React USWDS 3.13 components. No Salesforce dependencies. Publishable to npm as `@sfgps-ds/react-uswds`. |
16+
| **Ontario React component library** (`packages/react-ontario/src/`) | 33 Ontario Government Design System components wrapping `@ongov/ontario-design-system-component-library-react` Stencil web components. No Salesforce dependencies. Publishable to npm as `@sfgps-ds/react-ontario`. |
1617
| **Salesforce React Experience Cloud** | Components deploy directly using Salesforce's native React developer experience — no LWC bridge or IIFE bundle required. See [Salesforce Multi-Framework developer guide](https://developer.salesforce.com/docs/platform/agentforcevibes/guide/afv-reactdev-overview.html). |
1718

1819
---
1920

2021
## Quick start
2122

22-
### Standalone React (Vite, CRA, Next.js)
23+
### `@sfgps-ds/react-uswds` — USWDS (Standalone React: Vite, CRA, Next.js)
2324

2425
```bash
2526
npm install @sfgps-ds/react-uswds dompurify
@@ -41,9 +42,33 @@ function App() {
4142
}
4243
```
4344

45+
### `@sfgps-ds/react-ontario` — Ontario Design System (Standalone React: Vite, CRA, Next.js)
46+
47+
```bash
48+
npm install @sfgps-ds/react-ontario @ongov/ontario-design-system-component-library-react dompurify
49+
```
50+
51+
```tsx
52+
import { OntarioProvider, OntarioButton, OntarioHeader } from "@sfgps-ds/react-ontario";
53+
import "@sfgps-ds/react-ontario/dist/styles.css";
54+
55+
function App() {
56+
return (
57+
<OntarioProvider assetsBasePath="/assets">
58+
<OntarioHeader
59+
applicationHeaderInfo={{ name: "My Application", href: "/" }}
60+
/>
61+
<OntarioButton label="Get started" type="primary" />
62+
</OntarioProvider>
63+
);
64+
}
65+
```
66+
67+
`OntarioProvider` is required — it calls Stencil's `setAssetPath` to wire the web component asset path. Without it, icons and other static assets will not resolve.
68+
4469
### Salesforce React Experience Cloud
4570

46-
Import components directly — no bridge or bundle step required:
71+
Import components directly from either library — no bridge or bundle step required:
4772

4873
```tsx
4974
import { SfGpsDsUsGovProvider, Banner, Alert } from "@sfgps-ds/react-uswds";
@@ -222,7 +247,7 @@ Three lint rules enforce the React/Salesforce boundary at build time:
222247
| Rule | Scope | What it blocks |
223248
|---|---|---|
224249
| **SB-1** | All React source | `@salesforce/apex/*`, `@salesforce/label/*`, `@salesforce/schema/*`, and 6 other LWC virtual modules — not resolvable outside the LWC compile context |
225-
| **PB-1** | `packages/react-bridge/**` | Imports from `@sfgps-ds/react-uswds` or `@gov-ido/gov-components` — the platform adapter layer must stay design-system-agnostic |
250+
| **PB-1** | `packages/react-bridge/**` | Imports from `@sfgps-ds/react-uswds`, `@sfgps-ds/react-ontario`, or `@gov-ido/gov-components` — the platform adapter layer must stay design-system-agnostic |
226251
| **SER-2** | `packages/react-bridge/**` | `JSON.parse(JSON.stringify(` — use `stripProxy()` instead, which correctly handles `Date`, `Map`, `Set`, and `BigInt` |
227252

228253
All three are ESLint errors that fail the build. `@salesforce/agentforce-conversation-client` is a real npm package and is not restricted.
@@ -239,10 +264,11 @@ This section is intended for legal review. It lists every third-party package th
239264

240265
| Package | Version | License | Role | Ships how |
241266
|---|---|---|---|---|
242-
| `react` | 18.3.1 | [MIT](https://github.com/facebook/react/blob/main/LICENSE) | UI rendering engine | Peer dependency — caller provides |
243-
| `react-dom` | 18.3.1 | [MIT](https://github.com/facebook/react/blob/main/LICENSE) | DOM mounting | Peer dependency — caller provides |
267+
| `react` | >=18 (`@sfgps-ds/react-uswds`) / ^19 (`@sfgps-ds/react-ontario`) | [MIT](https://github.com/facebook/react/blob/main/LICENSE) | UI rendering engine | Peer dependency — caller provides |
268+
| `react-dom` | >=18 (`@sfgps-ds/react-uswds`) / ^19 (`@sfgps-ds/react-ontario`) | [MIT](https://github.com/facebook/react/blob/main/LICENSE) | DOM mounting | Peer dependency — caller provides |
244269
| `dompurify` | 3.2.6 | [MPL-2.0 OR Apache-2.0](https://github.com/cure53/DOMPurify/blob/main/LICENSE) | HTML sanitization for rich-text props (`Alert.body`, `Card.body`, etc.) | Peer dependency — caller provides; externalized from tsup bundle |
245270
| `@uswds/uswds` | 3.13.0 | [CC0 / Apache-2.0 / SIL OFL 1.1 / MIT (see note)](https://github.com/uswds/uswds/blob/main/LICENSE.md) | USWDS CSS, fonts (Source Sans Pro, Merriweather, Public Sans), icons, and SVG sprite | Served as static assets; not bundled into JS |
271+
| `@ongov/ontario-design-system-component-library-react` | ^8.1.0 | [MIT](https://github.com/ongov/ontario-design-system-component-library/blob/main/LICENSE) | Ontario Government Design System Stencil web components | Peer dependency for `@sfgps-ds/react-ontario` — caller provides |
246272
| `@salesforce/agentforce-conversation-client` | 10.2.0 | [Salesforce Terms of Use](https://github.com/salesforce/agentforce-conversation-client) | Agentforce chat widget SDK (`AgentforceWidget` component) | Peer dependency for `@sfgps-ds/react-uswds/agentforce` sub-path only; excluded from the main bundle |
247273

248274
### License notes for legal review

apps/ontario-lab/index.html

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<title>Ontario Component Lab</title>
7+
</head>
8+
<body>
9+
<div id="root"></div>
10+
<script type="module" src="/src/main.tsx"></script>
11+
</body>
12+
</html>

apps/ontario-lab/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "@gov-ido/ontario-lab",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"dev": "vite --port 4174 --host 127.0.0.1",
7+
"build": "vite build",
8+
"preview": "vite preview --port 4174 --host 127.0.0.1"
9+
},
10+
"dependencies": {
11+
"@ongov/ontario-design-system-component-library-react": "*",
12+
"@ongov/ontario-design-system-global-styles": "*",
13+
"@sfgps-ds/react-ontario": "*",
14+
"react": "^19.2.4",
15+
"react-dom": "^19.2.4",
16+
"react-router-dom": "^7.0.0"
17+
},
18+
"devDependencies": {
19+
"@types/node": "^24.0.0",
20+
"@types/react": "^19.0.0",
21+
"@types/react-dom": "^19.0.0",
22+
"@vitejs/plugin-react": "^6.0.0",
23+
"typescript": "~5.8.0",
24+
"vite": "^8.0.0"
25+
}
26+
}

apps/ontario-lab/src/DemoShell.tsx

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { useState } from 'react';
2+
import { useLocation } from 'react-router-dom';
3+
import { NAV_ENTRIES } from './nav-registry';
4+
5+
interface DemoShellProps {
6+
children: React.ReactNode;
7+
}
8+
9+
type Tab = 'preview' | 'source';
10+
11+
export default function DemoShell({ children }: DemoShellProps) {
12+
const [tab, setTab] = useState<Tab>('preview');
13+
const [copied, setCopied] = useState(false);
14+
const location = useLocation();
15+
16+
const entry = NAV_ENTRIES.find((e) => e.path === location.pathname);
17+
18+
function handleCopy() {
19+
if (!entry) return;
20+
navigator.clipboard.writeText(entry.source).then(() => {
21+
setCopied(true);
22+
setTimeout(() => setCopied(false), 1500);
23+
});
24+
}
25+
26+
return (
27+
<div className="cl-demo">
28+
<div className="cl-demo__header">
29+
<h1 className="cl-demo__title">{entry?.label ?? ''}</h1>
30+
<span className="cl-badge cl-badge--ontario">react-ontario</span>
31+
</div>
32+
33+
<div className="cl-demo__tabs" role="tablist" aria-label="Demo tabs">
34+
<button
35+
role="tab"
36+
id="tab-preview"
37+
aria-controls="panel-preview"
38+
aria-selected={tab === 'preview'}
39+
className={`cl-demo__tab${tab === 'preview' ? ' cl-demo__tab--active' : ''}`}
40+
onClick={() => setTab('preview')}
41+
>
42+
Preview
43+
</button>
44+
<button
45+
role="tab"
46+
id="tab-source"
47+
aria-controls="panel-source"
48+
aria-selected={tab === 'source'}
49+
className={`cl-demo__tab${tab === 'source' ? ' cl-demo__tab--active' : ''}`}
50+
onClick={() => setTab('source')}
51+
>
52+
Source
53+
</button>
54+
</div>
55+
56+
<div
57+
role="tabpanel"
58+
id="panel-preview"
59+
aria-labelledby="tab-preview"
60+
hidden={tab !== 'preview'}
61+
className="cl-demo__panel cl-demo__panel--preview"
62+
>
63+
{children}
64+
</div>
65+
66+
<div
67+
role="tabpanel"
68+
id="panel-source"
69+
aria-labelledby="tab-source"
70+
hidden={tab !== 'source'}
71+
className="cl-demo__panel cl-demo__panel--source"
72+
>
73+
<div className="cl-source__toolbar">
74+
<span className="cl-source__filename">
75+
{entry?.path.split('/').pop()}Page.tsx
76+
</span>
77+
<button
78+
className="cl-source__copy-btn"
79+
onClick={handleCopy}
80+
aria-label="Copy source code"
81+
>
82+
{copied ? '✓ Copied' : 'Copy'}
83+
</button>
84+
</div>
85+
<pre className="cl-source__pre" tabIndex={0}>
86+
<code>{entry?.source ?? ''}</code>
87+
</pre>
88+
</div>
89+
</div>
90+
);
91+
}

0 commit comments

Comments
 (0)