Build compliance portal home page sections
Add the frameworks (Compliance), security commitments, trusted-by, and recent updates sections to the home page, plus a site-wide "Powered by Probo" footer. Frameworks, references, and updates are relay-driven; the security commitments section uses placeholder POJO data until a backend exists. Keep generic primitives in the v2 kit (ProboLogo, a faint Text tone, and a soft Card variant with padding="none") and place the product-specific cards under the app's components folder, composing the base Card. Resolve each framework's themed logo in the graph via Framework.themedLogoUrl, mirroring the trust center logo resolver. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -13,7 +13,16 @@
|
||||
"signOut": "Sign out"
|
||||
},
|
||||
"home": {
|
||||
"heroTitle": "Trust at {{name}}."
|
||||
"heroTitle": "Trust at {{name}}.",
|
||||
"heroDescription": "Welcome to our Compliance Portal. Find our security documentation and certifications here.",
|
||||
"sections": {
|
||||
"compliance": "Compliance",
|
||||
"trustedBy": "Trusted by",
|
||||
"recentUpdates": "Recent updates"
|
||||
},
|
||||
"recentUpdates": {
|
||||
"viewAll": "View all"
|
||||
}
|
||||
},
|
||||
"documents": {
|
||||
"title": "Documents"
|
||||
@@ -33,5 +42,8 @@
|
||||
"title": "Page not found",
|
||||
"description": "The page you are looking for does not exist or has moved.",
|
||||
"backHome": "Back to home"
|
||||
},
|
||||
"footer": {
|
||||
"poweredBy": "Powered by"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,16 @@
|
||||
"signOut": "Se déconnecter"
|
||||
},
|
||||
"home": {
|
||||
"heroTitle": "La confiance chez {{name}}."
|
||||
"heroTitle": "La confiance chez {{name}}.",
|
||||
"heroDescription": "Bienvenue dans notre Compliance Portal. Retrouvez ici notre documentation et nos certifications de sécurité.",
|
||||
"sections": {
|
||||
"compliance": "Conformité",
|
||||
"trustedBy": "Ils nous font confiance",
|
||||
"recentUpdates": "Mises à jour récentes"
|
||||
},
|
||||
"recentUpdates": {
|
||||
"viewAll": "Voir tout"
|
||||
}
|
||||
},
|
||||
"documents": {
|
||||
"title": "Documents"
|
||||
@@ -33,5 +42,8 @@
|
||||
"title": "Page introuvable",
|
||||
"description": "La page que vous recherchez n'existe pas ou a été déplacée.",
|
||||
"backHome": "Retour à l'accueil"
|
||||
},
|
||||
"footer": {
|
||||
"poweredBy": "Propulsé par"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Card } from "@probo/ui/src/v2/Card/Card";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { dotPatternStyle } from "#/components/MediaTile/variants";
|
||||
|
||||
import { commitmentCard } from "./variants";
|
||||
|
||||
export interface CommitmentCardProps {
|
||||
// Leading icon shown at 32px over the dotted backdrop.
|
||||
icon: ReactNode;
|
||||
// Small accent eyebrow above the title.
|
||||
eyebrow: ReactNode;
|
||||
// Card heading.
|
||||
title: ReactNode;
|
||||
// Supporting body copy.
|
||||
description: ReactNode;
|
||||
}
|
||||
|
||||
// Commitment card, composing the soft Card surface. Region props are placed into
|
||||
// the layout slots; the consumer supplies typography (Text). Purely
|
||||
// presentational, so it doubles as the layout for its skeleton.
|
||||
export function CommitmentCard({ icon, eyebrow, title, description }: CommitmentCardProps) {
|
||||
const slots = commitmentCard();
|
||||
|
||||
return (
|
||||
<Card variant="soft" size={3} padding="none">
|
||||
<div className={slots.icon()}>
|
||||
<div className={slots.backdrop()} style={dotPatternStyle} />
|
||||
<div className={slots.backdropFade()} />
|
||||
<div className={slots.iconContent()}>{icon}</div>
|
||||
</div>
|
||||
<div className={slots.body()}>
|
||||
{eyebrow}
|
||||
{title}
|
||||
{description}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
|
||||
import { CommitmentCard } from "./CommitmentCard";
|
||||
|
||||
// Loading placeholder paired with CommitmentCard: renders the same layout with a
|
||||
// pulse icon and skeleton text lines.
|
||||
export function CommitmentCardSkeleton() {
|
||||
return (
|
||||
<CommitmentCard
|
||||
icon={<div className="size-full animate-pulse rounded-2 bg-sand-3" />}
|
||||
eyebrow={<TextSkeleton size={1} className="w-20" />}
|
||||
title={<TextSkeleton size={4} className="w-48" />}
|
||||
description={<TextSkeleton size={2} className="w-full" />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// Commitment card (Figma "Commitment Card"): an icon container with the dotted
|
||||
// + gradient backdrop above an eyebrow / title / description body, rendered
|
||||
// inside a soft Card. Slots are shared by the shell and its skeleton.
|
||||
export const commitmentCard = tv({
|
||||
slots: {
|
||||
icon: "relative flex w-full items-center overflow-hidden p-8",
|
||||
backdrop: "pointer-events-none absolute inset-0",
|
||||
backdropFade: "pointer-events-none absolute inset-0 bg-linear-to-b from-sand-1/0 to-sand-1",
|
||||
iconContent: "relative z-10 flex size-8 items-center justify-center text-gold-9",
|
||||
body: "flex flex-col gap-2 px-8 pb-8",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { complianceArticleItem } from "./variants";
|
||||
|
||||
export interface ComplianceArticleItemProps {
|
||||
// Leading category icon (pass a phosphor icon).
|
||||
icon: ReactNode;
|
||||
// Primary line.
|
||||
title: ReactNode;
|
||||
// Optional accent sub-label below the title.
|
||||
eyebrow?: ReactNode;
|
||||
// Right-aligned metadata (e.g. relative time).
|
||||
meta?: ReactNode;
|
||||
}
|
||||
|
||||
// A single row of a compliance article list. Encodes the row typography so
|
||||
// callers pass plain content; wrap rows in a bordered list container.
|
||||
export function ComplianceArticleItem({ icon, title, eyebrow, meta }: ComplianceArticleItemProps) {
|
||||
const slots = complianceArticleItem();
|
||||
|
||||
return (
|
||||
<div className={slots.root()}>
|
||||
<span className={slots.icon()}>{icon}</span>
|
||||
<div className={slots.content()}>
|
||||
<Text size={2} weight="medium" color="neutral" highContrast>
|
||||
{title}
|
||||
</Text>
|
||||
{eyebrow != null && (
|
||||
<Text size={1} color="gold">
|
||||
{eyebrow}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
{meta != null && (
|
||||
<Text size={1} color="faint" className={slots.meta()}>
|
||||
{meta}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
|
||||
import { complianceArticleItem } from "./variants";
|
||||
|
||||
// Loading placeholder paired with ComplianceArticleItem: same row layout with
|
||||
// a pulse icon and skeleton text.
|
||||
export function ComplianceArticleItemSkeleton() {
|
||||
const slots = complianceArticleItem();
|
||||
|
||||
return (
|
||||
<div className={slots.root()} aria-hidden>
|
||||
<div className={slots.iconPlaceholder()} />
|
||||
<div className={slots.content()}>
|
||||
<TextSkeleton size={2} className="w-48" />
|
||||
<TextSkeleton size={1} className="w-16" />
|
||||
</div>
|
||||
<TextSkeleton size={1} className="w-20 shrink-0" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// Compliance article list row (Figma "Compliance Article Item"): a leading
|
||||
// icon, a title (+ optional eyebrow), and right-aligned meta. Designed to sit
|
||||
// inside a bordered list container; the last row drops its divider. Slots are
|
||||
// shared by the row and its skeleton.
|
||||
export const complianceArticleItem = tv({
|
||||
slots: {
|
||||
root: "flex w-full items-center gap-4 border-b border-sand-a2 px-8 py-4 last:border-b-0",
|
||||
icon: "flex size-6 shrink-0 items-center justify-center text-gold-9 [&_svg]:size-full",
|
||||
content: "flex min-w-0 flex-1 flex-col gap-1",
|
||||
meta: "shrink-0",
|
||||
iconPlaceholder: "size-6 shrink-0 animate-pulse rounded-2 bg-sand-3",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { CertificateIcon } from "@phosphor-icons/react";
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { MediaTile } from "#/components/MediaTile/MediaTile";
|
||||
|
||||
import type { ComplianceFrameworkListItem_complianceFramework$key } from "./__generated__/ComplianceFrameworkListItem_complianceFramework.graphql";
|
||||
|
||||
const complianceFrameworkListItemFragment = graphql`
|
||||
fragment ComplianceFrameworkListItem_complianceFramework on ComplianceFramework {
|
||||
framework {
|
||||
name
|
||||
themedLogoUrl
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ComplianceFrameworkListItemProps {
|
||||
complianceFrameworkKey: ComplianceFrameworkListItem_complianceFramework$key;
|
||||
}
|
||||
|
||||
// A single framework tile in the Compliance grid.
|
||||
export function ComplianceFrameworkListItem({ complianceFrameworkKey }: ComplianceFrameworkListItemProps) {
|
||||
const { framework } = useFragment(complianceFrameworkListItemFragment, complianceFrameworkKey);
|
||||
const logoUrl = framework.themedLogoUrl;
|
||||
|
||||
return (
|
||||
<MediaTile
|
||||
media={
|
||||
logoUrl != null && logoUrl !== ""
|
||||
? <img src={logoUrl} alt={framework.name} />
|
||||
: <CertificateIcon size={48} weight="duotone" className="text-gold-9" />
|
||||
}
|
||||
label={(
|
||||
<Text size={2} weight="medium" color="neutral" highContrast align="center">
|
||||
{framework.name}
|
||||
</Text>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { HomeSection } from "#/components/HomeSection/HomeSection";
|
||||
|
||||
import type { ComplianceFrameworksSection_trustCenter$key } from "./__generated__/ComplianceFrameworksSection_trustCenter.graphql";
|
||||
import { ComplianceFrameworkListItem } from "./ComplianceFrameworkListItem";
|
||||
|
||||
const complianceFrameworksSectionFragment = graphql`
|
||||
fragment ComplianceFrameworksSection_trustCenter on TrustCenter {
|
||||
complianceFrameworks(first: 8) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...ComplianceFrameworkListItem_complianceFramework
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ComplianceFrameworksSectionProps {
|
||||
trustCenterKey: ComplianceFrameworksSection_trustCenter$key;
|
||||
}
|
||||
|
||||
// "Compliance" section: the grid of certification frameworks the trust center
|
||||
// covers.
|
||||
export function ComplianceFrameworksSection({ trustCenterKey }: ComplianceFrameworksSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const data = useFragment(complianceFrameworksSectionFragment, trustCenterKey);
|
||||
const frameworks = data.complianceFrameworks.edges.map(edge => edge.node);
|
||||
|
||||
if (frameworks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HomeSection title={t("home.sections.compliance")}>
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
{frameworks.map(framework => (
|
||||
<ComplianceFrameworkListItem key={framework.id} complianceFrameworkKey={framework} />
|
||||
))}
|
||||
</div>
|
||||
</HomeSection>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
|
||||
import { homeSection } from "#/components/HomeSection/variants";
|
||||
import { MediaTileSkeleton } from "#/components/MediaTile/MediaTileSkeleton";
|
||||
|
||||
export function ComplianceFrameworksSectionSkeleton() {
|
||||
const slots = homeSection();
|
||||
|
||||
return (
|
||||
<section className={slots.root()} aria-hidden>
|
||||
<div className={slots.header()}>
|
||||
<TextSkeleton size={2} className="w-24" />
|
||||
</div>
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
{Array.from({ length: 4 }, (_, index) => (
|
||||
<MediaTileSkeleton key={index} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import { tv } from "tailwind-variants/lite";
|
||||
// Hero and the page headers so the band is defined once.
|
||||
export const headerBand = tv({
|
||||
slots: {
|
||||
band: "flex w-full flex-col items-center border-b border-sand-6 bg-sand-1 px-8 py-8",
|
||||
band: "flex w-full flex-col items-center border-b border-sand-a3 bg-sand-1 px-8 py-8",
|
||||
inner: "w-full max-w-5xl",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@ export const hero = tv({
|
||||
export const organizationContactInfo = tv({
|
||||
slots: {
|
||||
// Only a top divider gap; the band's py-8 provides the bottom spacing.
|
||||
root: "flex w-full items-center gap-6 border-t border-sand-6 pt-4",
|
||||
root: "flex w-full items-center gap-6 border-t border-sand-a2 pt-4",
|
||||
item: "flex items-center gap-2 text-sand-11 [&_svg]:size-5",
|
||||
link: "flex items-center gap-2 text-sand-11 hover:underline [&_svg]:size-5",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { homeSection } from "./variants";
|
||||
|
||||
interface HomeSectionProps {
|
||||
title: ReactNode;
|
||||
// Optional trailing element in the header row (e.g. a "View all" link).
|
||||
action?: ReactNode;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// Shared frame for the home page body sections: a labelled header above the
|
||||
// section content.
|
||||
export function HomeSection({ title, action, children }: HomeSectionProps) {
|
||||
const slots = homeSection();
|
||||
|
||||
return (
|
||||
<section className={slots.root()}>
|
||||
<div className={slots.header()}>
|
||||
<Text size={2} weight="medium" color="neutral" highContrast>
|
||||
{title}
|
||||
</Text>
|
||||
{action}
|
||||
</div>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// Home page section frame: a labelled header row (with an optional trailing
|
||||
// action) above the section body. Shared by the live sections and skeletons.
|
||||
export const homeSection = tv({
|
||||
slots: {
|
||||
root: "flex w-full flex-col gap-4 py-8",
|
||||
header: "flex w-full items-center justify-between",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Card } from "@probo/ui/src/v2/Card/Card";
|
||||
import type { ReactNode } from "react";
|
||||
import type { VariantProps } from "tailwind-variants/lite";
|
||||
|
||||
import { dotPatternStyle, mediaTile } from "./variants";
|
||||
|
||||
export type MediaTileProps = VariantProps<typeof mediaTile> & {
|
||||
// Centered media (logo/icon image) shown over the backdrop.
|
||||
media: ReactNode;
|
||||
// Caption below the media (pass a Text primitive).
|
||||
label: ReactNode;
|
||||
// For the "logo" variant: the image URL rendered as a blurred, magnified
|
||||
// backdrop behind the media. When omitted, the dotted texture is shown.
|
||||
backdropSrc?: string;
|
||||
};
|
||||
|
||||
// Captioned media tile, composing the soft Card surface. The "icon" variant
|
||||
// shows a dotted backdrop; the "logo" variant shows a blurred, magnified copy of
|
||||
// the logo (via backdropSrc). Purely presentational, so it doubles as the layout
|
||||
// for its skeleton.
|
||||
export function MediaTile({ media, label, variant = "icon", backdropSrc }: MediaTileProps) {
|
||||
const slots = mediaTile({ variant });
|
||||
|
||||
return (
|
||||
<Card variant="soft" size={3} padding="none">
|
||||
<div className={slots.media()}>
|
||||
{backdropSrc != null
|
||||
? <img src={backdropSrc} alt="" aria-hidden className={slots.blurBackdrop()} />
|
||||
: <div className={slots.backdrop()} style={dotPatternStyle} />}
|
||||
<div className={slots.backdropFade()} />
|
||||
<div className={slots.mediaContent()}>{media}</div>
|
||||
</div>
|
||||
<div className={slots.caption()}>{label}</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
import type { VariantProps } from "tailwind-variants/lite";
|
||||
|
||||
import { MediaTile } from "./MediaTile";
|
||||
import type { mediaTile } from "./variants";
|
||||
|
||||
export type MediaTileSkeletonProps = VariantProps<typeof mediaTile>;
|
||||
|
||||
// Loading placeholder paired with MediaTile: renders the same layout with a
|
||||
// pulse block for the media and a TextSkeleton for the caption.
|
||||
export function MediaTileSkeleton({ variant = "icon" }: MediaTileSkeletonProps) {
|
||||
return (
|
||||
<MediaTile
|
||||
variant={variant}
|
||||
media={<div className="size-full animate-pulse rounded-3 bg-sand-3" />}
|
||||
label={<TextSkeleton size={2} className="w-20" />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
50
apps/compliance-portal/src/components/MediaTile/variants.ts
Normal file
50
apps/compliance-portal/src/components/MediaTile/variants.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import type { CSSProperties } from "react";
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// Captioned media tile (Figma "Framework Card" / "Logo Card"): a media container
|
||||
// with a decorative backdrop above a centered caption, rendered inside a soft
|
||||
// Card. The "icon" variant (default) uses a padded square with a dotted texture;
|
||||
// the "logo" variant fills a square container and uses a blurred, magnified copy
|
||||
// of the logo as the backdrop. Slots are shared by the shell and its skeleton.
|
||||
export const mediaTile = tv({
|
||||
slots: {
|
||||
media: "relative flex w-full items-center justify-center overflow-hidden border-b border-sand-a2",
|
||||
backdrop: "pointer-events-none absolute inset-0",
|
||||
blurBackdrop: "pointer-events-none absolute inset-0 size-full scale-150 object-cover opacity-10 blur-lg",
|
||||
backdropFade: "pointer-events-none absolute inset-0 bg-linear-to-b from-sand-1/0 to-sand-1",
|
||||
mediaContent: "relative z-10 flex size-16 items-center justify-center [&_img]:size-full [&_img]:object-contain",
|
||||
caption: "flex w-full items-center justify-center px-4 py-3",
|
||||
},
|
||||
variants: {
|
||||
variant: {
|
||||
icon: { media: "p-4" },
|
||||
logo: { media: "aspect-square" },
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "icon",
|
||||
},
|
||||
});
|
||||
|
||||
// Repeating dot texture as a radial-gradient using a Radix sand alpha token
|
||||
// (theme-aware, no binary asset), faded toward the card surface by the
|
||||
// backdropFade gradient slot. Maps Figma's neutral-alpha dots to sand-a5. Shared
|
||||
// by the media/commitment cards, the updates list, and the footer.
|
||||
export const dotPatternStyle: CSSProperties = {
|
||||
backgroundImage: "radial-gradient(var(--sand-a5) 0.75px, transparent 1.25px)",
|
||||
backgroundSize: "9px 9px",
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { ProboLogo } from "@probo/ui/src/v2/ProboLogo/ProboLogo";
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
import { dotPatternStyle } from "#/components/MediaTile/variants";
|
||||
|
||||
import { poweredBy } from "./variants";
|
||||
|
||||
export interface PoweredByProps {
|
||||
// Localized "Powered by" label. Defaults to the English string.
|
||||
label?: ReactNode;
|
||||
// Destination for the Probo logo link.
|
||||
href?: string;
|
||||
}
|
||||
|
||||
// "Powered by Probo" footer: a borderless attribution on the body surface, with
|
||||
// a dotted texture fading in from the bottom and the full Probo logo (picto +
|
||||
// wordmark).
|
||||
export function PoweredBy({ label = "Powered by", href = "https://www.probo.com/" }: PoweredByProps) {
|
||||
const slots = poweredBy();
|
||||
|
||||
return (
|
||||
<footer className={slots.root()}>
|
||||
<div className={slots.backdrop()} style={dotPatternStyle} />
|
||||
<div className={slots.backdropFade()} />
|
||||
<div className={slots.content()}>
|
||||
<Text size={1} color="neutral">
|
||||
{label}
|
||||
</Text>
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" aria-label="Probo">
|
||||
<ProboLogo className={slots.logo()} aria-hidden />
|
||||
</a>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
27
apps/compliance-portal/src/components/PoweredBy/variants.ts
Normal file
27
apps/compliance-portal/src/components/PoweredBy/variants.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// "Powered by Probo" footer (Figma "Powered by"): a borderless, full-width row
|
||||
// on the body surface, with a dotted texture that fades in from the bottom.
|
||||
export const poweredBy = tv({
|
||||
slots: {
|
||||
root: "relative flex w-full items-center justify-center overflow-hidden bg-sand-2 py-6 text-sand-11",
|
||||
backdrop: "pointer-events-none absolute inset-0",
|
||||
backdropFade: "pointer-events-none absolute inset-0 bg-linear-to-b from-sand-2 to-sand-2/0",
|
||||
content: "relative z-10 flex items-center justify-center gap-2",
|
||||
logo: "h-6 w-auto text-sand-9",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { NewspaperIcon } from "@phosphor-icons/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { ComplianceArticleItem } from "#/components/ComplianceArticleItem/ComplianceArticleItem";
|
||||
import { formatRelativeTime } from "#/lib/datetime/relativeTime";
|
||||
|
||||
import type { MailingListUpdateListItem_update$key } from "./__generated__/MailingListUpdateListItem_update.graphql";
|
||||
|
||||
const mailingListUpdateListItemFragment = graphql`
|
||||
fragment MailingListUpdateListItem_update on MailingListUpdate {
|
||||
title
|
||||
updatedAt
|
||||
}
|
||||
`;
|
||||
|
||||
interface MailingListUpdateListItemProps {
|
||||
updateKey: MailingListUpdateListItem_update$key;
|
||||
}
|
||||
|
||||
// A single "Recent updates" row. The schema has no category, so we show the
|
||||
// title and relative date with a single generic icon.
|
||||
export function MailingListUpdateListItem({ updateKey }: MailingListUpdateListItemProps) {
|
||||
const { i18n } = useTranslation();
|
||||
const update = useFragment(mailingListUpdateListItemFragment, updateKey);
|
||||
|
||||
return (
|
||||
<ComplianceArticleItem
|
||||
icon={<NewspaperIcon weight="light" />}
|
||||
title={update.title}
|
||||
meta={formatRelativeTime(update.updatedAt, i18n.language)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Link } from "@probo/ui/src/v2/Button/Link";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { HomeSection } from "#/components/HomeSection/HomeSection";
|
||||
import { dotPatternStyle } from "#/components/MediaTile/variants";
|
||||
|
||||
import type { RecentUpdatesSection_trustCenter$key } from "./__generated__/RecentUpdatesSection_trustCenter.graphql";
|
||||
import { MailingListUpdateListItem } from "./MailingListUpdateListItem";
|
||||
|
||||
const recentUpdatesSectionFragment = graphql`
|
||||
fragment RecentUpdatesSection_trustCenter on TrustCenter {
|
||||
updates(first: 5) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...MailingListUpdateListItem_update
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface RecentUpdatesSectionProps {
|
||||
trustCenterKey: RecentUpdatesSection_trustCenter$key;
|
||||
}
|
||||
|
||||
// "Recent updates" section: the latest mailing-list updates as a list, with a
|
||||
// link to the full updates page.
|
||||
export function RecentUpdatesSection({ trustCenterKey }: RecentUpdatesSectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const data = useFragment(recentUpdatesSectionFragment, trustCenterKey);
|
||||
const updates = data.updates.edges.map(edge => edge.node);
|
||||
|
||||
if (updates.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HomeSection
|
||||
title={t("home.sections.recentUpdates")}
|
||||
action={(
|
||||
<Link to="/updates" variant="ghost" color="neutral" size={2}>
|
||||
{t("home.recentUpdates.viewAll")}
|
||||
</Link>
|
||||
)}
|
||||
>
|
||||
<div className="relative overflow-hidden rounded-5 border border-sand-3 bg-sand-1">
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0" style={dotPatternStyle} />
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0 bg-linear-to-r from-sand-1/0 to-sand-1 to-[96px]" />
|
||||
<div className="relative">
|
||||
{updates.map(update => (
|
||||
<MailingListUpdateListItem key={update.id} updateKey={update} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</HomeSection>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
|
||||
import { ComplianceArticleItemSkeleton } from "#/components/ComplianceArticleItem/ComplianceArticleItemSkeleton";
|
||||
import { homeSection } from "#/components/HomeSection/variants";
|
||||
import { dotPatternStyle } from "#/components/MediaTile/variants";
|
||||
|
||||
export function RecentUpdatesSectionSkeleton() {
|
||||
const slots = homeSection();
|
||||
|
||||
return (
|
||||
<section className={slots.root()} aria-hidden>
|
||||
<div className={slots.header()}>
|
||||
<TextSkeleton size={2} className="w-28" />
|
||||
<TextSkeleton size={2} className="w-12" />
|
||||
</div>
|
||||
<div className="relative overflow-hidden rounded-5 border border-sand-3 bg-sand-1">
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0" style={dotPatternStyle} />
|
||||
<div aria-hidden className="pointer-events-none absolute inset-0 bg-linear-to-r from-sand-1/0 to-sand-1 to-[96px]" />
|
||||
<div className="relative">
|
||||
{Array.from({ length: 5 }, (_, index) => (
|
||||
<ComplianceArticleItemSkeleton key={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
|
||||
import { CommitmentCard } from "#/components/CommitmentCard/CommitmentCard";
|
||||
|
||||
import { SECURITY_COMMITMENT_GROUPS } from "./securityCommitments";
|
||||
import { securityCommitments } from "./variants";
|
||||
|
||||
// "Security Commitments" section.
|
||||
//
|
||||
// TODO: This section renders placeholder data from a local POJO
|
||||
// (./securityCommitments.ts) because there is no backend / DB structure for it
|
||||
// yet. Replace it with a relay-driven fragment (and i18n copy) once available.
|
||||
export function SecurityCommitmentsSection() {
|
||||
const slots = securityCommitments();
|
||||
|
||||
return (
|
||||
<section className={slots.root()}>
|
||||
{SECURITY_COMMITMENT_GROUPS.map(group => (
|
||||
<div key={group.title} className={slots.group()}>
|
||||
<div className={slots.groupHeader()}>
|
||||
{group.eyebrow != null && (
|
||||
<Text size={1} color="gold">
|
||||
{group.eyebrow}
|
||||
</Text>
|
||||
)}
|
||||
<Text size={2} weight="medium" color="neutral" highContrast>
|
||||
{group.title}
|
||||
</Text>
|
||||
<Text size={2} color="neutral">
|
||||
{group.description}
|
||||
</Text>
|
||||
</div>
|
||||
<div className={slots.grid()}>
|
||||
{group.items.map(item => (
|
||||
<CommitmentCard
|
||||
key={item.title}
|
||||
icon={<item.Icon size={32} weight="light" />}
|
||||
eyebrow={<Text size={1} color="gold">{item.eyebrow}</Text>}
|
||||
title={(
|
||||
<Text size={4} weight="medium" color="neutral" highContrast>
|
||||
{item.title}
|
||||
</Text>
|
||||
)}
|
||||
description={<Text size={2} color="neutral">{item.description}</Text>}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
|
||||
import { CommitmentCardSkeleton } from "#/components/CommitmentCard/CommitmentCardSkeleton";
|
||||
|
||||
import { securityCommitments } from "./variants";
|
||||
|
||||
export function SecurityCommitmentsSectionSkeleton() {
|
||||
const slots = securityCommitments();
|
||||
|
||||
return (
|
||||
<section className={slots.root()} aria-hidden>
|
||||
<div className={slots.group()}>
|
||||
<div className={slots.groupHeader()}>
|
||||
<TextSkeleton size={1} className="w-32" />
|
||||
<TextSkeleton size={2} className="w-28" />
|
||||
<TextSkeleton size={2} className="w-full max-w-2xl" />
|
||||
</div>
|
||||
<div className={slots.grid()}>
|
||||
{Array.from({ length: 3 }, (_, index) => (
|
||||
<CommitmentCardSkeleton key={index} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import type { Icon } from "@phosphor-icons/react";
|
||||
import { EyeSlashIcon, FingerprintIcon, LockKeyIcon, ShieldWarningIcon, SirenIcon } from "@phosphor-icons/react";
|
||||
|
||||
export interface SecurityCommitmentItem {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
Icon: Icon;
|
||||
}
|
||||
|
||||
export interface SecurityCommitmentGroup {
|
||||
// Only the first group carries the section eyebrow in the design.
|
||||
eyebrow?: string;
|
||||
title: string;
|
||||
description: string;
|
||||
items: SecurityCommitmentItem[];
|
||||
}
|
||||
|
||||
// TODO: This content is placeholder data. There is no backend / DB structure for
|
||||
// security commitments yet — replace this POJO with relay-sourced data (and move
|
||||
// the copy into i18n) once the schema exists.
|
||||
export const SECURITY_COMMITMENT_GROUPS: SecurityCommitmentGroup[] = [
|
||||
{
|
||||
eyebrow: "Security Commitments",
|
||||
title: "Data Protection",
|
||||
description:
|
||||
"Your data is protected with industry-leading encryption and strict privacy controls, from storage to transit to processing.",
|
||||
items: [
|
||||
{
|
||||
eyebrow: "Data Protection",
|
||||
title: "Encrypted at rest. Encrypted in transit. Always.",
|
||||
description:
|
||||
"Customer data is protected with AES-256 at rest and TLS 1.3 in transit. Keys rotated on a fixed schedule.",
|
||||
Icon: LockKeyIcon,
|
||||
},
|
||||
{
|
||||
eyebrow: "Privacy",
|
||||
title: "Customer data stays customer data.",
|
||||
description:
|
||||
"PII is masked in non-production environments. Access requires SSO with hardware-backed keys.",
|
||||
Icon: EyeSlashIcon,
|
||||
},
|
||||
{
|
||||
eyebrow: "Identity",
|
||||
title: "Phishing-resistant authentication, by default.",
|
||||
description:
|
||||
"Every employee authenticates via WebAuthn. Production access is just-in-time and recorded.",
|
||||
Icon: FingerprintIcon,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Operational Security",
|
||||
description:
|
||||
"We maintain continuous uptime and rapid incident response so you can rely on our platform around the clock.",
|
||||
items: [
|
||||
{
|
||||
eyebrow: "Threat Detection",
|
||||
title: "Anomalies do not wait. Neither do we.",
|
||||
description:
|
||||
"24/7 SIEM monitoring with automated triage. Critical incidents page on-call within 90 seconds.",
|
||||
Icon: ShieldWarningIcon,
|
||||
},
|
||||
{
|
||||
eyebrow: "Incident Response",
|
||||
title: "Issues are caught fast. And handled.",
|
||||
description:
|
||||
"Security incidents are investigated within hours. Customers notified within 24 hours of a confirmed breach.",
|
||||
Icon: SirenIcon,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { tv } from "tailwind-variants/lite";
|
||||
|
||||
// Security Commitments section: stacked groups, each a header above a grid of
|
||||
// commitment cards. Slots shared by the section and its skeleton.
|
||||
export const securityCommitments = tv({
|
||||
slots: {
|
||||
root: "flex w-full flex-col gap-8 py-8",
|
||||
group: "flex w-full flex-col gap-4",
|
||||
groupHeader: "flex w-full flex-col gap-2",
|
||||
grid: "grid grid-cols-3 gap-4",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { Text } from "@probo/ui/src/v2/typography/Text";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { MediaTile } from "#/components/MediaTile/MediaTile";
|
||||
import { externalHref } from "#/lib/url/hostname";
|
||||
|
||||
import type { TrustCenterReferenceListItem_reference$key } from "./__generated__/TrustCenterReferenceListItem_reference.graphql";
|
||||
|
||||
const trustCenterReferenceListItemFragment = graphql`
|
||||
fragment TrustCenterReferenceListItem_reference on TrustCenterReference {
|
||||
name
|
||||
websiteUrl
|
||||
logo {
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface TrustCenterReferenceListItemProps {
|
||||
referenceKey: TrustCenterReferenceListItem_reference$key;
|
||||
}
|
||||
|
||||
// A single "Trusted by" logo tile, linking to the reference's website.
|
||||
export function TrustCenterReferenceListItem({ referenceKey }: TrustCenterReferenceListItemProps) {
|
||||
const reference = useFragment(trustCenterReferenceListItemFragment, referenceKey);
|
||||
|
||||
return (
|
||||
<a
|
||||
className="block"
|
||||
href={externalHref(reference.websiteUrl)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<MediaTile
|
||||
variant="logo"
|
||||
backdropSrc={reference.logo.downloadUrl}
|
||||
media={<img src={reference.logo.downloadUrl} alt={reference.name} />}
|
||||
label={(
|
||||
<Text size={2} weight="medium" color="neutral" highContrast align="center">
|
||||
{reference.name}
|
||||
</Text>
|
||||
)}
|
||||
/>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { graphql, useFragment } from "react-relay";
|
||||
|
||||
import { HomeSection } from "#/components/HomeSection/HomeSection";
|
||||
|
||||
import type { TrustedBySection_trustCenter$key } from "./__generated__/TrustedBySection_trustCenter.graphql";
|
||||
import { TrustCenterReferenceListItem } from "./TrustCenterReferenceListItem";
|
||||
|
||||
const trustedBySectionFragment = graphql`
|
||||
fragment TrustedBySection_trustCenter on TrustCenter {
|
||||
references(first: 12) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...TrustCenterReferenceListItem_reference
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface TrustedBySectionProps {
|
||||
trustCenterKey: TrustedBySection_trustCenter$key;
|
||||
}
|
||||
|
||||
// "Trusted by" section: a grid of customer / reference logos.
|
||||
export function TrustedBySection({ trustCenterKey }: TrustedBySectionProps) {
|
||||
const { t } = useTranslation();
|
||||
const data = useFragment(trustedBySectionFragment, trustCenterKey);
|
||||
const references = data.references.edges.map(edge => edge.node);
|
||||
|
||||
if (references.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<HomeSection title={t("home.sections.trustedBy")}>
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
{references.map(reference => (
|
||||
<TrustCenterReferenceListItem key={reference.id} referenceKey={reference} />
|
||||
))}
|
||||
</div>
|
||||
</HomeSection>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton";
|
||||
|
||||
import { homeSection } from "#/components/HomeSection/variants";
|
||||
import { MediaTileSkeleton } from "#/components/MediaTile/MediaTileSkeleton";
|
||||
|
||||
export function TrustedBySectionSkeleton() {
|
||||
const slots = homeSection();
|
||||
|
||||
return (
|
||||
<section className={slots.root()} aria-hidden>
|
||||
<div className={slots.header()}>
|
||||
<TextSkeleton size={2} className="w-20" />
|
||||
</div>
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
{Array.from({ length: 6 }, (_, index) => (
|
||||
<MediaTileSkeleton key={index} variant="logo" />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
41
apps/compliance-portal/src/lib/datetime/relativeTime.ts
Normal file
41
apps/compliance-portal/src/lib/datetime/relativeTime.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
const DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[] = [
|
||||
{ amount: 60, unit: "second" },
|
||||
{ amount: 60, unit: "minute" },
|
||||
{ amount: 24, unit: "hour" },
|
||||
{ amount: 7, unit: "day" },
|
||||
{ amount: 4.34524, unit: "week" },
|
||||
{ amount: 12, unit: "month" },
|
||||
{ amount: Number.POSITIVE_INFINITY, unit: "year" },
|
||||
];
|
||||
|
||||
// Locale-aware "x days ago" style formatting via Intl.RelativeTimeFormat. The
|
||||
// locale must be the active i18next language so the output follows the UI.
|
||||
export function formatRelativeTime(date: Date | string | number, locale: string): string {
|
||||
const target = date instanceof Date ? date : new Date(date);
|
||||
const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" });
|
||||
|
||||
let duration = (target.getTime() - Date.now()) / 1000;
|
||||
|
||||
for (const division of DIVISIONS) {
|
||||
if (Math.abs(duration) < division.amount) {
|
||||
return formatter.format(Math.round(duration), division.unit);
|
||||
}
|
||||
duration /= division.amount;
|
||||
}
|
||||
|
||||
return formatter.format(Math.round(duration), "year");
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { graphql } from "react-relay";
|
||||
import { type LiveState, readFragment } from "relay-runtime";
|
||||
|
||||
import type { FrameworkLogoResolverFragment$key } from "./__generated__/FrameworkLogoResolverFragment.graphql";
|
||||
|
||||
function prefersDark(): boolean {
|
||||
return typeof window !== "undefined"
|
||||
&& !!window.matchMedia
|
||||
&& window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* @relayField Framework.themedLogoUrl: String
|
||||
* @rootFragment FrameworkLogoResolverFragment
|
||||
* @live
|
||||
*
|
||||
* Resolves the framework logo download URL for the current system color scheme:
|
||||
* the dark logo (falling back to the light one) when the OS prefers dark,
|
||||
* otherwise the light logo. Lives in the graph so consumers select a single
|
||||
* field instead of threading `useSystemTheme` through URL selection.
|
||||
*/
|
||||
export function themedLogoUrl(
|
||||
key: FrameworkLogoResolverFragment$key,
|
||||
): LiveState<string | null> {
|
||||
const data = readFragment(
|
||||
graphql`
|
||||
fragment FrameworkLogoResolverFragment on Framework {
|
||||
lightLogo {
|
||||
downloadUrl
|
||||
}
|
||||
darkLogo {
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
`,
|
||||
key,
|
||||
);
|
||||
const lightUrl = data.lightLogo?.downloadUrl ?? null;
|
||||
const darkUrl = data.darkLogo?.downloadUrl ?? lightUrl;
|
||||
|
||||
return {
|
||||
read: () => (prefersDark() ? darkUrl : lightUrl),
|
||||
subscribe: (callback) => {
|
||||
const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
mediaQuery.addEventListener("change", callback);
|
||||
return () => mediaQuery.removeEventListener("change", callback);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -16,8 +16,12 @@ import { useTranslation } from "react-i18next";
|
||||
import type { PreloadedQuery } from "react-relay";
|
||||
import { graphql, usePreloadedQuery } from "react-relay";
|
||||
|
||||
import { ComplianceFrameworksSection } from "#/components/ComplianceFrameworks/ComplianceFrameworksSection";
|
||||
import { Hero } from "#/components/Hero/Hero";
|
||||
import { OrganizationContactInfo } from "#/components/Hero/OrganizationContactInfo";
|
||||
import { RecentUpdatesSection } from "#/components/RecentUpdates/RecentUpdatesSection";
|
||||
import { SecurityCommitmentsSection } from "#/components/SecurityCommitments/SecurityCommitmentsSection";
|
||||
import { TrustedBySection } from "#/components/TrustedBy/TrustedBySection";
|
||||
|
||||
import type { HomePageQuery } from "./__generated__/HomePageQuery.graphql";
|
||||
|
||||
@@ -26,9 +30,11 @@ export const homePageQuery = graphql`
|
||||
currentTrustCenter @required(action: THROW) {
|
||||
organization {
|
||||
name
|
||||
description
|
||||
...OrganizationContactInfo_organization
|
||||
}
|
||||
...ComplianceFrameworksSection_trustCenter
|
||||
...TrustedBySection_trustCenter
|
||||
...RecentUpdatesSection_trustCenter
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -40,14 +46,25 @@ interface HomePageProps {
|
||||
export function HomePage({ queryRef }: HomePageProps) {
|
||||
const { t } = useTranslation();
|
||||
const data = usePreloadedQuery<HomePageQuery>(homePageQuery, queryRef);
|
||||
const { organization } = data.currentTrustCenter;
|
||||
const { currentTrustCenter } = data;
|
||||
const { organization } = currentTrustCenter;
|
||||
|
||||
return (
|
||||
<Hero
|
||||
title={t("home.heroTitle", { name: organization.name })}
|
||||
description={organization.description}
|
||||
>
|
||||
<OrganizationContactInfo organizationKey={organization} />
|
||||
</Hero>
|
||||
<>
|
||||
<Hero
|
||||
title={t("home.heroTitle", { name: organization.name })}
|
||||
description={t("home.heroDescription")}
|
||||
>
|
||||
<OrganizationContactInfo organizationKey={organization} />
|
||||
</Hero>
|
||||
<div className="flex w-full flex-col items-center px-8">
|
||||
<div className="flex w-full max-w-5xl flex-col">
|
||||
<ComplianceFrameworksSection trustCenterKey={currentTrustCenter} />
|
||||
<SecurityCommitmentsSection />
|
||||
<TrustedBySection trustCenterKey={currentTrustCenter} />
|
||||
<RecentUpdatesSection trustCenterKey={currentTrustCenter} />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,8 +12,24 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { ComplianceFrameworksSectionSkeleton } from "#/components/ComplianceFrameworks/ComplianceFrameworksSectionSkeleton";
|
||||
import { HeroSkeleton } from "#/components/Hero/HeroSkeleton";
|
||||
import { RecentUpdatesSectionSkeleton } from "#/components/RecentUpdates/RecentUpdatesSectionSkeleton";
|
||||
import { SecurityCommitmentsSectionSkeleton } from "#/components/SecurityCommitments/SecurityCommitmentsSectionSkeleton";
|
||||
import { TrustedBySectionSkeleton } from "#/components/TrustedBy/TrustedBySectionSkeleton";
|
||||
|
||||
export function HomePageSkeleton() {
|
||||
return <HeroSkeleton />;
|
||||
return (
|
||||
<>
|
||||
<HeroSkeleton />
|
||||
<div className="flex w-full flex-col items-center px-8">
|
||||
<div className="flex w-full max-w-5xl flex-col">
|
||||
<ComplianceFrameworksSectionSkeleton />
|
||||
<SecurityCommitmentsSectionSkeleton />
|
||||
<TrustedBySectionSkeleton />
|
||||
<RecentUpdatesSectionSkeleton />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,10 +12,12 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { PreloadedQuery } from "react-relay";
|
||||
import { graphql, usePreloadedQuery } from "react-relay";
|
||||
import { Outlet } from "react-router";
|
||||
|
||||
import { PoweredBy } from "#/components/PoweredBy/PoweredBy";
|
||||
import { TopBar } from "#/components/TopBar/TopBar";
|
||||
|
||||
import type { MainLayoutQuery } from "./__generated__/MainLayoutQuery.graphql";
|
||||
@@ -31,12 +33,16 @@ interface MainLayoutProps {
|
||||
}
|
||||
|
||||
export function MainLayout({ queryRef }: MainLayoutProps) {
|
||||
const { t } = useTranslation();
|
||||
const data = usePreloadedQuery<MainLayoutQuery>(mainLayoutQuery, queryRef);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-sand-2">
|
||||
<div className="flex min-h-screen flex-col bg-sand-2">
|
||||
<TopBar queryKey={data} />
|
||||
<Outlet />
|
||||
<div className="flex-1">
|
||||
<Outlet />
|
||||
</div>
|
||||
<PoweredBy label={t("footer.poweredBy")} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,10 +24,10 @@ export type CardProps = ComponentProps<"div"> & VariantProps<typeof card>;
|
||||
// clickable card set `interactive` and wrap the content in an anchor/link. Use
|
||||
// CardInset for content that should bleed to the card's edges.
|
||||
export function Card(props: CardProps) {
|
||||
const { size = 1, variant, interactive, className, children, ...rest } = props;
|
||||
const { size = 1, padding = size, variant, interactive, className, children, ...rest } = props;
|
||||
|
||||
return (
|
||||
<div className={card({ size, variant, interactive, className })} {...rest}>
|
||||
<div className={card({ size, padding, variant, interactive, className })} {...rest}>
|
||||
<CardProvider value={size}>{children}</CardProvider>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,17 +19,29 @@ import { tv } from "tailwind-variants/lite";
|
||||
export const card = tv({
|
||||
base: "block overflow-hidden",
|
||||
variants: {
|
||||
// Padding scales with size; radius scales with it too (per the Figma).
|
||||
// Radius scales with size (per the Figma).
|
||||
size: {
|
||||
1: "rounded-4 p-3",
|
||||
2: "rounded-4 p-4",
|
||||
3: "rounded-5 p-5",
|
||||
4: "rounded-5 p-6",
|
||||
5: "rounded-6 p-8",
|
||||
1: "rounded-4",
|
||||
2: "rounded-4",
|
||||
3: "rounded-5",
|
||||
4: "rounded-5",
|
||||
5: "rounded-6",
|
||||
},
|
||||
// Padding defaults to match `size` (set in Card.tsx); pass `padding="none"`
|
||||
// for cards whose regions own their own padding (e.g. full-bleed media).
|
||||
padding: {
|
||||
1: "p-3",
|
||||
2: "p-4",
|
||||
3: "p-5",
|
||||
4: "p-6",
|
||||
5: "p-8",
|
||||
none: "p-0",
|
||||
},
|
||||
variant: {
|
||||
surface: "border border-sand-6 bg-sand-2",
|
||||
classic: "border border-sand-6 bg-sand-2 shadow-2",
|
||||
// Soft surface: translucent neutral border on the app background.
|
||||
soft: "border border-sand-a3 bg-sand-1",
|
||||
ghost: "",
|
||||
},
|
||||
// Hover/active affordance for clickable cards. This tunes look only; wrap
|
||||
@@ -40,7 +52,7 @@ export const card = tv({
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{ variant: ["surface", "classic"], interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" },
|
||||
{ variant: ["surface", "classic", "soft"], interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" },
|
||||
{ variant: "ghost", interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" },
|
||||
],
|
||||
defaultVariants: {
|
||||
|
||||
37
packages/ui/src/v2/ProboLogo/ProboLogo.stories.tsx
Normal file
37
packages/ui/src/v2/ProboLogo/ProboLogo.stories.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import { ProboLogo } from "./ProboLogo";
|
||||
|
||||
export default {
|
||||
title: "v2/ProboLogo",
|
||||
component: ProboLogo,
|
||||
render: () => <ProboLogo className="h-6 w-auto text-sand-12" />,
|
||||
} satisfies Meta<typeof ProboLogo>;
|
||||
|
||||
type Story = StoryObj<typeof ProboLogo>;
|
||||
|
||||
export const Playground: Story = {};
|
||||
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div className="flex items-center gap-6 text-sand-11">
|
||||
<ProboLogo className="h-4 w-auto" />
|
||||
<ProboLogo className="h-6 w-auto" />
|
||||
<ProboLogo className="h-10 w-auto" />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
53
packages/ui/src/v2/ProboLogo/ProboLogo.tsx
Normal file
53
packages/ui/src/v2/ProboLogo/ProboLogo.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import type { ComponentProps } from "react";
|
||||
import { useId } from "react";
|
||||
|
||||
export type ProboLogoProps = ComponentProps<"svg">;
|
||||
|
||||
// The Probo brand logo (picto + wordmark). Rendered in `currentColor`; the
|
||||
// picto's mark is a transparent cutout (via mask) so the underlying surface
|
||||
// shows through on any background. Size it with a height class (e.g. `h-6`).
|
||||
export function ProboLogo(props: ProboLogoProps) {
|
||||
const maskId = useId();
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 90 24"
|
||||
role="img"
|
||||
{...props}
|
||||
>
|
||||
<mask id={maskId} maskUnits="userSpaceOnUse">
|
||||
<rect width="24" height="24" x=".85" fill="#fff" rx="5.14" />
|
||||
<path
|
||||
stroke="#000"
|
||||
strokeWidth="3.53"
|
||||
d="M20.57 6.17c2.96 4.47-6.43 13.97-20.32 5.66"
|
||||
/>
|
||||
<path
|
||||
fill="#000"
|
||||
d="M19.07 7.1a1.77 1.77 0 0 0 3-1.86l-1.5.93-1.5.94ZM8.82 25.18l1.71-.41c-2.3-9.67.01-14.66 2.54-16.83A6.25 6.25 0 0 1 17 6.33c1.22-.01 1.87.44 2.08.78l1.5-.94 1.5-.93c-1.1-1.74-3.16-2.46-5.13-2.44-2.03.03-4.26.81-6.17 2.45-3.9 3.35-6.16 9.92-3.67 20.33l1.72-.41Z"
|
||||
/>
|
||||
</mask>
|
||||
<rect width="24" height="24" x=".85" fill="currentColor" rx="5.14" mask={`url(#${maskId})`} />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M66.87 0v7.32a4.84 4.84 0 0 1 1.48-.74 5.63 5.63 0 0 1 1.76-.28c1.63 0 3 .55 4.1 1.68a5.5 5.5 0 0 1 1.67 4.06c0 1.6-.56 2.93-1.69 4.06a5.49 5.49 0 0 1-4.08 1.69 5.8 5.8 0 0 1-4.06-1.56 5.48 5.48 0 0 1-1.71-4.27V0h2.53Zm3.24 8.72a3.2 3.2 0 0 0-3.24 3.32 3.18 3.18 0 0 0 3.24 3.32 3.2 3.2 0 0 0 3.24-3.32c0-1.9-1.43-3.32-3.24-3.32ZM52.75 7.98a5.53 5.53 0 0 1 4.08-1.68c1.61 0 2.93.55 4.06 1.68a5.51 5.51 0 0 1 1.69 4.06c0 1.58-.55 2.95-1.69 4.09a5.59 5.59 0 0 1-4.06 1.66 5.64 5.64 0 0 1-4.08-1.66 5.53 5.53 0 0 1-1.69-4.09c0-1.6.56-2.92 1.7-4.06Zm.84 4.06c0 .98.32 1.77.93 2.4.63.6 1.4.92 2.31.92.92 0 1.7-.31 2.3-.92.63-.64.94-1.42.94-2.4 0-.97-.31-1.76-.94-2.37a3.03 3.03 0 0 0-2.3-.95 3.22 3.22 0 0 0-3.24 3.32ZM79.32 7.98A5.53 5.53 0 0 1 83.4 6.3c1.6 0 2.93.55 4.06 1.68a5.51 5.51 0 0 1 1.69 4.06c0 1.58-.56 2.95-1.7 4.09a5.59 5.59 0 0 1-4.05 1.66 5.64 5.64 0 0 1-4.08-1.66 5.53 5.53 0 0 1-1.69-4.09c0-1.6.55-2.92 1.69-4.06Zm.84 4.06c0 .98.32 1.77.92 2.4.64.6 1.4.92 2.32.92.92 0 1.69-.31 2.3-.92.63-.64.94-1.42.94-2.4 0-.97-.31-1.76-.95-2.37a3.03 3.03 0 0 0-2.29-.95 3.22 3.22 0 0 0-3.24 3.32ZM34.24 16.76V24h-2.53V12.12c0-1.82.58-3.24 1.71-4.27a5.87 5.87 0 0 1 4.06-1.55c1.56 0 2.98.55 4.08 1.68a5.51 5.51 0 0 1 1.7 4.06 5.66 5.66 0 0 1-5.77 5.75 5.43 5.43 0 0 1-2.88-.77l-.36-.26Zm3.24-8.04a3.2 3.2 0 0 0-3.24 3.32c0 1.9 1.42 3.32 3.24 3.32a3.2 3.2 0 0 0 3.24-3.32 3.2 3.2 0 0 0-3.24-3.32ZM47.4 17.65h-2.5v-6.7c0-3.23 1.82-4.52 4.27-4.52h.6v2.42h-.47c-1.26 0-1.9.71-1.9 2.1v6.7Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
@@ -46,8 +46,10 @@ const typographyVariants = {
|
||||
},
|
||||
// Hue only; the resolved text step comes from the color × highContrast
|
||||
// compound variants below (step 11 low-contrast, step 12 high-contrast).
|
||||
// `faint` is a de-emphasized neutral (sand alpha 8) for fine-print metadata.
|
||||
color: {
|
||||
neutral: "",
|
||||
faint: "",
|
||||
gold: "",
|
||||
red: "",
|
||||
green: "",
|
||||
@@ -63,6 +65,9 @@ const typographyVariants = {
|
||||
const colorCompoundVariants = [
|
||||
{ color: "neutral", highContrast: false, class: "text-sand-11" },
|
||||
{ color: "neutral", highContrast: true, class: "text-sand-12" },
|
||||
// Faint metadata; a single de-emphasized step regardless of highContrast.
|
||||
{ color: "faint", highContrast: false, class: "text-sand-a8" },
|
||||
{ color: "faint", highContrast: true, class: "text-sand-a8" },
|
||||
{ color: "gold", highContrast: false, class: "text-gold-11" },
|
||||
{ color: "gold", highContrast: true, class: "text-gold-12" },
|
||||
{ color: "red", highContrast: false, class: "text-red-11" },
|
||||
|
||||
Reference in New Issue
Block a user