Skip to content

Added email design foundation components#26841

Merged
troyciesco merged 4 commits intomainfrom
cmraible/welcome-email-foundation
Mar 18, 2026
Merged

Added email design foundation components#26841
troyciesco merged 4 commits intomainfrom
cmraible/welcome-email-foundation

Conversation

@cmraible
Copy link
Copy Markdown
Collaborator

@cmraible cmraible commented Mar 17, 2026

Summary

Foundation layer for the welcome email design customization modal:

  • types.tsEmailDesignSettings interface and default values
  • design-utils.ts — Color manipulation (contrast, luminance) and font resolution utilities
  • email-design-context.tsx — React context provider for design settings state management
  • color-picker-field.tsx — Reusable color picker form field wrapper
  • email-design-modal.tsx — Shared modal layout with sidebar + preview panes
  • shade/index.ts — Exported ColorPicker component from shade

Stack: PR 1 of 3 — this PR targets main. Next: #26842 (visual components) → #26843 (modal wiring)

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 17, 2026

Walkthrough

This change introduces a new email design settings system for the admin interface. It adds five new modules to the email-design directory: a ColorPickerField component for color input, a design-utils module with color resolution logic and ResolvedEmailColors interface, an email-design-context file with React context and provider for state management, an email-design-modal component for a two-pane settings modal, and a types file defining EmailDesignSettings interface and default values. Additionally, the shade component library exports ColorPicker and ColorPickerProps to make the color picker functionality available across the application.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Added email design foundation components' accurately and concisely summarizes the primary change—introducing foundational components and utilities for email design customization.
Description check ✅ Passed The description clearly outlines all the foundation components being added (types, utilities, context, UI components) and explains the PR's role within a larger three-part stack, directly relating to the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cmraible/welcome-email-foundation
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cmraible cmraible changed the title ✨ Added email design foundation components Added email design foundation components Mar 17, 2026
/* eslint-env node */
const tailwindConfig = `${__dirname}/tailwind.config.cjs`;

module.exports = {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole file diff will go away once this PR is merged.

@cmraible cmraible marked this pull request as ready for review March 17, 2026 05:45
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsx`:
- Around line 19-33: The color swatch trigger button lacks an explicit
accessible name; add one on the PopoverTrigger/button (e.g., aria-label or
aria-labelledby) so screen readers can identify it (use the existing title/
title prop text). For example, set aria-label={`Select ${title} color`} on the
button or give the adjacent span a unique id and reference it via
aria-labelledby on the button; update the button inside PopoverTrigger and, if
using aria-labelledby, add the id to the inner span that currently renders the
color value.

In `@apps/admin-x-settings/src/components/settings/email-design/design-utils.ts`:
- Around line 28-37: The resolver resolveHeaderBackgroundColor currently only
accepts hex values or 'transparent' and returns 'transparent' for other strings;
update it to treat the literal string 'accent' as a valid value and return
'accent' (i.e., if value === 'accent' return 'accent') in addition to the
existing checks so the preview matches the email renderer; apply the same change
to the other similar resolver referenced around the 102-105 region (the other
header/accent color resolver) so both functions accept and return 'accent' when
present, leaving VALID_HEX handling unchanged.
- Line 19: The VALID_HEX regex allows suffix matches like "foo#fff" because it
lacks a start anchor; update the constant VALID_HEX to require the entire string
to be a hex color by adding a start anchor and using a pattern that matches
either 3 or 6 hex digits (for example change VALID_HEX to a regex that begins
with ^ and matches # followed by exactly 3 or 6 hex characters, e.g. use
/^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i or equivalent) so only whole-string valid hex
colors pass.

In `@apps/shade/src/index.ts`:
- Around line 58-59: The export blanket (`export * from
'./components/features/color-picker/color-picker'`) exposes internal symbols
(e.g., useColorPicker, ColorPickerRoot, internal prop types) as part of the
public API; remove the wildcard and replace it with an explicit named export
list that only publishes intended public symbols (for example keep export {
default as ColorPicker } and, if required, explicitly add only approved symbols
like export { ColorPickerProps } or other specific utilities), ensuring internal
helpers (useColorPicker, ColorPickerRoot, etc.) are not re-exported.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a4252f2a-975c-411a-a50b-3a18750995d3

📥 Commits

Reviewing files that changed from the base of the PR and between bea537d and debefad.

📒 Files selected for processing (7)
  • apps/admin-x-settings/.eslintrc.cjs
  • apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsx
  • apps/admin-x-settings/src/components/settings/email-design/design-utils.ts
  • apps/admin-x-settings/src/components/settings/email-design/email-design-context.tsx
  • apps/admin-x-settings/src/components/settings/email-design/email-design-modal.tsx
  • apps/admin-x-settings/src/components/settings/email-design/types.ts
  • apps/shade/src/index.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/admin-x-settings/src/components/settings/email-design/design-utils.ts (1)

105-123: Note: resolveAllColors is exported but not currently used in the codebase.

The technical concern about accentColor validation is sound—if this function is integrated for use, accentColor should be validated once at entry before being passed to downstream resolver functions. The existing VALID_HEX constant (line 19) can support this. However, since the function has no active callers, prioritize integrating it into the actual color resolution flow before implementing the hardening. If this is preparation for future use or a planned refactor to consolidate the color logic currently scattered in newsletter-preview.tsx, proceed with the hardening. If not, consider whether this function is necessary or should be replaced by the newsletter preview's existing color resolution logic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-x-settings/src/components/settings/email-design/design-utils.ts`
around lines 105 - 123, resolveAllColors is exported but unused and does not
validate the accentColor input; if you plan to consolidate color logic here,
validate accentColor at the start of resolveAllColors using the existing
VALID_HEX constant and sanitize/normalize it (or fall back to a safe default)
before calling resolveHeaderBackgroundColor, resolveButtonColor,
resolvePostTitleColor, resolveSectionTitleColor, resolveLinkColor, and
resolveDividerColor; alternatively, if this helper is not intended for use yet,
either wire resolveAllColors into the newsletter-preview.tsx color flow
(replacing the scattered resolution logic) or remove it to avoid dead code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/admin-x-settings/src/components/settings/email-design/design-utils.ts`:
- Around line 105-123: resolveAllColors is exported but unused and does not
validate the accentColor input; if you plan to consolidate color logic here,
validate accentColor at the start of resolveAllColors using the existing
VALID_HEX constant and sanitize/normalize it (or fall back to a safe default)
before calling resolveHeaderBackgroundColor, resolveButtonColor,
resolvePostTitleColor, resolveSectionTitleColor, resolveLinkColor, and
resolveDividerColor; alternatively, if this helper is not intended for use yet,
either wire resolveAllColors into the newsletter-preview.tsx color flow
(replacing the scattered resolution logic) or remove it to avoid dead code.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d97c1c4-8762-45e5-9700-3a9fc7de37b6

📥 Commits

Reviewing files that changed from the base of the PR and between debefad and 8c5dbe0.

📒 Files selected for processing (3)
  • apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsx
  • apps/admin-x-settings/src/components/settings/email-design/design-utils.ts
  • apps/shade/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsx
  • apps/shade/src/index.ts

@cmraible cmraible requested a review from troyciesco March 17, 2026 06:21
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/admin-x-settings/src/components/settings/email-design/design-utils.ts`:
- Around line 100-123: Normalize and validate the incoming accentColor at the
start of resolveAllColors (e.g., compute a normalizedAccent variable via the
project's color-normalization helper or a small validation routine) and use that
normalizedAccent instead of accentColor when calling
resolveHeaderBackgroundColor, resolveButtonColor, resolvePostTitleColor,
resolveSectionTitleColor, resolveLinkColor, resolveDividerColor; ensure the
normalization returns a safe fallback (empty/transparent or a default theme
accent) when the input is malformed so invalid CSS colors cannot propagate
through the resolved fields.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 830d1f2d-f2e0-4cbc-9675-d14f50585675

📥 Commits

Reviewing files that changed from the base of the PR and between 8c5dbe0 and 1b20510.

📒 Files selected for processing (1)
  • apps/admin-x-settings/src/components/settings/email-design/design-utils.ts

Comment on lines +100 to +123
export function resolveAllColors(settings: EmailDesignSettings, accentColor: string): ResolvedEmailColors {
const bgColor = resolveBackgroundColor(settings.background_color);
const headerBgColor = resolveHeaderBackgroundColor(settings.header_background_color, accentColor);
const buttonColor = resolveButtonColor(settings.button_color, accentColor, bgColor);

const textColor = textColorForBackgroundColor(bgColor).hex();
const secondaryTextColor = textColorForBackgroundColor(bgColor).alpha(0.5).toString();
const headerTextColor = headerBgColor === 'transparent' ? textColor : textColorForBackgroundColor(headerBgColor).hex();
const secondaryHeaderTextColor = headerBgColor === 'transparent' ? secondaryTextColor : textColorForBackgroundColor(headerBgColor).alpha(0.5).toString();

return {
backgroundColor: bgColor,
headerBackgroundColor: headerBgColor,
postTitleColor: resolvePostTitleColor(settings.post_title_color, accentColor, bgColor, headerBgColor),
sectionTitleColor: resolveSectionTitleColor(settings.section_title_color, accentColor, bgColor),
buttonColor,
buttonTextColor: resolveButtonTextColor(settings.button_style, buttonColor),
linkColor: resolveLinkColor(settings.link_color, accentColor, bgColor),
dividerColor: resolveDividerColor(settings.divider_color, accentColor),
textColor,
secondaryTextColor,
headerTextColor,
secondaryHeaderTextColor
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Normalize accentColor before using it across resolvers.

resolveAllColors currently trusts accentColor as-is. If it is malformed, preview output can drift from server-rendered email behavior (which normalizes accent values), and invalid CSS colors can propagate through resolved fields.

Suggested fix
 const VALID_HEX = /^#(?:[0-9a-f]{3}){1,2}$/i;
+const DEFAULT_ACCENT_COLOR = '#15212A';
+
+function resolveAccentColor(value: string): string {
+    return VALID_HEX.test(value) ? value : DEFAULT_ACCENT_COLOR;
+}
@@
 export function resolveAllColors(settings: EmailDesignSettings, accentColor: string): ResolvedEmailColors {
+    const resolvedAccentColor = resolveAccentColor(accentColor);
     const bgColor = resolveBackgroundColor(settings.background_color);
-    const headerBgColor = resolveHeaderBackgroundColor(settings.header_background_color, accentColor);
-    const buttonColor = resolveButtonColor(settings.button_color, accentColor, bgColor);
+    const headerBgColor = resolveHeaderBackgroundColor(settings.header_background_color, resolvedAccentColor);
+    const buttonColor = resolveButtonColor(settings.button_color, resolvedAccentColor, bgColor);
@@
-        postTitleColor: resolvePostTitleColor(settings.post_title_color, accentColor, bgColor, headerBgColor),
-        sectionTitleColor: resolveSectionTitleColor(settings.section_title_color, accentColor, bgColor),
+        postTitleColor: resolvePostTitleColor(settings.post_title_color, resolvedAccentColor, bgColor, headerBgColor),
+        sectionTitleColor: resolveSectionTitleColor(settings.section_title_color, resolvedAccentColor, bgColor),
         buttonColor,
         buttonTextColor: resolveButtonTextColor(settings.button_style, buttonColor),
-        linkColor: resolveLinkColor(settings.link_color, accentColor, bgColor),
-        dividerColor: resolveDividerColor(settings.divider_color, accentColor),
+        linkColor: resolveLinkColor(settings.link_color, resolvedAccentColor, bgColor),
+        dividerColor: resolveDividerColor(settings.divider_color, resolvedAccentColor),
         textColor,
         secondaryTextColor,
         headerTextColor,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-x-settings/src/components/settings/email-design/design-utils.ts`
around lines 100 - 123, Normalize and validate the incoming accentColor at the
start of resolveAllColors (e.g., compute a normalizedAccent variable via the
project's color-normalization helper or a small validation routine) and use that
normalizedAccent instead of accentColor when calling
resolveHeaderBackgroundColor, resolveButtonColor, resolvePostTitleColor,
resolveSectionTitleColor, resolveLinkColor, resolveDividerColor; ensure the
normalization returns a safe fallback (empty/transparent or a default theme
accent) when the input is malformed so invalid CSS colors cannot propagate
through the resolved fields.

cmraible and others added 4 commits March 18, 2026 09:03
Added shared types, utilities, context provider, and modal layout for
email design customization. Exported ColorPicker from shade.

New files:
- types.ts: EmailDesignSettings interface and defaults
- design-utils.ts: Color manipulation and font resolution utilities
- email-design-context.tsx: React context for design settings state
- color-picker-field.tsx: Reusable color picker form field
- email-design-modal.tsx: Shared modal layout with sidebar + preview
Uses absolute path for tailwind.config.cjs so eslint produces
consistent results regardless of CWD.
- Added aria-label to color picker button for accessibility
- Anchored hex color regex to reject malformed values like "foo#fff"
- Handle 'accent' value in resolveHeaderBackgroundColor
- Narrowed ColorPicker export from shade to only ColorPickerProps type
@troyciesco troyciesco force-pushed the cmraible/welcome-email-foundation branch from 1b20510 to 18b07f5 Compare March 18, 2026 13:16
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
apps/admin-x-settings/src/components/settings/email-design/types.ts (1)

1-18: Consider using string literal unions for fields with known finite values.

Fields like title_alignment, button_style, button_corners, link_style, image_corners, and divider_style have known valid values based on the defaults. Using union types would improve type safety and IDE autocompletion.

♻️ Suggested type refinements
 export interface EmailDesignSettings {
     background_color: string;
-    title_font_category: string;
-    title_font_weight: string;
-    body_font_category: string;
+    title_font_category: 'sans_serif' | 'serif';
+    title_font_weight: 'normal' | 'bold';
+    body_font_category: 'sans_serif' | 'serif';
     header_background_color: string;
     post_title_color: string | null;
-    title_alignment: string;
+    title_alignment: 'left' | 'center';
     section_title_color: string | null;
     button_color: string | null;
-    button_style: string;
-    button_corners: string;
+    button_style: 'fill' | 'outline';
+    button_corners: 'rounded' | 'square';
     link_color: string | null;
-    link_style: string;
-    image_corners: string;
+    link_style: 'underline' | 'none';
+    image_corners: 'rounded' | 'square';
     divider_color: string | null;
-    divider_style: string;
+    divider_style: 'solid' | 'dashed' | 'none';
 }

Note: Adjust the union values based on the actual options supported by the design system. This is a suggested starting point.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/admin-x-settings/src/components/settings/email-design/types.ts` around
lines 1 - 18, The EmailDesignSettings interface currently uses plain strings for
several fields; change fields with known finite option sets (title_alignment,
button_style, button_corners, link_style, image_corners, divider_style and any
other enumerated fields) to string literal union types to improve type safety
and autocomplete (e.g., title_alignment: 'left' | 'center' | 'right',
button_style: 'solid' | 'outline' | ...), update the EmailDesignSettings
declaration accordingly, and ensure any code that constructs or reads these
fields (components, default objects, and setters) is updated to use the new
union values or casts to keep TS happy.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/admin-x-settings/src/components/settings/email-design/types.ts`:
- Around line 1-18: The EmailDesignSettings interface currently uses plain
strings for several fields; change fields with known finite option sets
(title_alignment, button_style, button_corners, link_style, image_corners,
divider_style and any other enumerated fields) to string literal union types to
improve type safety and autocomplete (e.g., title_alignment: 'left' | 'center' |
'right', button_style: 'solid' | 'outline' | ...), update the
EmailDesignSettings declaration accordingly, and ensure any code that constructs
or reads these fields (components, default objects, and setters) is updated to
use the new union values or casts to keep TS happy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c37b698a-af8b-4268-a6f9-10254614e409

📥 Commits

Reviewing files that changed from the base of the PR and between 1b20510 and 18b07f5.

📒 Files selected for processing (6)
  • apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsx
  • apps/admin-x-settings/src/components/settings/email-design/design-utils.ts
  • apps/admin-x-settings/src/components/settings/email-design/email-design-context.tsx
  • apps/admin-x-settings/src/components/settings/email-design/email-design-modal.tsx
  • apps/admin-x-settings/src/components/settings/email-design/types.ts
  • apps/shade/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsx
  • apps/admin-x-settings/src/components/settings/email-design/email-design-context.tsx
  • apps/admin-x-settings/src/components/settings/email-design/email-design-modal.tsx
  • apps/admin-x-settings/src/components/settings/email-design/design-utils.ts

@troyciesco troyciesco merged commit 29d1fb6 into main Mar 18, 2026
28 checks passed
@troyciesco troyciesco deleted the cmraible/welcome-email-foundation branch March 18, 2026 13:47
troyciesco pushed a commit that referenced this pull request Mar 18, 2026
## Summary

Visual components for the welcome email design customization:

- **`email-preview.tsx`** — Composable email preview with
sub-components: `EnvelopeHeader`, `PublicationHeader`, `Divider`,
`BodyContent`, `ActionButton`, `Footer`
- **`design-fields/`** — 13 individual design field components (colors,
fonts, button style, corners, etc.) that self-wire via
`EmailDesignProvider` context
- **`design-fields/font-constants.ts`** — Font family definitions shared
across fields

> **Stack:** PR 2 of 3 — targets #26841 (foundation). Next: #26843
(modal wiring)
troyciesco pushed a commit that referenced this pull request Mar 18, 2026
## Summary

Integrates all foundation and visual components into the welcome email
customization modal:

- **`welcome-email-customize-modal.tsx`** — Full modal with `GeneralTab`
(sender name, reply-to, header image, footer), `DesignTab` (all design
fields via context), `Sidebar` (tab navigation), and live `EmailPreview`
- Reads current design settings from Ghost settings API on mount
- Hydrates general settings when global data loads asynchronously

> **Stack:** PR 3 of 3 — targets
`cmraible/welcome-email-visual-components` (#26842)
> 
> **Full stack:** #26841 (foundation) → #26842 (visual components) →
this PR (wiring)

## Known TODOs

- **Header image upload** — The UI shows a preview/remove control when a
header image is set, but there is no upload control to add one yet. This
will be wired up in a follow-up.
- **Save handler** — The Save button currently closes the modal without
persisting changes. Backend persistence will be added once the design
columns exist in the database.
troyciesco added a commit that referenced this pull request Mar 19, 2026
no ref

Iterative cleanup and improvements to the welcome email design
customization modal (PRs #26841, #26842, #26843). Fixes bugs found
during review, removes dead code, adds missing functionality (image
upload, Ghost badge toggle), and refactors the preview to be composable
and visually consistent with the existing newsletter preview.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants