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
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import type { LegalBlock } from '@/app/(landing)/components/prose-page/types'
/**
* Renders a single {@link LegalBlock} into its canonical chrome. The block's
* `kind` discriminant selects the element (paragraph / subheading `<h3>` /
* bulleted list / callout box); all sizing and color come from `PROSE_TYPE`, so
* Terms and Privacy share one visual treatment for every block type. Content
* bulleted list / callout box / reference table); all sizing and color come
* from `PROSE_TYPE`, so Terms and Privacy share one visual treatment for every
* block type. Content
* only - no layout knob. Server Component.
*/

Expand Down Expand Up @@ -35,6 +36,46 @@ export function LegalBlockView({ block }: LegalBlockViewProps) {
)
case 'callout':
return <div className={PROSE_TYPE.callout}>{block.content}</div>
case 'table':
return (
<div className={PROSE_TYPE.tableWrap}>
<table className={PROSE_TYPE.table}>
{block.caption ? (
<caption className={PROSE_TYPE.tableCaption}>{block.caption}</caption>
) : null}
{block.columnWidths ? (
<colgroup>
{block.columnWidths.map((width, index) => (
<col key={`col-${index}`} className={width} />
))}
</colgroup>
) : null}
<thead>
<tr>
{block.columns.map((column) => (
<th key={column} scope='col' className={PROSE_TYPE.tableHeadCell}>
{column}
</th>
))}
</tr>
</thead>
<tbody>
{block.rows.map((row, rowIndex) => {
const rowKey = `row-${rowIndex}`
return (
<tr key={rowKey}>
{row.map((cell, cellIndex) => (
<td key={`${rowKey}-${cellIndex}`} className={PROSE_TYPE.tableCell}>
{block.codeColumns?.includes(cellIndex) ? <code>{cell}</code> : cell}
</td>
))}
</tr>
)
})}
</tbody>
</table>
</div>
)
default:
return null
}
Expand Down
17 changes: 17 additions & 0 deletions apps/sim/app/(landing)/components/prose-page/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ export const PROSE_SPACING = {
listIndent: 'pl-6',
} as const

/**
* Column widths for the cookie-inventory tables. Sibling tables sharing a header
* must be given a fixed layout or each sizes itself to its own content and the
* group reads as unaligned grids — so the widths are chrome, and live here
* rather than as class strings in a content config.
*/
export const PROSE_TABLE_WIDTHS = {
cookieInventory: ['w-[24%]', 'w-[16%]', 'w-[45%]', 'w-[15%]'],
} as const

/**
* Prose type tokens - the single source of truth for every heading size, body
* color, list, callout, and inline-link treatment. Centralized alongside the
Expand All @@ -53,4 +63,11 @@ export const PROSE_TYPE = {
callout:
'rounded-lg border border-[var(--border)] bg-[var(--surface-2)] px-4 py-3 text-[14px] text-[var(--text-body)] leading-[1.6]',
link: 'text-[var(--text-primary)] underline underline-offset-2 transition-colors hover:text-[var(--text-body)]',
tableWrap: 'w-full overflow-x-auto',
tableCaption: 'pb-2 text-left text-[15px] text-[var(--text-primary)]',
table: 'w-full min-w-[560px] table-fixed border-collapse text-left',
tableHeadCell:
'border-[var(--border)] border-b px-3 py-2 align-bottom font-medium text-[13px] text-[var(--text-primary)] first:pl-0 last:pr-0',
tableCell:
'border-[var(--border)] border-b px-3 py-2.5 align-top text-[14px] text-[var(--text-body)] leading-[1.55] first:pl-0 last:pr-0 [&_code]:font-mono [&_code]:text-[13px]',
} as const
27 changes: 27 additions & 0 deletions apps/sim/app/(landing)/components/prose-page/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,33 @@ export type LegalBlock =
| { kind: 'list'; items: ReactNode[] }
/** An emphasized callout box (e.g. the arbitration / GDPR notices). */
| { kind: 'callout'; content: ReactNode }
/**
* A reference table — the cookie inventory's name / provider / purpose /
* retention grid. Rows are positional against `columns`, so every row must
* have the same length as the header.
*/
| {
kind: 'table'
caption?: string
columns: string[]
/**
* Tailwind width fragments applied per column, e.g. `['w-[22%]', …]`. Set
* them whenever a page renders sibling tables with the same columns:
* without a fixed layout each table sizes itself to its own content and
* the group reads as three unaligned grids.
*/
columnWidths?: string[]
/**
* Indices of columns rendered as inline code — cookie names, config keys.
* A marker rather than a `<code>` in the row data, because a row carries
* content and the renderer owns chrome. It is also what keeps the rows
* plain strings: biome's `useJsxKeyInIterable` fires on JSX inside an
* array literal, and the key it wants means nothing to a cell the
* renderer already keys by column.
*/
codeColumns?: number[]
rows: ReactNode[][]
}

/** A numbered (or named) legal section - an `<h2>` plus its ordered blocks. */
export interface LegalSection {
Expand Down
31 changes: 31 additions & 0 deletions apps/sim/app/(landing)/cookie-policy/consent-preferences-link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client'

import type { ReactNode } from 'react'
import { OPEN_CONSENT_PREFERENCES_EVENT } from '@/lib/consent/constants'
import { PROSE_TYPE } from '@/app/(landing)/components/prose-page/constants'

interface ConsentPreferencesLinkProps {
children: ReactNode
}

/**
* Inline control that reopens the consent banner with its category switches
* expanded, so a recorded choice can be withdrawn or changed. Wearing the
* prose link chrome, it reads as part of the sentence it sits in.
*
* Only rendered where the consent runtime is mounted — see the call site. On a
* self-hosted deployment nothing would listen for the event, so the Cookie
* Policy renders the phrase as plain text rather than a control that does
* nothing when clicked.
*/
export function ConsentPreferencesLink({ children }: ConsentPreferencesLinkProps) {
return (
<button
type='button'
className={PROSE_TYPE.link}
onClick={() => window.dispatchEvent(new Event(OPEN_CONSENT_PREFERENCES_EVENT))}
>
Comment thread
waleedlatif1 marked this conversation as resolved.
{children}
</button>
)
}
Loading
Loading