Skip to content
Open
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
17 changes: 17 additions & 0 deletions .changeset/social-icon-tone.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@templatical/types": minor
"@templatical/renderer": minor
"@templatical/editor": minor
"@templatical/import-mjml": patch
"@templatical/import-topol": patch
---

Social icons take a tone, separate from their shape

`SocialIconsBlock` gains an optional `iconTone`: `"brand"` (each platform's own color, the default and what an absent value means), `"dark"` or `"light"`. `iconStyle` stays the shape and gains `"plain"`, the glyph alone with no badge or outline. A filled shape draws the glyph white on the tone, or near-black on `"light"`; `outlined` and `plain` draw in the tone. The editor offers both as separate selects, Style and Color.

The renderer ships a PNG set for every style and tone: 6 × 3 sets of 17 platforms, about 1.9 MB in the package. A brand-colored icon keeps its URL, `{style}/{platform}.png`, byte for byte; a tone lives at `{style}-{tone}/{platform}.png`. If you self-host the icons through `socialIconsBaseUrl`, copy the new folders before using a tone or `plain`.

`@templatical/types` exports `SocialIconTone`, `SOCIAL_ICON_STYLES`, `SOCIAL_ICON_TONES`, `SOCIAL_ICON_TONE_COLORS`, `socialIconColors()` and `socialIconAssetDir()`, the rules the editor, the renderer and the PNG build share.

The MJML importer reads the style and tone back from a tone's folder, and the Topol importer maps its black-and-white `outlinedbw` set to `outlined` in `"dark"`.
8 changes: 4 additions & 4 deletions apps/docs/api/renderer-typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ interface RenderOptions {
| `renderCustomBlock` | -- | Resolves custom blocks to HTML. Called once per custom block. Editor consumers pass `editor.renderCustomBlock`; headless consumers wire their own resolver. If omitted, custom blocks fall back to the block's `renderedHtml` field (if present) and otherwise are omitted. |
| `blockRenderers` | `{}` | Per-type renderer overrides keyed by `block.type`. A countdown GIF backend uses this. An override owns the hidden-on-all-viewports check. |
| `getCustomBlockStylesheet` | -- | `(customType) => string \| undefined \| null`. Called once per unique `customType`. Editor consumers pass the registry stylesheet. |
| `socialIconsBaseUrl` | version-pinned jsDelivr URL | Base URL (no trailing slash) for the social icon PNG assets. Resolved per icon to `${baseUrl}/${style}/${platform}.png`. See [Social icons](#social-icons) below. |
| `socialIconsBaseUrl` | version-pinned jsDelivr URL | Base URL (no trailing slash) for the social icon PNG assets. Resolved per icon to `${baseUrl}/${dir}/${platform}.png`, where `dir` is the style, plus `-${tone}` for a `dark` or `light` `iconTone`. See [Social icons](#social-icons) below. |

### Custom blocks

Expand Down Expand Up @@ -101,10 +101,10 @@ const mjml = await renderToMjml(content, {

### Social icons

Social icon blocks are emitted as `<img src="…/{style}/{platform}.png">`. The default `socialIconsBaseUrl` points at the version-pinned jsDelivr mirror of `@templatical/renderer`, which ships pre-rasterized PNGs (every `SocialPlatform` × 5 styles) alongside the package:
Social icon blocks are emitted as `<img src="…/{dir}/{platform}.png">`. `dir` is the icon style for brand colors (`circle`) and `{style}-{tone}` for an `iconTone` of `dark` or `light` (`circle-dark`). The default `socialIconsBaseUrl` points at the version-pinned jsDelivr mirror of `@templatical/renderer`, which ships pre-rasterized PNGs (every `SocialPlatform` × 6 styles × 3 tones) alongside the package:

```
https://cdn.jsdelivr.net/npm/@templatical/renderer@<version>/assets/social/{style}/{platform}.png
https://cdn.jsdelivr.net/npm/@templatical/renderer@<version>/assets/social/{dir}/{platform}.png
```

**Why PNGs.** Outlook desktop (Word rendering engine) does not support SVG and rejects base64 data URIs in `<img src>`. Hosted PNGs are the only format that renders across every mainstream email client.
Expand All @@ -119,7 +119,7 @@ const mjml = await renderToMjml(content, {
});
```

The exact filenames the renderer expects are `{style}/{platform}.png` where `style` is one of `solid | outlined | rounded | square | circle` and `platform` is one of `facebook | twitter | instagram | linkedin | youtube | tiktok | pinterest | email | whatsapp | telegram | discord | snapchat | reddit | github | dribbble | behance | website`. The shipped 192×192 PNGs are a reasonable starting point if you want to mirror them.
The exact filenames the renderer expects are `{dir}/{platform}.png`, where `dir` is a `style` of `solid | outlined | rounded | square | circle | plain`, followed by `-dark` or `-light` for those colors (a brand-colored icon has no suffix), and `platform` is one of `facebook | twitter | instagram | linkedin | youtube | tiktok | pinterest | email | whatsapp | telegram | discord | snapchat | reddit | github | dribbble | behance | website`. The shipped 192×192 PNGs are a reasonable starting point if you want to mirror them.

The package also exports `DEFAULT_SOCIAL_ICONS_BASE_URL` if you want to compose URLs against the same default:

Expand Down
5 changes: 4 additions & 1 deletion apps/docs/api/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ interface SocialIconsBlock extends BaseBlock {
type: 'social';
icons: SocialIcon[];
iconStyle: SocialIconStyle;
iconTone?: SocialIconTone; // absent means 'brand'
iconSize: SocialIconSize;
spacing: number;
align: 'left' | 'center' | 'right';
Expand All @@ -323,7 +324,9 @@ type SocialPlatform =
| 'website';


type SocialIconStyle = 'solid' | 'outlined' | 'rounded' | 'square' | 'circle';
type SocialIconStyle =
| 'solid' | 'outlined' | 'rounded' | 'square' | 'circle' | 'plain';
type SocialIconTone = 'brand' | 'dark' | 'light';
type SocialIconSize = 'small' | 'medium' | 'large';
```

Expand Down
8 changes: 4 additions & 4 deletions apps/docs/de/api/renderer-typescript.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ interface RenderOptions {
| `renderCustomBlock` | -- | Wandelt benutzerdefinierte Blöcke in HTML um. Wird einmal pro benutzerdefiniertem Block aufgerufen. Editor-Konsumenten übergeben `editor.renderCustomBlock`; Headless-Konsumenten verwenden einen eigenen Resolver. Wenn weggelassen, fällt der Renderer auf das `renderedHtml`-Feld des Blocks zurück (falls vorhanden) und lässt den Block andernfalls weg. |
| `blockRenderers` | `{}` | Typbezogene Renderer-Overrides, keyed nach `block.type`. Ein Countdown-GIF-Backend nutzt dies. Ein Override übernimmt die Prüfung auf „auf allen Viewports ausgeblendet“. |
| `getCustomBlockStylesheet` | -- | `(customType) => string \| undefined \| null`. Wird einmal pro eindeutigem `customType` aufgerufen. Editor-Konsumenten übergeben das Registry-Stylesheet. |
| `socialIconsBaseUrl` | versionsgebundene jsDelivr-URL | Basis-URL (ohne abschließenden Schrägstrich) für die PNG-Assets der Social-Media-Icons. Wird pro Icon zu `${baseUrl}/${style}/${platform}.png` aufgelöst. Siehe [Social-Media-Icons](#social-media-icons) unten. |
| `socialIconsBaseUrl` | versionsgebundene jsDelivr-URL | Basis-URL (ohne abschließenden Schrägstrich) für die PNG-Assets der Social-Media-Icons. Wird pro Icon zu `${baseUrl}/${dir}/${platform}.png` aufgelöst, wobei `dir` der Stil ist, bei einem `iconTone` von `dark` oder `light` gefolgt von `-${tone}`. Siehe [Social-Media-Icons](#social-media-icons) unten. |

### Benutzerdefinierte Blöcke

Expand Down Expand Up @@ -101,10 +101,10 @@ const mjml = await renderToMjml(content, {

### Social-Media-Icons

Social-Icon-Blöcke werden als `<img src="…/{style}/{platform}.png">` ausgegeben. Der Standardwert von `socialIconsBaseUrl` verweist auf den versionsgebundenen jsDelivr-Mirror von `@templatical/renderer`, der vorgerasterte PNGs (jede `SocialPlatform` × 5 Stile) mit dem Paket ausliefert:
Social-Icon-Blöcke werden als `<img src="…/{dir}/{platform}.png">` ausgegeben. `dir` ist bei Markenfarben der Icon-Stil (`circle`) und bei einem `iconTone` von `dark` oder `light` `{style}-{tone}` (`circle-dark`). Der Standardwert von `socialIconsBaseUrl` verweist auf den versionsgebundenen jsDelivr-Mirror von `@templatical/renderer`, der vorgerasterte PNGs (jede `SocialPlatform` × 6 Stile × 3 Farbtöne) mit dem Paket ausliefert:

```
https://cdn.jsdelivr.net/npm/@templatical/renderer@<version>/assets/social/{style}/{platform}.png
https://cdn.jsdelivr.net/npm/@templatical/renderer@<version>/assets/social/{dir}/{platform}.png
```

**Warum PNGs.** Outlook Desktop (Word-Rendering-Engine) unterstützt kein SVG und lehnt base64-Daten-URIs in `<img src>` ab. Gehostete PNGs sind das einzige Format, das in allen gängigen E-Mail-Clients zuverlässig dargestellt wird.
Expand All @@ -119,7 +119,7 @@ const mjml = await renderToMjml(content, {
});
```

Die exakten Dateinamen, die der Renderer erwartet, sind `{style}/{platform}.png`, wobei `style` einer von `solid | outlined | rounded | square | circle` und `platform` einer von `facebook | twitter | instagram | linkedin | youtube | tiktok | pinterest | email | whatsapp | telegram | discord | snapchat | reddit | github | dribbble | behance | website` ist. Die ausgelieferten 192×192-PNGs sind ein sinnvoller Ausgangspunkt, wenn Sie sie spiegeln möchten.
Die exakten Dateinamen, die der Renderer erwartet, sind `{dir}/{platform}.png`, wobei `dir` ein `style` aus `solid | outlined | rounded | square | circle | plain` ist, für diese Farben gefolgt von `-dark` oder `-light` (ein Icon in Markenfarbe hat kein Suffix), und `platform` einer von `facebook | twitter | instagram | linkedin | youtube | tiktok | pinterest | email | whatsapp | telegram | discord | snapchat | reddit | github | dribbble | behance | website` ist. Die ausgelieferten 192×192-PNGs sind ein sinnvoller Ausgangspunkt, wenn Sie sie spiegeln möchten.

Das Paket exportiert außerdem `DEFAULT_SOCIAL_ICONS_BASE_URL`, falls Sie URLs gegen denselben Standardwert komponieren möchten:

Expand Down
5 changes: 4 additions & 1 deletion apps/docs/de/api/types.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ interface SocialIconsBlock extends BaseBlock {
type: 'social';
icons: SocialIcon[];
iconStyle: SocialIconStyle;
iconTone?: SocialIconTone; // fehlt: 'brand'
iconSize: SocialIconSize;
spacing: number;
align: 'left' | 'center' | 'right';
Expand All @@ -323,7 +324,9 @@ type SocialPlatform =
| 'website';


type SocialIconStyle = 'solid' | 'outlined' | 'rounded' | 'square' | 'circle';
type SocialIconStyle =
| 'solid' | 'outlined' | 'rounded' | 'square' | 'circle' | 'plain';
type SocialIconTone = 'brand' | 'dark' | 'light';
type SocialIconSize = 'small' | 'medium' | 'large';
```

Expand Down
3 changes: 2 additions & 1 deletion apps/docs/de/guide/blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ Eine Reihe von Social-Media-Icons, die zu Plattformprofilen verlinken.
| Eigenschaft | Typ | Beschreibung |
|----------|------|-------------|
| `icons` | `SocialIcon[]` | Liste der Social Icons |
| `iconStyle` | `'solid' \| 'outlined' \| 'rounded' \| 'square' \| 'circle'` | Visueller Stil |
| `iconStyle` | `'solid' \| 'outlined' \| 'rounded' \| 'square' \| 'circle' \| 'plain'` | Form. `plain` ist das Symbol allein, ohne Fläche oder Umriss |
| `iconTone` | `'brand' \| 'dark' \| 'light'` | Optional. Die Markenfarbe jeder Plattform (Standard) oder ein Farbton für alle Icons. Eine gefüllte Form zeigt das Symbol weiß auf dem Farbton, bei `light` fast schwarz |
| `iconSize` | `'small' \| 'medium' \| 'large'` | Icon-Größe |
| `spacing` | `number` | Abstand zwischen Icons in px |
| `align` | `'left' \| 'center' \| 'right'` | Horizontale Ausrichtung |
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/guide/blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ A row of social media icons linking to platform profiles.
| Property | Type | Description |
|----------|------|-------------|
| `icons` | `SocialIcon[]` | List of social icons |
| `iconStyle` | `'solid' \| 'outlined' \| 'rounded' \| 'square' \| 'circle'` | Visual style |
| `iconStyle` | `'solid' \| 'outlined' \| 'rounded' \| 'square' \| 'circle' \| 'plain'` | Shape. `plain` is the glyph alone, with no badge or outline |
| `iconTone` | `'brand' \| 'dark' \| 'light'` | Optional. Each platform's brand color (the default), or one tone for every icon. A filled shape draws the glyph in white on the tone, or near-black on `light` |
| `iconSize` | `'small' \| 'medium' \| 'large'` | Icon size |
| `spacing` | `number` | Space between icons in px |
| `align` | `'left' \| 'center' \| 'right'` | Horizontal alignment |
Expand Down
16 changes: 10 additions & 6 deletions apps/docs/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ interface RenderOptions {
| `renderCustomBlock` | -- | Resolves custom blocks to HTML. Called once per custom block. Editor consumers pass `editor.renderCustomBlock`; headless consumers wire their own resolver. If omitted, custom blocks fall back to the block's `renderedHtml` field (if present) and otherwise are omitted. |
| `blockRenderers` | `{}` | Per-type renderer overrides keyed by `block.type`. A countdown GIF backend uses this. An override owns the hidden-on-all-viewports check. |
| `getCustomBlockStylesheet` | -- | `(customType) => string \| undefined \| null`. Called once per unique `customType`. Editor consumers pass the registry stylesheet. |
| `socialIconsBaseUrl` | version-pinned jsDelivr URL | Base URL (no trailing slash) for the social icon PNG assets. Resolved per icon to `${baseUrl}/${style}/${platform}.png`. See [Social icons](#social-icons) below. |
| `socialIconsBaseUrl` | version-pinned jsDelivr URL | Base URL (no trailing slash) for the social icon PNG assets. Resolved per icon to `${baseUrl}/${dir}/${platform}.png`, where `dir` is the style, plus `-${tone}` for a `dark` or `light` `iconTone`. See [Social icons](#social-icons) below. |

### Custom blocks

Expand Down Expand Up @@ -777,10 +777,10 @@ const mjml = await renderToMjml(content, {

### Social icons

Social icon blocks are emitted as `<img src="…/{style}/{platform}.png">`. The default `socialIconsBaseUrl` points at the version-pinned jsDelivr mirror of `@templatical/renderer`, which ships pre-rasterized PNGs (every `SocialPlatform` × 5 styles) alongside the package:
Social icon blocks are emitted as `<img src="…/{dir}/{platform}.png">`. `dir` is the icon style for brand colors (`circle`) and `{style}-{tone}` for an `iconTone` of `dark` or `light` (`circle-dark`). The default `socialIconsBaseUrl` points at the version-pinned jsDelivr mirror of `@templatical/renderer`, which ships pre-rasterized PNGs (every `SocialPlatform` × 6 styles × 3 tones) alongside the package:

```
https://cdn.jsdelivr.net/npm/@templatical/renderer@<version>/assets/social/{style}/{platform}.png
https://cdn.jsdelivr.net/npm/@templatical/renderer@<version>/assets/social/{dir}/{platform}.png
```

**Why PNGs.** Outlook desktop (Word rendering engine) does not support SVG and rejects base64 data URIs in `<img src>`. Hosted PNGs are the only format that renders across every mainstream email client.
Expand All @@ -795,7 +795,7 @@ const mjml = await renderToMjml(content, {
});
```

The exact filenames the renderer expects are `{style}/{platform}.png` where `style` is one of `solid | outlined | rounded | square | circle` and `platform` is one of `facebook | twitter | instagram | linkedin | youtube | tiktok | pinterest | email | whatsapp | telegram | discord | snapchat | reddit | github | dribbble | behance | website`. The shipped 192×192 PNGs are a reasonable starting point if you want to mirror them.
The exact filenames the renderer expects are `{dir}/{platform}.png`, where `dir` is a `style` of `solid | outlined | rounded | square | circle | plain`, followed by `-dark` or `-light` for those colors (a brand-colored icon has no suffix), and `platform` is one of `facebook | twitter | instagram | linkedin | youtube | tiktok | pinterest | email | whatsapp | telegram | discord | snapchat | reddit | github | dribbble | behance | website`. The shipped 192×192 PNGs are a reasonable starting point if you want to mirror them.

The package also exports `DEFAULT_SOCIAL_ICONS_BASE_URL` if you want to compose URLs against the same default:

Expand Down Expand Up @@ -1425,6 +1425,7 @@ interface SocialIconsBlock extends BaseBlock {
type: 'social';
icons: SocialIcon[];
iconStyle: SocialIconStyle;
iconTone?: SocialIconTone; // absent means 'brand'
iconSize: SocialIconSize;
spacing: number;
align: 'left' | 'center' | 'right';
Expand All @@ -1444,7 +1445,9 @@ type SocialPlatform =
| 'website';


type SocialIconStyle = 'solid' | 'outlined' | 'rounded' | 'square' | 'circle';
type SocialIconStyle =
| 'solid' | 'outlined' | 'rounded' | 'square' | 'circle' | 'plain';
type SocialIconTone = 'brand' | 'dark' | 'light';
type SocialIconSize = 'small' | 'medium' | 'large';
```

Expand Down Expand Up @@ -4992,7 +4995,8 @@ A row of social media icons linking to platform profiles.
| Property | Type | Description |
|----------|------|-------------|
| `icons` | `SocialIcon[]` | List of social icons |
| `iconStyle` | `'solid' \| 'outlined' \| 'rounded' \| 'square' \| 'circle'` | Visual style |
| `iconStyle` | `'solid' \| 'outlined' \| 'rounded' \| 'square' \| 'circle' \| 'plain'` | Shape. `plain` is the glyph alone, with no badge or outline |
| `iconTone` | `'brand' \| 'dark' \| 'light'` | Optional. Each platform's brand color (the default), or one tone for every icon. A filled shape draws the glyph in white on the tone, or near-black on `light` |
| `iconSize` | `'small' \| 'medium' \| 'large'` | Icon size |
| `spacing` | `number` | Space between icons in px |
| `align` | `'left' \| 'center' \| 'right'` | Horizontal alignment |
Expand Down
31 changes: 19 additions & 12 deletions packages/editor/src/components/blocks/SocialIconSvg.vue
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
<script setup lang="ts">
import { socialIcons, socialIconSizeMap } from "../../constants/socialIcons";
import type {
SocialIconTone,
SocialIconSize,
SocialIconStyle,
SocialPlatform,
} from "@templatical/types";
import { socialIconColors, socialIconGlyphScale } from "@templatical/types";
import { computed } from "vue";

const props = defineProps<{
platform: SocialPlatform;
iconStyle: SocialIconStyle;
iconTone?: SocialIconTone;
iconSize: SocialIconSize;
}>();

const iconDef = computed(() => socialIcons[props.platform]);
const size = computed(() => socialIconSizeMap[props.iconSize]);
// The same colors the renderer's PNGs are drawn in.
const colors = computed(() => socialIconColors(props.platform, props.iconTone));

const containerStyle = computed(() => {
const baseStyles: Record<string, string> = {
Expand All @@ -24,52 +29,54 @@ const containerStyle = computed(() => {
width: `${size.value}px`,
height: `${size.value}px`,
};
const fill = colors.value.fill;

switch (props.iconStyle) {
case "solid":
return {
...baseStyles,
backgroundColor: iconDef.value.color,
backgroundColor: fill,
borderRadius: "4px",
};
case "outlined":
return {
...baseStyles,
backgroundColor: "transparent",
border: `2px solid ${iconDef.value.color}`,
border: `2px solid ${fill}`,
borderRadius: "4px",
};
case "rounded":
return {
...baseStyles,
backgroundColor: iconDef.value.color,
backgroundColor: fill,
borderRadius: "8px",
};
case "square":
return {
...baseStyles,
backgroundColor: iconDef.value.color,
backgroundColor: fill,
borderRadius: "0",
};
case "circle":
return {
...baseStyles,
backgroundColor: iconDef.value.color,
backgroundColor: fill,
borderRadius: "50%",
};
default:
return baseStyles;
}
});

const svgSize = computed(() => Math.floor(size.value * 0.6));
const svgSize = computed(() =>
Math.floor(size.value * socialIconGlyphScale(props.iconStyle)),
);

const svgColor = computed(() => {
if (props.iconStyle === "outlined") {
return iconDef.value.color;
}
return "#ffffff";
});
const svgColor = computed(() =>
props.iconStyle === "outlined" || props.iconStyle === "plain"
? colors.value.fill
: colors.value.onFill,
);
</script>

<template>
Expand Down
1 change: 1 addition & 0 deletions packages/editor/src/components/blocks/SocialIconsBlock.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const hasIcons = computed(() => props.block.icons.length > 0);
<SocialIconSvg
:platform="icon.platform"
:icon-style="block.iconStyle"
:icon-tone="block.iconTone"
:icon-size="block.iconSize"
/>
</a>
Expand Down
Loading