@flamingo-stack/openframe-frontend-core 0.0.668-snapshot.20260925231935 → 0.0.668

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.
@@ -12,17 +12,7 @@
12
12
  * server answers as one flat, grouped, counted list.
13
13
  */
14
14
  import { type ReactNode } from 'react';
15
- import { type TrustCenterAiPractice, type TrustCenterControlsPage, type TrustCenterDocument, type TrustCenterFramework, type TrustCenterSectionId, type TrustCenterSubprocessor } from '../../types/trust-center';
16
- /**
17
- * The page body's layout, ONE copy for the loaded page and its skeleton, so the
18
- * skeleton cannot drift from what replaces it: the status row, then the sections
19
- * beside the section rail.
20
- */
21
- export declare const TRUST_STATUS_ROW_CLASS = "flex flex-col gap-[var(--spacing-system-xs)] md:flex-row md:items-center md:justify-between";
22
- export declare const TRUST_BODY_GRID_CLASS = "grid grid-cols-1 gap-[var(--spacing-system-xl)] lg:grid-cols-[minmax(0,1fr)_12rem]";
23
- export declare const TRUST_SECTIONS_COLUMN_CLASS = "flex min-w-0 flex-col gap-[var(--spacing-system-xxl)]";
24
- /** Each section's one-line lead under its heading (the page and its skeleton read the same copy). */
25
- export declare const TRUST_SECTION_LEADS: Partial<Record<TrustCenterSectionId, string>>;
15
+ import { type TrustCenterAiPractice, type TrustCenterControlsPage, type TrustCenterDocument, type TrustCenterFramework, type TrustCenterSubprocessor } from '../../types/trust-center';
26
16
  /** One anchored page section: an `h2` + optional lead line + content. */
27
17
  export declare function TrustSection({ id, title, lead, children, }: {
28
18
  id: string;
@@ -94,11 +84,6 @@ export declare function SubprocessorsSection({ subprocessors }: {
94
84
  export declare function ContactSection({ onContact }: {
95
85
  onContact: () => void;
96
86
  }): import("react").JSX.Element;
97
- /**
98
- * The page BODY while the trust center loads: built on the loaded body's own
99
- * layout classes and the real section headings and panels, so nothing moves when
100
- * the content replaces it. The status row, then Compliance, Controls and
101
- * Documents (what fills the first screens), beside the section rail.
102
- */
87
+ /** Mirrors the page: a card row, then the controls grid. */
103
88
  export declare function TrustCenterSkeleton(): import("react").JSX.Element;
104
89
  //# sourceMappingURL=trust-center-sections.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"trust-center-sections.d.ts","sourceRoot":"","sources":["../../../src/components/help-center-pages/trust-center-sections.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGhE,OAAO,EAOL,KAAK,qBAAqB,EAE1B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAE7B,MAAM,0BAA0B,CAAC;AAiClC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,gGAC4D,CAAC;AAChG,eAAO,MAAM,qBAAqB,uFACoD,CAAC;AACvF,eAAO,MAAM,2BAA2B,0DAA0D,CAAC;AAEnG,qGAAqG;AACrG,eAAO,MAAM,mBAAmB,EAAE,OAAO,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAM7E,CAAC;AAWF,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,EAC3B,EAAE,EACF,KAAK,EACL,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAkBA;AAMD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,qBAAqB,EAAE,CAAA;CAAE,+BAkB9E;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,oBAAoB,EAAE,CAAA;CAAE,+BA0BvF;AAkED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,uBAAuB,CAAA;CAAE,+BAkFtG;AAMD,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,WAAW,EACX,SAAS,GACV,EAAE;IACD,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,kGAAkG;IAClG,WAAW,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,CAAC;IAC9D,oEAAoE;IACpE,SAAS,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CACnD,+BA0CA;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,OAAO,GACR,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,6GAA6G;IAC7G,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAgCA;AA8BD,wBAAgB,oBAAoB,CAAC,EAAE,aAAa,EAAE,EAAE;IAAE,aAAa,EAAE,uBAAuB,EAAE,CAAA;CAAE,+BA4BnG;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,IAAI,CAAA;CAAE,+BA8BtE;AAoGD;;;;;GAKG;AACH,wBAAgB,mBAAmB,gCAmClC"}
1
+ {"version":3,"file":"trust-center-sections.d.ts","sourceRoot":"","sources":["../../../src/components/help-center-pages/trust-center-sections.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAA2B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGhE,OAAO,EAML,KAAK,qBAAqB,EAE1B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,uBAAuB,EAE7B,MAAM,0BAA0B,CAAC;AA4ClC,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,EAC3B,EAAE,EACF,KAAK,EACL,IAAI,EACJ,QAAQ,GACT,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAkBA;AAMD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,qBAAqB,EAAE,CAAA;CAAE,+BAkB9E;AAMD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,UAAU,EAAE,oBAAoB,EAAE,CAAA;CAAE,+BA0BvF;AAkED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,uBAAuB,CAAA;CAAE,+BAkFtG;AAMD,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,WAAW,EACX,SAAS,GACV,EAAE;IACD,SAAS,EAAE,mBAAmB,EAAE,CAAC;IACjC,kGAAkG;IAClG,WAAW,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,CAAC;IAC9D,oEAAoE;IACpE,SAAS,EAAE,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CACnD,+BA0CA;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,EACnC,IAAI,EACJ,aAAa,EACb,QAAQ,EACR,OAAO,GACR,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,6GAA6G;IAC7G,QAAQ,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,+BAgCA;AA8BD,wBAAgB,oBAAoB,CAAC,EAAE,aAAa,EAAE,EAAE;IAAE,aAAa,EAAE,uBAAuB,EAAE,CAAA;CAAE,+BA4BnG;AAMD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,IAAI,CAAA;CAAE,+BA8BtE;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,gCAOlC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.668-snapshot.20260925231935",
3
+ "version": "0.0.668",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -14,8 +14,7 @@ import {
14
14
  type TrustCenterPublic,
15
15
  } from '../../../types/trust-center';
16
16
  import { TRUST_CENTER_FIXTURE_WINDOW_MS, makeTrustCenterData } from '../__fixtures__/trust-center';
17
- import { TrustCenterPage, TrustCenterPageSkeleton } from '../trust-center-page';
18
- import { TRUST_SECTION_LEADS } from '../trust-center-sections';
17
+ import { TrustCenterPage } from '../trust-center-page';
19
18
 
20
19
  // The real ContactForm needs the endpoints + chat runtimes; the page only
21
20
  // decides WHAT it is handed, so a stub that echoes its props is the honest
@@ -115,32 +114,6 @@ describe('TrustCenterPage', () => {
115
114
  expect(String(fetchMock.mock.calls[0][0])).toBe('/content/api/trust-center');
116
115
  });
117
116
 
118
- it('while it loads, the skeleton shows the real section headings and leads the content will fill', async () => {
119
- let answer: (response: Response) => void = () => {};
120
- fetchMock.mockReturnValueOnce(new Promise<Response>(resolve => (answer = resolve)));
121
- render(<TrustCenterPage endpoint="/content/api/trust-center" />);
122
- const loading = await screen.findByLabelText('Loading the trust center');
123
- for (const [label, lead] of [
124
- ['Compliance', TRUST_SECTION_LEADS.compliance],
125
- ['Controls', TRUST_SECTION_LEADS.controls],
126
- ['Documents', TRUST_SECTION_LEADS.documents],
127
- ] as const) {
128
- expect(within(loading).getByRole('heading', { level: 2, name: label, hidden: true })).toBeInTheDocument();
129
- expect(within(loading).getByText(lead as string)).toBeInTheDocument();
130
- }
131
- act(() => answer(jsonResponse(makeData())));
132
- expect(await screen.findByText('SOC 2 Type II')).toBeInTheDocument();
133
- expect(screen.queryByLabelText('Loading the trust center')).toBeNull();
134
- });
135
-
136
- it('TrustCenterPageSkeleton wears the page chrome it is given: no title and subtitle means no header at all', () => {
137
- const { rerender } = render(<TrustCenterPageSkeleton shell={false} title="" subtitle="" />);
138
- expect(screen.queryByRole('heading', { level: 1 })).toBeNull();
139
- expect(screen.getByLabelText('Loading the trust center')).toBeInTheDocument();
140
- rerender(<TrustCenterPageSkeleton shell={false} />);
141
- expect(screen.getByRole('heading', { level: 1, name: TRUST_CENTER_TITLE })).toBeInTheDocument();
142
- });
143
-
144
117
  it('is ONE page: every section is an anchored h2, in reading order — AI & data use after Subprocessors, Contact before FAQ', () => {
145
118
  render(<TrustCenterPage initialData={makeData()} />);
146
119
  const headings = screen.getAllByRole('heading', { level: 2 }).map(h => h.textContent);
@@ -21,7 +21,7 @@ export { RoadmapPage, type RoadmapPageProps } from './roadmap-page';
21
21
  export { ProductReleasesListPage, type ProductReleasesListPageProps } from './product-releases-list-page';
22
22
  export { DeliveryPage, type DeliveryPageProps } from './delivery-page';
23
23
  export { OnboardingGuidesCatalogPage, type OnboardingGuidesCatalogPageProps } from './onboarding-guides-catalog-page';
24
- export { TrustCenterPage, TrustCenterPageSkeleton, type TrustCenterPageProps } from './trust-center-page';
24
+ export { TrustCenterPage, type TrustCenterPageProps } from './trust-center-page';
25
25
 
26
26
  // Existing full-pages re-exported for a single Help Center import site.
27
27
  export { FaqDocumentPage, type FaqDocumentPageProps } from '../faq';
@@ -61,10 +61,6 @@ import {
61
61
  DocumentRequestModal,
62
62
  DocumentsSection,
63
63
  SubprocessorsSection,
64
- TRUST_BODY_GRID_CLASS,
65
- TRUST_SECTIONS_COLUMN_CLASS,
66
- TRUST_SECTION_LEADS,
67
- TRUST_STATUS_ROW_CLASS,
68
64
  TrustCenterSkeleton,
69
65
  TrustSection,
70
66
  } from './trust-center-sections';
@@ -150,6 +146,7 @@ export function TrustCenterPage({
150
146
  title = TRUST_CENTER_TITLE,
151
147
  subtitle = TRUST_CENTER_TAGLINE,
152
148
  }: TrustCenterPageProps) {
149
+ const router = useRouter();
153
150
  const { data, isLoading, error, reload } = useSelfFetch<TrustCenterPublic>(endpoint, {
154
151
  initialData,
155
152
  revalidateOnVisibleAfterMs: TRUST_CENTER_CACHE_SECONDS * 1000,
@@ -194,6 +191,11 @@ export function TrustCenterPage({
194
191
  const openRequest = useCallback((documentTitle: string | null) => setRequest({ open: true, documentTitle }), []);
195
192
  const closeRequest = useCallback(() => setRequest(current => ({ ...current, open: false })), []);
196
193
 
194
+ const backCfg =
195
+ backButton === false
196
+ ? undefined
197
+ : { label: backButton.label ?? 'Back to home', onClick: () => router.push(backButton.href ?? '/') };
198
+
197
199
  const hasGatedDocuments = data?.documents.some(document => document.access === 'request') ?? false;
198
200
  const actions = hasGatedDocuments
199
201
  ? [{ label: 'Request access', variant: 'accent' as const, onClick: () => openRequest(null) }]
@@ -203,19 +205,19 @@ export function TrustCenterPage({
203
205
  // sections show comes from `visibleSections` — this map only says what each renders.
204
206
  const views: Record<TrustCenterSectionId, TrustSectionView> = {
205
207
  ai: {
206
- lead: TRUST_SECTION_LEADS.ai,
208
+ lead: 'How customer data is handled by the AI in our products.',
207
209
  render: d => <AiSection practices={d.aiPractices} />,
208
210
  },
209
211
  compliance: {
210
- lead: TRUST_SECTION_LEADS.compliance,
212
+ lead: 'Frameworks we are audited against, and what is next.',
211
213
  render: d => <ComplianceSection frameworks={d.frameworks} />,
212
214
  },
213
215
  controls: {
214
- lead: TRUST_SECTION_LEADS.controls,
216
+ lead: 'Security controls currently passing in continuous monitoring (via Vanta).',
215
217
  render: d => <ControlsSection endpoint={endpoint} seed={controlsSeed(d)} />,
216
218
  },
217
219
  documents: {
218
- lead: TRUST_SECTION_LEADS.documents,
220
+ lead: 'Public documents open directly. Gated documents are shared under NDA after one short request.',
219
221
  render: d => (
220
222
  <DocumentsSection
221
223
  documents={d.documents}
@@ -225,7 +227,7 @@ export function TrustCenterPage({
225
227
  ),
226
228
  },
227
229
  subprocessors: {
228
- lead: TRUST_SECTION_LEADS.subprocessors,
230
+ lead: 'Third parties that process customer data on our behalf.',
229
231
  render: d => <SubprocessorsSection subprocessors={d.subprocessors} />,
230
232
  },
231
233
  faq: { render: d => <FaqSection initialFaqs={d.faqs} heading={null} /> },
@@ -241,7 +243,7 @@ export function TrustCenterPage({
241
243
  const status = monitoringStatus(data, hydrated, nowMs);
242
244
  body = (
243
245
  <>
244
- <div className={TRUST_STATUS_ROW_CLASS}>
246
+ <div className="flex flex-col gap-[var(--spacing-system-xs)] md:flex-row md:items-center md:justify-between">
245
247
  <StatusIndicator status={status.status} label={status.label} />
246
248
  {data.connected ? (
247
249
  <DataAttribution
@@ -261,8 +263,8 @@ export function TrustCenterPage({
261
263
  ) : null}
262
264
  </div>
263
265
 
264
- <div className={TRUST_BODY_GRID_CLASS}>
265
- <div className={TRUST_SECTIONS_COLUMN_CLASS}>
266
+ <div className="grid grid-cols-1 gap-[var(--spacing-system-xl)] lg:grid-cols-[minmax(0,1fr)_12rem]">
267
+ <div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxl)]">
266
268
  {sections.map(section => {
267
269
  const view = views[section.id];
268
270
  return (
@@ -294,34 +296,6 @@ export function TrustCenterPage({
294
296
  );
295
297
  }
296
298
 
297
- return (
298
- <TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton} actions={actions}>
299
- {body}
300
- </TrustCenterChrome>
301
- );
302
- }
303
-
304
- /**
305
- * The page's chrome, ONE copy for the page and its skeleton: the standalone
306
- * `PageShell` or the host's padded box (`shell`), and the frozen `PageLayout`
307
- * header (title, subtitle, back button, actions).
308
- */
309
- function TrustCenterChrome({
310
- shell,
311
- title,
312
- subtitle,
313
- backButton,
314
- actions,
315
- children,
316
- }: Required<Pick<TrustCenterPageProps, 'shell' | 'title' | 'subtitle' | 'backButton'>> & {
317
- actions?: Array<{ label: string; variant: 'accent'; onClick: () => void }>;
318
- children: ReactNode;
319
- }) {
320
- const router = useRouter();
321
- const backCfg =
322
- backButton === false
323
- ? undefined
324
- : { label: backButton.label ?? 'Back to home', onClick: () => router.push(backButton.href ?? '/') };
325
299
  const inner = (
326
300
  <PageLayout
327
301
  title={title}
@@ -332,26 +306,9 @@ function TrustCenterChrome({
332
306
  actions={actions}
333
307
  actionsVariant="primary-buttons"
334
308
  >
335
- {children}
309
+ {body}
336
310
  </PageLayout>
337
311
  );
338
- return shell ? <PageShell>{inner}</PageShell> : <div className="page-shell-content">{inner}</div>;
339
- }
340
312
 
341
- /**
342
- * The whole page while a host loads the data itself (the hub's admin preview):
343
- * the SAME chrome and the same body skeleton `TrustCenterPage` shows while it
344
- * self-fetches. Pass the chrome props the page will get.
345
- */
346
- export function TrustCenterPageSkeleton({
347
- shell = true,
348
- backButton = false,
349
- title = TRUST_CENTER_TITLE,
350
- subtitle = TRUST_CENTER_TAGLINE,
351
- }: Pick<TrustCenterPageProps, 'shell' | 'backButton' | 'title' | 'subtitle'>) {
352
- return (
353
- <TrustCenterChrome shell={shell} title={title} subtitle={subtitle} backButton={backButton}>
354
- <TrustCenterSkeleton />
355
- </TrustCenterChrome>
356
- );
313
+ return shell ? <PageShell>{inner}</PageShell> : <div className="page-shell-content">{inner}</div>;
357
314
  }
@@ -19,7 +19,6 @@ import { useDebounce } from '../../hooks/ui/use-debounce';
19
19
  import { useSelfFetch } from '../../hooks/use-self-fetch';
20
20
  import {
21
21
  TRUST_CENTER_SEARCH_DEBOUNCE_MS,
22
- TRUST_CENTER_SECTIONS,
23
22
  normalizeTrustControlQuery,
24
23
  trustCenterControlsUrl,
25
24
  trustFrameworkBadge,
@@ -29,7 +28,6 @@ import {
29
28
  type TrustCenterControlsPage,
30
29
  type TrustCenterDocument,
31
30
  type TrustCenterFramework,
32
- type TrustCenterSectionId,
33
31
  type TrustCenterSubprocessor,
34
32
  type TrustFrameworkMonitoringEntry,
35
33
  } from '../../types/trust-center';
@@ -45,7 +43,8 @@ import { ShieldCheckIcon } from '../icons-v2-generated/security/shield-check-ico
45
43
  import { CheckIcon } from '../icons-v2-generated/signs-and-symbols/check-icon';
46
44
  import { SECTION_HEADING_CLASS } from '../layout/page-heading';
47
45
  import { ListEmptyState } from '../list-empty-state';
48
- import { TextSkeleton, UnifiedSkeleton } from '../loading/unified-skeleton';
46
+ import { CardSkeletonGrid } from '../loading/card-skeleton';
47
+ import { TextSkeleton } from '../loading/unified-skeleton';
49
48
  import { Button } from '../ui/button/button';
50
49
  import { ComplianceLogo } from '../ui/compliance-badge';
51
50
  import { EntityImage } from '../ui/entity-image';
@@ -63,27 +62,8 @@ const FRAMEWORK_ICONS: Record<TrustFrameworkMonitoringEntry['icon'], typeof Shie
63
62
  'file-shield': FileShieldIcon,
64
63
  };
65
64
 
65
+ const STACK_CLASSES = 'flex flex-col gap-[var(--spacing-system-l)]';
66
66
  const BODY_TEXT = 'text-h6 text-ods-text-secondary';
67
-
68
- /**
69
- * The page body's layout, ONE copy for the loaded page and its skeleton, so the
70
- * skeleton cannot drift from what replaces it: the status row, then the sections
71
- * beside the section rail.
72
- */
73
- export const TRUST_STATUS_ROW_CLASS =
74
- 'flex flex-col gap-[var(--spacing-system-xs)] md:flex-row md:items-center md:justify-between';
75
- export const TRUST_BODY_GRID_CLASS =
76
- 'grid grid-cols-1 gap-[var(--spacing-system-xl)] lg:grid-cols-[minmax(0,1fr)_12rem]';
77
- export const TRUST_SECTIONS_COLUMN_CLASS = 'flex min-w-0 flex-col gap-[var(--spacing-system-xxl)]';
78
-
79
- /** Each section's one-line lead under its heading (the page and its skeleton read the same copy). */
80
- export const TRUST_SECTION_LEADS: Partial<Record<TrustCenterSectionId, string>> = {
81
- compliance: 'Frameworks we are audited against, and what is next.',
82
- controls: 'Security controls currently passing in continuous monitoring (via Vanta).',
83
- documents: 'Public documents open directly. Gated documents are shared under NDA after one short request.',
84
- subprocessors: 'Third parties that process customer data on our behalf.',
85
- ai: 'How customer data is handled by the AI in our products.',
86
- };
87
67
  /**
88
68
  * The right-hand action / status column: ONE width from tablet up, so every
89
69
  * panel's rows line up; on phones it hugs its content so the title keeps the room.
@@ -560,143 +540,12 @@ export function ContactSection({ onContact }: { onContact: () => void }) {
560
540
  );
561
541
  }
562
542
 
563
- // ---------------------------------------------------------------------------
564
- // Skeleton
565
- // ---------------------------------------------------------------------------
566
-
567
- /** A section's heading and lead, exactly as `TrustSection` renders them (real text: it does not load). */
568
- function SkeletonSection({ id, children }: { id: TrustCenterSectionId; children: ReactNode }) {
569
- const label = TRUST_CENTER_SECTIONS.find(section => section.id === id)?.label ?? id;
570
- const lead = TRUST_SECTION_LEADS[id];
571
- return (
572
- <section aria-hidden="true" className="flex flex-col gap-[var(--spacing-system-m)]">
573
- <div className="flex min-w-0 flex-col gap-[var(--spacing-system-xxs)]">
574
- <h2 className={SECTION_HEADING_CLASS}>{label}</h2>
575
- {lead ? <p className={BODY_TEXT}>{lead}</p> : null}
576
- </div>
577
- {children}
578
- </section>
579
- );
580
- }
581
-
582
- /**
583
- * Rows of the REAL `StackedRowsPanel` (its own row heights and borders) whose
584
- * cells hold bars: a mark (a 40px compliance logo or a 24px glyph), a value over
585
- * a label, and the right-hand badge or button in the page's `ACTION_COLUMN`.
586
- */
587
- function skeletonRows(count: number, shape: { mark: 'logo' | 'glyph'; action: 'badge' | 'button' }): PanelRow[] {
588
- return Array.from({ length: count }, (_, index) => ({
589
- id: `skeleton-${index}`,
590
- columns: [
591
- {
592
- key: 'item',
593
- content: (
594
- <div className="flex w-full items-center gap-[var(--spacing-system-s)]">
595
- <UnifiedSkeleton
596
- className={shape.mark === 'logo' ? 'size-10 shrink-0 rounded-full' : 'size-6 shrink-0 rounded'}
597
- />
598
- <div className="flex min-w-0 flex-1 flex-col gap-[var(--spacing-system-xxs)]">
599
- <UnifiedSkeleton variant="text" className="h-5 w-1/3 md:h-6" />
600
- <TextSkeleton.Body className="w-1/2" />
601
- </div>
602
- </div>
603
- ),
604
- },
605
- {
606
- key: 'action',
607
- width: ACTION_COLUMN,
608
- align: 'right',
609
- content:
610
- shape.action === 'badge' ? (
611
- <UnifiedSkeleton className="h-4 w-28 rounded" />
612
- ) : (
613
- <UnifiedSkeleton className="h-6 w-16 rounded md:h-8" />
614
- ),
615
- },
616
- ],
617
- }));
618
- }
619
-
620
- /** The Controls section while loading: its tab strip, its summary and search, and the list box's own skeleton. */
621
- function ControlsSectionSkeleton() {
622
- return (
623
- <div className="flex flex-col gap-[var(--spacing-system-m)]">
624
- <div className="flex items-center gap-[var(--spacing-system-xxs)] overflow-hidden">
625
- {[0, 1, 2, 3].map(index => (
626
- <div key={index} className="shrink-0 p-[var(--spacing-system-m)]">
627
- <UnifiedSkeleton variant="text" className="h-5 w-36 md:h-6" />
628
- </div>
629
- ))}
630
- </div>
631
- <div className="flex flex-col gap-[var(--spacing-system-s)] md:flex-row md:items-center md:justify-between">
632
- <TextSkeleton.Body className="w-48" />
633
- <UnifiedSkeleton className="h-11 w-full rounded-[6px] md:h-12 md:w-80" />
634
- </div>
635
- <div className={CONTROLS_LIST_HEIGHT}>
636
- <ControlsListSkeleton />
637
- </div>
638
- </div>
639
- );
640
- }
641
-
642
- /** The section rail while loading: `StickySectionNav`'s line and its 40px items. */
643
- function SectionRailSkeleton() {
644
- return (
645
- <div className="sticky" style={{ top: STICKY_HEADER_OFFSET_PX }}>
646
- <div className="relative">
647
- <div
648
- className="absolute -left-0.5 top-0 w-px bg-ods-border"
649
- style={{ height: TRUST_CENTER_SECTIONS.length * 40 }}
650
- />
651
- {TRUST_CENTER_SECTIONS.map(section => (
652
- <div key={section.id} className="flex h-10 items-center px-3 py-2">
653
- <TextSkeleton.Body className="w-24" />
654
- </div>
655
- ))}
656
- </div>
657
- </div>
658
- );
659
- }
660
-
661
- /**
662
- * The page BODY while the trust center loads: built on the loaded body's own
663
- * layout classes and the real section headings and panels, so nothing moves when
664
- * the content replaces it. The status row, then Compliance, Controls and
665
- * Documents (what fills the first screens), beside the section rail.
666
- */
543
+ /** Mirrors the page: a card row, then the controls grid. */
667
544
  export function TrustCenterSkeleton() {
668
545
  return (
669
- <>
670
- <div className={TRUST_STATUS_ROW_CLASS} aria-hidden="true">
671
- <div className="flex items-center gap-1.5">
672
- <UnifiedSkeleton className="size-3 rounded-full" />
673
- <TextSkeleton.Body className="w-44" />
674
- </div>
675
- {/* `DataAttribution`'s own layout: two lines below `sm`, one row above it. */}
676
- <div className="flex shrink-0 flex-col gap-[var(--spacing-system-xxs)] sm:flex-row sm:items-center sm:gap-[var(--spacing-system-sf)]">
677
- <div className="flex items-center gap-[var(--spacing-system-xsf)]">
678
- <UnifiedSkeleton className="size-4 rounded" />
679
- <TextSkeleton.Body className="w-36" />
680
- </div>
681
- <TextSkeleton.Body className="w-44" />
682
- </div>
683
- </div>
684
- <div className={TRUST_BODY_GRID_CLASS} aria-busy="true" aria-label="Loading the trust center">
685
- <div className={TRUST_SECTIONS_COLUMN_CLASS}>
686
- <SkeletonSection id="compliance">
687
- <StackedRowsPanel rows={skeletonRows(3, { mark: 'logo', action: 'badge' })} />
688
- </SkeletonSection>
689
- <SkeletonSection id="controls">
690
- <ControlsSectionSkeleton />
691
- </SkeletonSection>
692
- <SkeletonSection id="documents">
693
- <StackedRowsPanel rows={skeletonRows(2, { mark: 'glyph', action: 'button' })} />
694
- </SkeletonSection>
695
- </div>
696
- <aside className="hidden lg:block" aria-hidden="true">
697
- <SectionRailSkeleton />
698
- </aside>
699
- </div>
700
- </>
546
+ <div className={STACK_CLASSES}>
547
+ <CardSkeletonGrid count={2} variant="category" />
548
+ <CardSkeletonGrid count={4} variant="category" />
549
+ </div>
701
550
  );
702
551
  }