From af43af7dc12a675a033d07a5244e8f2d15e0254d Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Wed, 7 Oct 2026 11:24:52 -0700 Subject: [PATCH] feat(site-builder): form blocks default to palette tokens Refs #44 Co-Authored-By: Claude Sonnet 5.5 --- .../components/forms/ContactForm.toHtml.test.ts | 14 ++++++++++++++ craft/src/components/forms/ContactForm.tsx | 11 ++++++----- .../components/forms/FormButton.toHtml.test.ts | 8 ++++++++ craft/src/components/forms/FormButton.tsx | 5 +++-- .../components/forms/SubscribeForm.toHtml.test.ts | 14 ++++++++++++++ craft/src/components/forms/SubscribeForm.tsx | 15 ++++++++------- 6 files changed, 53 insertions(+), 14 deletions(-) diff --git a/craft/src/components/forms/ContactForm.toHtml.test.ts b/craft/src/components/forms/ContactForm.toHtml.test.ts index d32201b..2dd75ad 100644 --- a/craft/src/components/forms/ContactForm.toHtml.test.ts +++ b/craft/src/components/forms/ContactForm.toHtml.test.ts @@ -286,3 +286,17 @@ describe('ContactForm.toHtml destination marker', () => { expect(out.html).toContain('name="_gotcha"'); }); }); + +describe('ContactForm.toHtml palette token fallbacks (Refs #44)', () => { + test('submitColor empty -> primary token; input text -> text token', () => { + const { html } = toHtml({ recipientEmail: 'a@b.com', fields: [{ type: 'text', label: 'Name', name: 'name' }], submitColor: '' }, ''); + expect(html).toContain('background-color:var(--wsb-primary)'); + expect(html).toContain('color:var(--wsb-text)'); + expect(html).not.toContain('#3b82f6'); + expect(html).not.toContain('#1f2937'); + }); + test('explicit submitColor wins', () => { + const { html } = toHtml({ recipientEmail: 'a@b.com', fields: [], submitColor: '#ff0000' }, ''); + expect(html).toContain('background-color:#ff0000'); + }); +}); diff --git a/craft/src/components/forms/ContactForm.tsx b/craft/src/components/forms/ContactForm.tsx index f525efa..a19acb4 100644 --- a/craft/src/components/forms/ContactForm.tsx +++ b/craft/src/components/forms/ContactForm.tsx @@ -3,6 +3,7 @@ import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { relayFormWiring } from '../../utils/form-relay-wiring'; import { escapeHtml, escapeAttr, slugId, cssValue, sanitizeInputType } from '../../utils/escape'; +import { tokenRef } from '../../utils/tokens'; // The allowlist enforced at export time lives in `sanitizeInputType` // (utils/escape.ts) -- this union is a superset (it also covers 'textarea' @@ -61,7 +62,7 @@ const defaultFields: ContactFormField[] = [ export const ContactForm: UserComponent = ({ fields = defaultFields, submitText = 'Send Message', - submitColor = '#3b82f6', + submitColor = tokenRef('primary'), formAction = '#', successMessage = 'Thank you! We\'ll get back to you soon.', style = {}, @@ -84,7 +85,7 @@ export const ContactForm: UserComponent = ({ border: `1px solid ${inputBorder}`, borderRadius: '6px', backgroundColor: inputBg, - color: '#1f2937', + color: tokenRef('text'), boxSizing: 'border-box', outline: 'none', }; @@ -168,7 +169,7 @@ ContactForm.craft = { props: { fields: defaultFields, submitText: 'Send Message', - submitColor: '#3b82f6', + submitColor: tokenRef('primary'), formAction: '#', successMessage: 'Thank you! We\'ll get back to you soon.', style: { @@ -215,7 +216,7 @@ ContactForm.craft = { const labelColor = cssValue(props.labelColor) || '#374151'; const inputBg = cssValue(props.inputBg) || '#ffffff'; const inputBorder = cssValue(props.inputBorder) || '#d1d5db'; - const inputStyleStr = `width:100%;padding:10px 14px;font-size:14px;font-family:Inter,sans-serif;border:1px solid ${inputBorder};border-radius:6px;background-color:${inputBg};color:#1f2937;box-sizing:border-box;outline:none`; + const inputStyleStr = `width:100%;padding:10px 14px;font-size:14px;font-family:Inter,sans-serif;border:1px solid ${inputBorder};border-radius:6px;background-color:${inputBg};color:${tokenRef('text')};box-sizing:border-box;outline:none`; const fieldsHtml = (props.fields || defaultFields).map((field, i) => { const reqStar = field.required ? '*' : ''; @@ -242,7 +243,7 @@ ContactForm.craft = { fontWeight: '600', fontFamily: 'Inter, sans-serif', color: '#ffffff', - backgroundColor: props.submitColor || '#3b82f6', + backgroundColor: cssValue(props.submitColor) || tokenRef('primary'), border: 'none', borderRadius: '8px', cursor: 'pointer', diff --git a/craft/src/components/forms/FormButton.toHtml.test.ts b/craft/src/components/forms/FormButton.toHtml.test.ts index 68deb13..421b80e 100644 --- a/craft/src/components/forms/FormButton.toHtml.test.ts +++ b/craft/src/components/forms/FormButton.toHtml.test.ts @@ -38,3 +38,11 @@ describe('FormButton.craft.props includes animation/visibility defaults', () => }); }); }); + +describe('FormButton.toHtml palette token default (Refs #44)', () => { + test('a new form button (craft default props) paints the primary token', () => { + const { html } = toHtml({ ...(FormButton as any).craft.props }, ''); + expect(html).toContain('background-color:var(--wsb-primary)'); + expect(html).not.toContain('#3b82f6'); + }); +}); diff --git a/craft/src/components/forms/FormButton.tsx b/craft/src/components/forms/FormButton.tsx index d76491b..41444fd 100644 --- a/craft/src/components/forms/FormButton.tsx +++ b/craft/src/components/forms/FormButton.tsx @@ -2,6 +2,7 @@ import React, { CSSProperties } from 'react'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { escapeHtml } from '../../utils/escape'; +import { tokenRef } from '../../utils/tokens'; interface FormButtonProps { text?: string; @@ -31,7 +32,7 @@ export const FormButton: UserComponent = ({ onClick={(e) => e.preventDefault()} style={{ padding: '12px 32px', - backgroundColor: '#3b82f6', + backgroundColor: tokenRef('primary'), color: '#ffffff', border: 'none', borderRadius: '6px', @@ -55,7 +56,7 @@ FormButton.craft = { props: { text: 'Submit', style: { - backgroundColor: '#3b82f6', + backgroundColor: tokenRef('primary'), color: '#ffffff', padding: '12px 32px', borderRadius: '6px', diff --git a/craft/src/components/forms/SubscribeForm.toHtml.test.ts b/craft/src/components/forms/SubscribeForm.toHtml.test.ts index 791f968..0c9e73e 100644 --- a/craft/src/components/forms/SubscribeForm.toHtml.test.ts +++ b/craft/src/components/forms/SubscribeForm.toHtml.test.ts @@ -79,3 +79,17 @@ describe('SubscribeForm.craft.props includes animation/visibility defaults', () }); }); }); + +describe('SubscribeForm.toHtml palette token fallbacks (Refs #44)', () => { + test('buttonColor absent -> primary token; heading/input text -> text token', () => { + const { html } = toHtml({ heading: 'Join us' }, ''); + expect(html).toContain('background-color:var(--wsb-primary)'); + expect(html).toContain('color:var(--wsb-text)'); + expect(html).not.toContain('#3b82f6'); + expect(html).not.toContain('#1f2937'); + }); + test('explicit buttonColor wins', () => { + const { html } = toHtml({ buttonColor: '#ff0000' }, ''); + expect(html).toContain('background-color:#ff0000'); + }); +}); diff --git a/craft/src/components/forms/SubscribeForm.tsx b/craft/src/components/forms/SubscribeForm.tsx index d8019fb..b2d730e 100644 --- a/craft/src/components/forms/SubscribeForm.tsx +++ b/craft/src/components/forms/SubscribeForm.tsx @@ -3,6 +3,7 @@ import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { relayFormWiring } from '../../utils/form-relay-wiring'; import { escapeHtml, escapeAttr } from '../../utils/escape'; +import { tokenRef } from '../../utils/tokens'; interface SubscribeFormProps { heading?: string; @@ -28,7 +29,7 @@ export const SubscribeForm: UserComponent = ({ heading = 'Subscribe to our newsletter', placeholder = 'Enter your email', buttonText = 'Subscribe', - buttonColor = '#3b82f6', + buttonColor = tokenRef('primary'), layout = 'inline', style = {}, }) => { @@ -55,7 +56,7 @@ export const SubscribeForm: UserComponent = ({

@@ -84,7 +85,7 @@ export const SubscribeForm: UserComponent = ({ border: '1px solid #d1d5db', borderRadius: isInline ? '8px 0 0 8px' : '8px', backgroundColor: '#ffffff', - color: '#1f2937', + color: tokenRef('text'), outline: 'none', boxSizing: 'border-box', }} @@ -119,7 +120,7 @@ SubscribeForm.craft = { heading: 'Subscribe to our newsletter', placeholder: 'Enter your email', buttonText: 'Subscribe', - buttonColor: '#3b82f6', + buttonColor: tokenRef('primary'), layout: 'inline', style: { backgroundColor: '#f8fafc' }, recipientEmail: '', @@ -144,7 +145,7 @@ SubscribeForm.craft = { heading = 'Subscribe to our newsletter', placeholder = 'Enter your email', buttonText = 'Subscribe', - buttonColor = '#3b82f6', + buttonColor = tokenRef('primary'), layout = 'inline', style = {}, } = props; @@ -158,7 +159,7 @@ SubscribeForm.craft = { }); const headingHtml = heading - ? `

${escapeHtml(heading)}

` + ? `

${escapeHtml(heading)}

` : ''; const formStyle = cssPropsToString({ @@ -170,7 +171,7 @@ SubscribeForm.craft = { alignItems: 'stretch', }); - const inputStyleStr = `flex:1;padding:12px 16px;font-size:15px;font-family:Inter,sans-serif;border:1px solid #d1d5db;border-radius:${isInline ? '8px 0 0 8px' : '8px'};background-color:#ffffff;color:#1f2937;outline:none;box-sizing:border-box`; + const inputStyleStr = `flex:1;padding:12px 16px;font-size:15px;font-family:Inter,sans-serif;border:1px solid #d1d5db;border-radius:${isInline ? '8px 0 0 8px' : '8px'};background-color:#ffffff;color:${tokenRef('text')};outline:none;box-sizing:border-box`; const btnStyle = cssPropsToString({ padding: '12px 24px',