Text, from the Building blocks group of the nertia component library.
npx shadcn@latest add https://ner.sh/r/block-text.jsonThe recipe carries this block's layout and variant; the install brings the component.
{
"registries": {
"@nertia": "https://ner.sh/r/{name}.json"
}
}npx shadcn@latest add @nertia/block-textnpm create nersh@latest my-site -- --site https://nertia.ai/components/building-blocks/element-block-text/site.json scaffolds a project from it.{
"slug": "element-block-text",
"mode": "builder",
"status": "draft",
"skinId": "zinc",
"composition": {
"id": "element-block-text",
"sections": [
{
"id": "block-text",
"instanceId": "stage-block-text"
}
]
},
"copy": {
"stage-block-text.text": "Body text",
"stage-block-text.textStyle": ""
}
}
import type { Section } from '../types';
import { Component } from './Component';
import { schema } from './schema';
import { meta } from './meta';
import { writeCopy } from './writeCopy';
export const blockText: Section = { meta, schema, Component, writeCopy };
import type { SectionProps } from '../types';
import { compositionEditPath } from '@/lib/composition/compositionEditableCopy';
import { Text } from '@/components/foundation';
/**
* `textStyle` is a closed role set: {lead, eyebrow, stat, note, body}. 'lead' is the
* larger opening paragraph on the site's lead type step. 'eyebrow' is the
* small tracked uppercase kicker above a hero heading (the typeshit comp:
* 12px/700/0.16em in the accent colour). 'stat' is the bold hero-figure
* treatment block-stat gives its number, for text nodes standing in as a
* pricing card's price. 'note' is the small muted line under a figure or a
* name (a pricing tier's one-line description, a caption), on the site's
* small type step. Anything else stays body. The class lands after the
* size class so the role's declarations win at equal specificity.
*/
export function Component({ copy, instanceId, overrides }: SectionProps) {
const edit = (slotKey: string) => compositionEditPath(instanceId, slotKey);
const textStyle = (copy.textStyle ?? '').trim().toLowerCase();
const eyebrow = textStyle === 'eyebrow';
const stat = textStyle === 'stat';
const note = textStyle === 'note';
const lead = textStyle === 'lead';
const roleClassName = eyebrow
? 'site-type-eyebrow'
: stat
? 'site-type-stat font-bold text-[var(--foreground)]'
: undefined;
return (
<div data-section="block-text" data-font-body={overrides?.['--font-body'] ? '' : undefined}>
<Text size={note ? 'sm' : lead ? 'lg' : undefined} muted={note} className={roleClassName} dataEditPath={edit('text')}>
{copy.text || 'Body text'}
</Text>
</div>
);
}
import type { SectionCopySchemaField } from '../types';
export const schema: SectionCopySchemaField[] = [
{ key: 'text', label: 'Text', type: 'textarea', placeholder: 'Body text' },
{
key: 'textStyle',
label: 'Style',
type: 'text',
required: false,
maxLength: 7,
advanced: true,
helpText:
'Lead is a larger opening paragraph, Eyebrow a small uppercase label in the accent color, Stat a large bold figure, and Note a small muted line. Body is the normal paragraph style.',
options: [
{ value: '', label: 'Body' },
{ value: 'lead', label: 'Lead' },
{ value: 'eyebrow', label: 'Eyebrow', budgets: { text: { maxLength: 24 } } },
{ value: 'stat', label: 'Stat', budgets: { text: { maxLength: 16 } } },
{ value: 'note', label: 'Note', budgets: { text: { maxLength: 60 } } },
],
},
];
import type { SectionMeta } from '../types';
export const meta: SectionMeta = {
id: 'block-text',
displayName: 'Text',
family: 'component',
tags: ['component', 'atom', 'text', 'body'],
bucket: 'atom',
};
import type { BrandContext } from '@/lib/brandContext';
export function writeCopy(_ctx: BrandContext) {
return { text: 'Body text' };
}