Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 66 additions & 36 deletions packages/ui/src/layout/OpenFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,6 @@ import type { ComponentProps, ReactNode } from 'react';

import { cn } from '../utils/cn.js';

export interface OpenFooterSocial {
/** Target href. */
href: string;
/** Icon element (typically an inline SVG or lucide icon). */
icon: ReactNode;
/** Accessible label for the link. */
label: string;
}

export interface OpenFooterBrand {
/** URL the logo + wordmark link to. */
href: string;
Expand All @@ -23,28 +14,32 @@ export interface OpenFooterBrand {
export interface OpenFooterProps extends Omit<ComponentProps<'footer'>, 'children'> {
/** "Maintained by" brand block on the left. */
brand: OpenFooterBrand;
/** Disclaimer / legal paragraph rendered at the bottom. */
disclaimer: ReactNode;
/** GitHub repository URL (rendered as a social icon). */
githubUrl: string;
/** "Maintained by" label. Default: "Mantido por". */
maintainedByLabel?: ReactNode;
/** Social links rendered on the right as round icon buttons. */
socials?: OpenFooterSocial[];
/**
* Optional npm package or org URL (rendered as a social icon when set).
* Omit for projects that don't publish to npm yet.
*/
npmUrl?: string;
}

/**
* Footer for the OSS sites (fhir-brasil, medbench-brasil, datasus-brasil).
*
Comment thread
rlueder marked this conversation as resolved.
* Layout: "Mantido por <brand>" on the left + social icon row on the
* right, with a thin divider + full-width disclaimer paragraph below.
* All copy is consumer-provided — the component ships the structure,
* typography and spacing conventions only.
* Layout: "Mantido por <brand>" on the left + GitHub (and optional npm)
* social icons on the right, with a thin divider + a shared medical
* disclaimer paragraph below. Icons and disclaimer are intentionally
* baked in — they're identical across the OSS sites — so consumers only
* supply their brand and the per-project repo / npm URLs.
*/
export function OpenFooter({
brand,
className,
disclaimer,
githubUrl,
maintainedByLabel = 'Mantido por',
socials,
npmUrl,
...props
}: OpenFooterProps) {
return (
Comment thread
rlueder marked this conversation as resolved.
Expand Down Expand Up @@ -81,29 +76,64 @@ export function OpenFooter({
</a>
</div>

{socials && socials.length > 0 ? (
<nav aria-label="Social" className="flex items-center gap-3">
{socials.map((s) => (
<a
key={s.href}
href={s.href}
target="_blank"
rel="noopener noreferrer"
aria-label={s.label}
className="flex h-10 w-10 items-center justify-center rounded-full bg-ps-violet-dark/5 text-ps-violet-dark/50 transition-colors hover:bg-ps-violet-dark/10 hover:text-ps-violet-dark"
>
{s.icon}
</a>
))}
</nav>
) : null}
<nav aria-label="Social" className="flex items-center gap-3">
<SocialLink href={githubUrl} label="GitHub">
<GitHubIcon />
</SocialLink>
{npmUrl ? (
<SocialLink href={npmUrl} label="npm">
<NpmIcon />
</SocialLink>
) : null}
</nav>
</div>

<p className="mt-8 border-t border-ps-violet-dark/10 pt-6 text-justify font-margem text-xs leading-relaxed text-ps-violet-dark/30 [text-align-last:left]">
{disclaimer}
{DISCLAIMER}
</p>
</div>
</div>
</footer>
);
}

function SocialLink({
children,
href,
label,
}: {
children: ReactNode;
href: string;
label: string;
}) {
return (
Comment thread
rlueder marked this conversation as resolved.
<a
href={href}
target="_blank"
rel="noopener noreferrer"
aria-label={label}
className="flex h-10 w-10 items-center justify-center rounded-full bg-ps-violet-dark/5 text-ps-violet-dark/50 transition-colors hover:bg-ps-violet-dark/10 hover:text-ps-violet-dark"
>
{children}
</a>
);
}

function GitHubIcon() {
return (
<svg viewBox="0 0 24 24" className="h-5 w-5 fill-current" aria-hidden="true">
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
</svg>
);
}

function NpmIcon() {
return (
<svg viewBox="0 0 24 24" className="h-5 w-5 fill-current" aria-hidden="true">
<path d="M0 7.334v8h6.666v1.332H12v-1.332h12v-8H0Zm6.666 6.664H5.334v-4H3.999v4H1.335V8.667h5.331v5.331Zm4 0h-2.666V8.667h5.334v5.331h-2.668v-4h-1.332v4h1.332Zm10.668 0h-1.332v-4h-1.334v4h-1.332v-4h-1.334v4h-2.668V8.667h8.002v5.331h-.002Z" />
</svg>
);
}

const DISCLAIMER =
'Este software é fornecido exclusivamente para fins informativos, de pesquisa e educacionais. Não constitui aconselhamento médico, diagnóstico ou recomendação de tratamento. Os resultados aqui apresentados não substituem a avaliação de um profissional de saúde qualificado. Consulte sempre um profissional de saúde antes de tomar decisões clínicas.';
2 changes: 1 addition & 1 deletion packages/ui/src/layout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type { HeaderProps } from './Header.js';
export { Header } from './Header.js';
export type { MobileDrawerHeader, MobileDrawerItem, MobileDrawerProps } from './MobileDrawer.js';
export { MobileDrawer } from './MobileDrawer.js';
export type { OpenFooterBrand, OpenFooterProps, OpenFooterSocial } from './OpenFooter.js';
export type { OpenFooterBrand, OpenFooterProps } from './OpenFooter.js';
export { OpenFooter } from './OpenFooter.js';
export type { PageContainerProps } from './PageContainer.js';
export { PageContainer } from './PageContainer.js';
Expand Down
Loading