Added email design foundation components#26841
Conversation
WalkthroughThis 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)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
📝 Coding Plan
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. Comment |
| /* eslint-env node */ | ||
| const tailwindConfig = `${__dirname}/tailwind.config.cjs`; | ||
|
|
||
| module.exports = { |
There was a problem hiding this comment.
This whole file diff will go away once this PR is merged.
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
apps/admin-x-settings/.eslintrc.cjsapps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsxapps/admin-x-settings/src/components/settings/email-design/design-utils.tsapps/admin-x-settings/src/components/settings/email-design/email-design-context.tsxapps/admin-x-settings/src/components/settings/email-design/email-design-modal.tsxapps/admin-x-settings/src/components/settings/email-design/types.tsapps/shade/src/index.ts
apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsx
Show resolved
Hide resolved
apps/admin-x-settings/src/components/settings/email-design/design-utils.ts
Outdated
Show resolved
Hide resolved
apps/admin-x-settings/src/components/settings/email-design/design-utils.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/admin-x-settings/src/components/settings/email-design/design-utils.ts (1)
105-123: Note:resolveAllColorsis exported but not currently used in the codebase.The technical concern about
accentColorvalidation is sound—if this function is integrated for use,accentColorshould be validated once at entry before being passed to downstream resolver functions. The existingVALID_HEXconstant (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
📒 Files selected for processing (3)
apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsxapps/admin-x-settings/src/components/settings/email-design/design-utils.tsapps/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
apps/admin-x-settings/src/components/settings/email-design/design-utils.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
apps/admin-x-settings/src/components/settings/email-design/design-utils.ts
| 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 | ||
| }; |
There was a problem hiding this comment.
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.
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
1b20510 to
18b07f5
Compare
There was a problem hiding this comment.
🧹 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, anddivider_stylehave 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
📒 Files selected for processing (6)
apps/admin-x-settings/src/components/settings/email-design/color-picker-field.tsxapps/admin-x-settings/src/components/settings/email-design/design-utils.tsapps/admin-x-settings/src/components/settings/email-design/email-design-context.tsxapps/admin-x-settings/src/components/settings/email-design/email-design-modal.tsxapps/admin-x-settings/src/components/settings/email-design/types.tsapps/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
## 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)
## 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.
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.
Summary
Foundation layer for the welcome email design customization modal:
types.ts—EmailDesignSettingsinterface and default valuesdesign-utils.ts— Color manipulation (contrast, luminance) and font resolution utilitiesemail-design-context.tsx— React context provider for design settings state managementcolor-picker-field.tsx— Reusable color picker form field wrapperemail-design-modal.tsx— Shared modal layout with sidebar + preview panesshade/index.ts— ExportedColorPickercomponent from shade