From 2877bc06fa50802400773d053b2c93987ade7da4 Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Wed, 7 Oct 2026 12:44:39 -0700 Subject: [PATCH] feat(site-builder): tokenise colours hard-coded inside components Component render/toHtml now use var(--wsb-*) instead of hex so Site Design palette changes reach FeaturesGrid, Testimonials, Accordion, PricingTable, CTA/CallToAction, forms, Gallery, Tabs and others. Exemption lists removed. Refs #44 Co-Authored-By: Claude Sonnet 5.5 --- .../basic/ButtonLink.toHtml.test.ts | 9 +++ craft/src/components/basic/ButtonLink.tsx | 2 +- .../components/basic/Divider.toHtml.test.ts | 9 +++ craft/src/components/basic/Divider.tsx | 7 +- .../src/components/basic/Logo.toHtml.test.ts | 9 +++ craft/src/components/basic/Logo.tsx | 3 +- .../components/basic/SearchBar.toHtml.test.ts | 9 +++ craft/src/components/basic/SearchBar.tsx | 14 ++-- .../basic/StarRating.toHtml.test.ts | 9 +++ craft/src/components/basic/StarRating.tsx | 13 ++-- .../components/basic/TextBlock.toHtml.test.ts | 9 +++ craft/src/components/basic/TextBlock.tsx | 3 +- .../forms/ContactForm.toHtml.test.ts | 9 +++ craft/src/components/forms/ContactForm.tsx | 30 ++++---- .../forms/FormButton.toHtml.test.ts | 9 +++ craft/src/components/forms/FormButton.tsx | 4 +- .../forms/FormContainer.toHtml.test.ts | 9 +++ craft/src/components/forms/FormContainer.tsx | 5 +- .../forms/InputField.toHtml.test.ts | 9 +++ craft/src/components/forms/InputField.tsx | 15 ++-- .../forms/SubscribeForm.toHtml.test.ts | 9 +++ craft/src/components/forms/SubscribeForm.tsx | 12 +-- .../forms/TextareaField.toHtml.test.ts | 9 +++ craft/src/components/forms/TextareaField.tsx | 15 ++-- .../sections/Accordion.toHtml.test.ts | 9 +++ craft/src/components/sections/Accordion.tsx | 33 ++++---- .../sections/CTASection.toHtml.test.ts | 9 +++ craft/src/components/sections/CTASection.tsx | 14 ++-- .../sections/CallToAction.toHtml.test.ts | 9 +++ .../src/components/sections/CallToAction.tsx | 30 +++++--- .../sections/FeaturesGrid.toHtml.test.ts | 9 +++ .../src/components/sections/FeaturesGrid.tsx | 22 +++--- .../sections/Gallery.toHtml.test.ts | 9 +++ craft/src/components/sections/Gallery.tsx | 9 ++- .../sections/NumberCounter.toHtml.test.ts | 9 +++ .../src/components/sections/NumberCounter.tsx | 6 +- .../sections/PricingTable.toHtml.test.ts | 20 +++-- .../src/components/sections/PricingTable.tsx | 48 ++++++------ .../components/sections/Tabs.toHtml.test.ts | 9 +++ craft/src/components/sections/Tabs.tsx | 38 ++++----- .../sections/Testimonials.toHtml.test.ts | 9 +++ .../src/components/sections/Testimonials.tsx | 35 +++++---- .../src/components/sections/_cta-helpers.tsx | 7 +- .../component-tokens.harness.test.tsx | 77 +++++++++++++++++++ .../src/sections/families/a-content.test.tsx | 6 +- .../src/sections/families/c-content.test.tsx | 7 +- .../templates/definitions.tokens-B.test.ts | 13 ++-- 47 files changed, 473 insertions(+), 195 deletions(-) create mode 100644 craft/src/components/sections/component-tokens.harness.test.tsx diff --git a/craft/src/components/basic/ButtonLink.toHtml.test.ts b/craft/src/components/basic/ButtonLink.toHtml.test.ts index 2b40c18..bb86953 100644 --- a/craft/src/components/basic/ButtonLink.toHtml.test.ts +++ b/craft/src/components/basic/ButtonLink.toHtml.test.ts @@ -126,3 +126,12 @@ describe('ButtonLink.toHtml palette token default (Refs #44)', () => { expect(html).not.toContain('#3b82f6'); }); }); + +describe('ButtonLink.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ ...(ButtonLink as any).craft.props }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "primary"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#ffffff"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/basic/ButtonLink.tsx b/craft/src/components/basic/ButtonLink.tsx index 53036d7..5d450a8 100644 --- a/craft/src/components/basic/ButtonLink.tsx +++ b/craft/src/components/basic/ButtonLink.tsx @@ -77,7 +77,7 @@ ButtonLink.craft = { hoverColor: '', style: { backgroundColor: tokenRef('primary'), - color: '#ffffff', + color: tokenRef('bg'), padding: '12px 24px', borderRadius: '8px', fontWeight: '600', diff --git a/craft/src/components/basic/Divider.toHtml.test.ts b/craft/src/components/basic/Divider.toHtml.test.ts index db186b5..53937b1 100644 --- a/craft/src/components/basic/Divider.toHtml.test.ts +++ b/craft/src/components/basic/Divider.toHtml.test.ts @@ -32,3 +32,12 @@ describe('Divider.toHtml XSS hardening (thickness/color into style=)', () => { expect(html).not.toMatch(/"\s+onmouseover="/); }); }); + +describe('Divider.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({}, ''); + const lower = html.toLowerCase(); + for (const t of ["border"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#e4e4e7"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/basic/Divider.tsx b/craft/src/components/basic/Divider.tsx index 707c07d..b746f84 100644 --- a/craft/src/components/basic/Divider.tsx +++ b/craft/src/components/basic/Divider.tsx @@ -1,4 +1,5 @@ import React, { CSSProperties } from 'react'; +import { tokenRef } from '../../utils/tokens'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; @@ -9,7 +10,7 @@ interface DividerProps { } export const Divider: UserComponent = ({ - color = '#e4e4e7', + color = tokenRef('border'), thickness = '1px', style = {}, }) => { @@ -39,7 +40,7 @@ export const Divider: UserComponent = ({ Divider.craft = { displayName: 'Divider', props: { - color: '#e4e4e7', + color: tokenRef('border'), thickness: '1px', style: {}, }, @@ -55,7 +56,7 @@ Divider.craft = { (Divider as any).toHtml = (props: DividerProps, _childrenHtml: string) => { const styleStr = cssPropsToString({ border: 'none', - borderTop: `${props.thickness || '1px'} solid ${props.color || '#e4e4e7'}`, + borderTop: `${props.thickness || '1px'} solid ${props.color || tokenRef('border')}`, margin: '16px 0', ...props.style, }); diff --git a/craft/src/components/basic/Logo.toHtml.test.ts b/craft/src/components/basic/Logo.toHtml.test.ts index e6429f4..a9ae530 100644 --- a/craft/src/components/basic/Logo.toHtml.test.ts +++ b/craft/src/components/basic/Logo.toHtml.test.ts @@ -83,3 +83,12 @@ describe('Logo.toHtml text-logo styling sanitization', () => { expect(html).toContain('MySite'); }); }); + +describe('Logo.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ text: "Acme" }, ''); + const lower = html.toLowerCase(); + for (const t of ["text"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#1f2937"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/basic/Logo.tsx b/craft/src/components/basic/Logo.tsx index ed39efa..87439a9 100644 --- a/craft/src/components/basic/Logo.tsx +++ b/craft/src/components/basic/Logo.tsx @@ -1,4 +1,5 @@ import React, { CSSProperties } from 'react'; +import { tokenRef } from '../../utils/tokens'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { useSiteDesign } from '../../state/SiteDesignContext'; @@ -125,7 +126,7 @@ Logo.craft = { fontWeight: props.fontWeight || '700', fontSize: props.fontSize || '20px', fontFamily: props.fontFamily || 'Inter, sans-serif', - color: props.color || '#1f2937', + color: props.color || tokenRef('text'), }); innerHtml = `${escapeHtml(props.text || 'MySite')}`; } diff --git a/craft/src/components/basic/SearchBar.toHtml.test.ts b/craft/src/components/basic/SearchBar.toHtml.test.ts index 52fa9fe..9632081 100644 --- a/craft/src/components/basic/SearchBar.toHtml.test.ts +++ b/craft/src/components/basic/SearchBar.toHtml.test.ts @@ -83,3 +83,12 @@ describe('SearchBar.toHtml palette token fallbacks (Refs #44)', () => { expect(html).not.toContain('#1f2937'); }); }); + +describe('SearchBar.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({}, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "border", "muted", "primary"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#d1d5db", "#9ca3af", "#ffffff"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/basic/SearchBar.tsx b/craft/src/components/basic/SearchBar.tsx index 16db6ff..4e2135d 100644 --- a/craft/src/components/basic/SearchBar.tsx +++ b/craft/src/components/basic/SearchBar.tsx @@ -57,7 +57,7 @@ export const SearchBar: UserComponent = ({ left: '14px', top: '50%', transform: 'translateY(-50%)', - color: '#9ca3af', + color: tokenRef('muted'), fontSize: '14px', pointerEvents: 'none', }} @@ -71,9 +71,9 @@ export const SearchBar: UserComponent = ({ padding: '12px 16px 12px 40px', fontSize: '15px', fontFamily: 'Inter, sans-serif', - border: '1px solid #d1d5db', + border: `1px solid ${tokenRef('border')}`, borderRadius: showButton ? '8px 0 0 8px' : '8px', - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), color: tokenRef('text'), outline: 'none', boxSizing: 'border-box', @@ -88,7 +88,7 @@ export const SearchBar: UserComponent = ({ fontSize: '15px', fontWeight: '600', fontFamily: 'Inter, sans-serif', - color: '#ffffff', + color: tokenRef('bg'), backgroundColor: tokenRef('primary'), border: 'none', borderRadius: '0 8px 8px 0', @@ -148,10 +148,10 @@ SearchBar.craft = { ...style, }); - const inputStyleStr = `width:100%;padding:12px 16px 12px 40px;font-size:15px;font-family:Inter,sans-serif;border:1px solid #d1d5db;border-radius:${showButton ? '8px 0 0 8px' : '8px'};background-color:#ffffff;color:${tokenRef('text')};outline:none;box-sizing:border-box`; + const inputStyleStr = `width:100%;padding:12px 16px 12px 40px;font-size:15px;font-family:Inter,sans-serif;border:1px solid ${tokenRef('border')};border-radius:${showButton ? '8px 0 0 8px' : '8px'};background-color:${tokenRef('bg')};color:${tokenRef('text')};outline:none;box-sizing:border-box`; const btnHtml = showButton - ? `` + ? `` : ''; // F2: previously a purely decorative
-- no action/method/input @@ -165,7 +165,7 @@ SearchBar.craft = { return { html: `
- +
${btnHtml} diff --git a/craft/src/components/basic/StarRating.toHtml.test.ts b/craft/src/components/basic/StarRating.toHtml.test.ts index 680c256..d335b69 100644 --- a/craft/src/components/basic/StarRating.toHtml.test.ts +++ b/craft/src/components/basic/StarRating.toHtml.test.ts @@ -86,3 +86,12 @@ describe('StarRating.craft.props includes the box-model/animation/visibility rol expect(props).toHaveProperty('hideOnMobile', false); }); }); + +describe('StarRating.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({}, ''); + const lower = html.toLowerCase(); + for (const t of ["warning", "border"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#f59e0b", "#d1d5db"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/basic/StarRating.tsx b/craft/src/components/basic/StarRating.tsx index f678ff3..3660d0c 100644 --- a/craft/src/components/basic/StarRating.tsx +++ b/craft/src/components/basic/StarRating.tsx @@ -1,4 +1,5 @@ import React, { CSSProperties } from 'react'; +import { tokenRef } from '../../utils/tokens'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { cssValue, escapeAttr } from '../../utils/escape'; @@ -21,8 +22,8 @@ export const StarRating: UserComponent = ({ rating = 4.5, maxStars = 5, size = '24px', - filledColor = '#f59e0b', - emptyColor = '#d1d5db', + filledColor = tokenRef('warning'), + emptyColor = tokenRef('border'), style = {}, }) => { const { @@ -89,8 +90,8 @@ StarRating.craft = { rating: 4.5, maxStars: 5, size: '24px', - filledColor: '#f59e0b', - emptyColor: '#d1d5db', + filledColor: tokenRef('warning'), + emptyColor: tokenRef('border'), style: {}, animation: '', animationDelay: '', @@ -123,8 +124,8 @@ StarRating.craft = { : 5; // Sanitized -- raw string-interpolation sinks in the star glyphs below. const size = cssValue(props.size) || '24px'; - const filledColor = cssValue(props.filledColor) || '#f59e0b'; - const emptyColor = cssValue(props.emptyColor) || '#d1d5db'; + const filledColor = cssValue(props.filledColor) || tokenRef('warning'); + const emptyColor = cssValue(props.emptyColor) || tokenRef('border'); const wrapperStyle = cssPropsToString({ display: 'inline-flex', alignItems: 'center', diff --git a/craft/src/components/basic/TextBlock.toHtml.test.ts b/craft/src/components/basic/TextBlock.toHtml.test.ts index 43ad5f2..564d1e5 100644 --- a/craft/src/components/basic/TextBlock.toHtml.test.ts +++ b/craft/src/components/basic/TextBlock.toHtml.test.ts @@ -108,3 +108,12 @@ describe('TextBlock.toHtml inline rich text (#46)', () => { expect((TextBlock as any).craft.props.html).toBe(''); }); }); + +describe('TextBlock.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ ...(TextBlock as any).craft.props }, ''); + const lower = html.toLowerCase(); + for (const t of ["text"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#3f3f46"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/basic/TextBlock.tsx b/craft/src/components/basic/TextBlock.tsx index 5bb21b2..94ec5fb 100644 --- a/craft/src/components/basic/TextBlock.tsx +++ b/craft/src/components/basic/TextBlock.tsx @@ -1,4 +1,5 @@ import React, { CSSProperties } from 'react'; +import { tokenRef } from '../../utils/tokens'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { escapeHtml } from '../../utils/escape'; @@ -65,7 +66,7 @@ TextBlock.craft = { style: { fontSize: '16px', lineHeight: '1.6', - color: '#3f3f46', + color: tokenRef('text'), letterSpacing: '', textTransform: '' as CSSProperties['textTransform'], fontStyle: '' as CSSProperties['fontStyle'], diff --git a/craft/src/components/forms/ContactForm.toHtml.test.ts b/craft/src/components/forms/ContactForm.toHtml.test.ts index 2dd75ad..bdb091c 100644 --- a/craft/src/components/forms/ContactForm.toHtml.test.ts +++ b/craft/src/components/forms/ContactForm.toHtml.test.ts @@ -300,3 +300,12 @@ describe('ContactForm.toHtml palette token fallbacks (Refs #44)', () => { expect(html).toContain('background-color:#ff0000'); }); }); + +describe('ContactForm.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ fields: [{ label: 'N', name: 'n', type: 'text', required: true }] }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "border", "text", "error"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#ef4444", "#374151", "#d1d5db", "#ffffff"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/forms/ContactForm.tsx b/craft/src/components/forms/ContactForm.tsx index a19acb4..c08eade 100644 --- a/craft/src/components/forms/ContactForm.tsx +++ b/craft/src/components/forms/ContactForm.tsx @@ -66,9 +66,9 @@ export const ContactForm: UserComponent = ({ formAction = '#', successMessage = 'Thank you! We\'ll get back to you soon.', style = {}, - labelColor = '#374151', - inputBg = '#ffffff', - inputBorder = '#d1d5db', + labelColor = tokenRef('text'), + inputBg = tokenRef('bg'), + inputBorder = tokenRef('border'), }) => { const { connectors: { connect, drag }, @@ -109,7 +109,7 @@ export const ContactForm: UserComponent = ({
{field.type === 'textarea' ? ( +
`, }; }; diff --git a/craft/src/components/sections/Accordion.toHtml.test.ts b/craft/src/components/sections/Accordion.toHtml.test.ts index 6d31080..66be0e4 100644 --- a/craft/src/components/sections/Accordion.toHtml.test.ts +++ b/craft/src/components/sections/Accordion.toHtml.test.ts @@ -36,3 +36,12 @@ describe('Accordion.craft.props includes the box-model/animation/visibility roll expect(props).toHaveProperty('hideOnMobile', false); }); }); + +describe('Accordion.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ items: [{ title: 'A', content: 'B' }] }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "border", "text", "muted"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#f8fafc", "#e2e8f0", "#18181b", "#4b5563"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/sections/Accordion.tsx b/craft/src/components/sections/Accordion.tsx index d21112a..182001a 100644 --- a/craft/src/components/sections/Accordion.tsx +++ b/craft/src/components/sections/Accordion.tsx @@ -1,4 +1,5 @@ import React, { CSSProperties, useState } from 'react'; +import { tokenRef } from '../../utils/tokens'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { escapeHtml, escapeAttr, cssValue } from '../../utils/escape'; @@ -33,10 +34,10 @@ const defaultItems: AccordionItem[] = [ export const Accordion: UserComponent = ({ items = defaultItems, style = {}, - headerBg = '#f8fafc', - headerColor = '#18181b', - contentBg = '#ffffff', - borderColor = '#e2e8f0', + headerBg = tokenRef('bg'), + headerColor = tokenRef('text'), + contentBg = tokenRef('bg'), + borderColor = tokenRef('border'), anchorId, }) => { const { @@ -67,7 +68,7 @@ export const Accordion: UserComponent = ({ id={anchorId || undefined} style={{ padding: '60px 20px', - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} @@ -110,7 +111,7 @@ export const Accordion: UserComponent = ({ style={{ padding: '16px 20px', backgroundColor: contentBg, - color: '#4b5563', + color: tokenRef('muted'), fontSize: '14px', lineHeight: '1.6', borderTop: `1px solid ${borderColor}`, @@ -134,11 +135,11 @@ Accordion.craft = { displayName: 'Accordion', props: { items: defaultItems, - style: { backgroundColor: '#ffffff' }, - headerBg: '#f8fafc', - headerColor: '#18181b', - contentBg: '#ffffff', - borderColor: '#e2e8f0', + style: { backgroundColor: tokenRef('bg') }, + headerBg: tokenRef('bg'), + headerColor: tokenRef('text'), + contentBg: tokenRef('bg'), + borderColor: tokenRef('border'), anchorId: '', animation: '', animationDelay: '', @@ -163,10 +164,10 @@ Accordion.craft = { const idAttr = props.anchorId ? ` id="${escapeAttr(props.anchorId)}"` : ''; // Sanitized -- raw string-interpolation sinks in the
/ // style attributes below. - const headerBg = cssValue(props.headerBg) || '#f8fafc'; - const headerColor = cssValue(props.headerColor) || '#18181b'; - const contentBg = cssValue(props.contentBg) || '#ffffff'; - const borderColor = cssValue(props.borderColor) || '#e2e8f0'; + const headerBg = cssValue(props.headerBg) || tokenRef('bg'); + const headerColor = cssValue(props.headerColor) || tokenRef('text'); + const contentBg = cssValue(props.contentBg) || tokenRef('bg'); + const borderColor = cssValue(props.borderColor) || tokenRef('border'); const items = props.items || defaultItems; const panels = items.map((item, i) => { @@ -178,7 +179,7 @@ Accordion.craft = { ${escapeHtml(item.title)} -
+
${escapeHtml(item.content)}
`; diff --git a/craft/src/components/sections/CTASection.toHtml.test.ts b/craft/src/components/sections/CTASection.toHtml.test.ts index 8c6f2bc..9d305ef 100644 --- a/craft/src/components/sections/CTASection.toHtml.test.ts +++ b/craft/src/components/sections/CTASection.toHtml.test.ts @@ -35,3 +35,12 @@ describe('CTASection.craft.props includes the box-model/animation/visibility rol expect(props).toHaveProperty('hideOnMobile', false); }); }); + +describe('CTASection.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ ...(CTASection as any).craft.props }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "text", "primary", "secondary"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#18181b", "#2563eb", "#7c3aed"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/sections/CTASection.tsx b/craft/src/components/sections/CTASection.tsx index 258a7d7..b5e2bae 100644 --- a/craft/src/components/sections/CTASection.tsx +++ b/craft/src/components/sections/CTASection.tsx @@ -3,6 +3,7 @@ import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { CtaButton, normalizeCtas, ctaInlineStyle, ctasToHtml } from './_cta-helpers'; import { escapeHtml, escapeAttr } from '../../utils/escape'; +import { tokenRef } from '../../utils/tokens'; interface CTASectionProps { heading?: string; @@ -21,7 +22,10 @@ interface CTASectionProps { hideOnMobile?: boolean; } -const defaultGradient = 'linear-gradient(135deg, #2563eb 0%, #7c3aed 100%)'; +const defaultGradient = `linear-gradient(135deg, ${tokenRef('primary')} 0%, ${tokenRef('secondary')} 100%)`; +// Fixed white: the default background is a primary->secondary gradient / image, so heading +// and outline-button text stay light regardless of the palette (no token means "on gradient"). +const WHITE_ON_GRADIENT = '#ffffff'; export const CTASection: UserComponent = ({ heading = 'Ready to Get Started?', @@ -41,7 +45,7 @@ export const CTASection: UserComponent = ({ })); const effectiveCtas = normalizeCtas({ ctas, buttonText, buttonHref }); - const ctaDefaults = { primaryBg: '#ffffff', primaryText: '#18181b', outlineText: '#ffffff' }; + const ctaDefaults = { primaryBg: tokenRef('bg'), primaryText: tokenRef('text'), outlineText: WHITE_ON_GRADIENT }; return (
= ({ }} >
-

+

{heading}

@@ -111,12 +115,12 @@ CTASection.craft = { ...props.style, }); const ctas = normalizeCtas(props); - const buttonsHtml = ctasToHtml(ctas, { primaryBg: '#ffffff', primaryText: '#18181b', outlineText: '#ffffff' }); + const buttonsHtml = ctasToHtml(ctas, { primaryBg: tokenRef('bg'), primaryText: tokenRef('text'), outlineText: WHITE_ON_GRADIENT }); const idAttr = props.anchorId ? ` id="${escapeAttr(props.anchorId)}"` : ''; return { html: `

-

${escapeHtml(props.heading || '')}

+

${escapeHtml(props.heading || '')}

${escapeHtml(props.description || '')}

${buttonsHtml}
diff --git a/craft/src/components/sections/CallToAction.toHtml.test.ts b/craft/src/components/sections/CallToAction.toHtml.test.ts index 9558445..7cded89 100644 --- a/craft/src/components/sections/CallToAction.toHtml.test.ts +++ b/craft/src/components/sections/CallToAction.toHtml.test.ts @@ -35,3 +35,12 @@ describe('CallToAction.craft.props includes the box-model/animation/visibility r expect(props).toHaveProperty('hideOnMobile', false); }); }); + +describe('CallToAction.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ ...(CallToAction as any).craft.props }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "text", "primary", "secondary"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#18181b", "#2563eb", "#7c3aed"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/sections/CallToAction.tsx b/craft/src/components/sections/CallToAction.tsx index d2b103e..0e46bf8 100644 --- a/craft/src/components/sections/CallToAction.tsx +++ b/craft/src/components/sections/CallToAction.tsx @@ -3,6 +3,7 @@ import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { CtaButton, normalizeCtas, ctaInlineStyle, ctasToHtml } from './_cta-helpers'; import { escapeHtml, escapeAttr, cssValue } from '../../utils/escape'; +import { tokenRef } from '../../utils/tokens'; interface CallToActionProps { heading?: string; @@ -28,7 +29,16 @@ interface CallToActionProps { hideOnMobile?: boolean; } -const defaultGradient = 'linear-gradient(135deg, #2563eb 0%, #7c3aed 100%)'; +const defaultGradient = `linear-gradient(135deg, ${tokenRef('primary')} 0%, ${tokenRef('secondary')} 100%)`; +// Fixed white: default background is a gradient / image + dark overlay, so text stays light +// whatever the palette. Everything else (button, gradient) follows the design tokens. +const WHITE_ON_GRADIENT = '#ffffff'; + +/** A light button (token bg, or legacy white / slate-50) takes the text token; any other fill takes the bg token. */ +function isLightButton(c: string): boolean { + const v = c.trim().toLowerCase(); + return v === tokenRef('bg') || v === '#ffffff' || v === '#f8fafc'; +} export const CallToAction: UserComponent = ({ heading = 'Ready to Get Started?', @@ -42,8 +52,8 @@ export const CallToAction: UserComponent = ({ bgValue = defaultGradient, overlayColor = '#000000', overlayOpacity = 0, - textColor = '#ffffff', - buttonColor = '#ffffff', + textColor = WHITE_ON_GRADIENT, + buttonColor = tokenRef('bg'), anchorId, style = {}, }) => { @@ -65,8 +75,7 @@ export const CallToAction: UserComponent = ({ bgStyle.backgroundPosition = 'center'; } - const isButtonDark = buttonColor === '#ffffff' || buttonColor === '#f8fafc'; - const buttonTextColor = isButtonDark ? '#18181b' : '#ffffff'; + const buttonTextColor = isLightButton(buttonColor) ? tokenRef('text') : tokenRef('bg'); const effectiveCtas = normalizeCtas({ ctas, buttonText, buttonHref, secondaryButtonText, secondaryButtonHref }); const ctaDefaults = { primaryBg: buttonColor, primaryText: buttonTextColor, outlineText: textColor }; @@ -133,8 +142,8 @@ CallToAction.craft = { bgValue: defaultGradient, overlayColor: '#000000', overlayOpacity: 0, - textColor: '#ffffff', - buttonColor: '#ffffff', + textColor: WHITE_ON_GRADIENT, + buttonColor: tokenRef('bg'), style: {}, animation: '', animationDelay: '', @@ -156,10 +165,9 @@ CallToAction.craft = { const bgValue = props.bgValue || defaultGradient; // Sanitized -- raw string-interpolation sink in the heading/description // style attributes below. - const textColor = cssValue(props.textColor) || '#ffffff'; - const buttonColor = props.buttonColor || '#ffffff'; - const isButtonDark = buttonColor === '#ffffff' || buttonColor === '#f8fafc'; - const buttonTextColor = isButtonDark ? '#18181b' : '#ffffff'; + const textColor = cssValue(props.textColor) || WHITE_ON_GRADIENT; + const buttonColor = props.buttonColor || tokenRef('bg'); + const buttonTextColor = isLightButton(buttonColor) ? tokenRef('text') : tokenRef('bg'); const sectionCss: CSSProperties = { position: 'relative', diff --git a/craft/src/components/sections/FeaturesGrid.toHtml.test.ts b/craft/src/components/sections/FeaturesGrid.toHtml.test.ts index 6197dc1..4954a6f 100644 --- a/craft/src/components/sections/FeaturesGrid.toHtml.test.ts +++ b/craft/src/components/sections/FeaturesGrid.toHtml.test.ts @@ -41,3 +41,12 @@ describe('FeaturesGrid.toHtml palette token fallback (Refs #44)', () => { expect(html).not.toContain('#3b82f6'); }); }); + +describe('FeaturesGrid.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({}, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "border", "text", "muted"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#f8fafc", "#e2e8f0", "#18181b", "#64748b", "#fff"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/sections/FeaturesGrid.tsx b/craft/src/components/sections/FeaturesGrid.tsx index 63d117e..7ff34a3 100644 --- a/craft/src/components/sections/FeaturesGrid.tsx +++ b/craft/src/components/sections/FeaturesGrid.tsx @@ -52,7 +52,7 @@ export const FeaturesGrid: UserComponent = ({ id={anchorId || undefined} style={{ padding: '80px 20px', - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} @@ -65,8 +65,8 @@ export const FeaturesGrid: UserComponent = ({ textAlign: 'center', padding: '32px 24px', borderRadius: '12px', - backgroundColor: '#f8fafc', - border: '1px solid #e2e8f0', + backgroundColor: tokenRef('bg'), + border: `1px solid ${tokenRef('border')}`, }} > {feat.image ? ( @@ -78,13 +78,13 @@ export const FeaturesGrid: UserComponent = ({ ) : (
{feat.icon}
)} -

{feat.title}

-

{feat.description}

+

{feat.title}

+

{feat.description}

{feat.buttonText ? ( e.preventDefault()} - style={{ display: 'inline-block', marginTop: '16px', padding: '10px 24px', background: tokenRef('primary'), color: '#fff', borderRadius: '8px', textDecoration: 'none', fontSize: '14px', fontWeight: 600 }} + style={{ display: 'inline-block', marginTop: '16px', padding: '10px 24px', background: tokenRef('primary'), color: tokenRef('bg'), borderRadius: '8px', textDecoration: 'none', fontSize: '14px', fontWeight: 600 }} > {feat.buttonText} @@ -102,7 +102,7 @@ FeaturesGrid.craft = { displayName: 'Features Grid', props: { features: defaultFeatures, - style: { backgroundColor: '#ffffff' }, + style: { backgroundColor: tokenRef('bg') }, anchorId: '', animation: '', animationDelay: '', @@ -130,12 +130,12 @@ FeaturesGrid.craft = { ? `${escapeAttr(feat.imageAlt || feat.title || '')}` : `
${escapeHtml(feat.icon)}
`; const button = feat.buttonText - ? `\n ${escapeHtml(feat.buttonText)}` + ? `\n ${escapeHtml(feat.buttonText)}` : ''; - return `
+ return `
${media} -

${escapeHtml(feat.title)}

-

${escapeHtml(feat.description)}

${button} +

${escapeHtml(feat.title)}

+

${escapeHtml(feat.description)}

${button}
`; }).join('\n '); diff --git a/craft/src/components/sections/Gallery.toHtml.test.ts b/craft/src/components/sections/Gallery.toHtml.test.ts index 22daeb6..0500a0b 100644 --- a/craft/src/components/sections/Gallery.toHtml.test.ts +++ b/craft/src/components/sections/Gallery.toHtml.test.ts @@ -216,3 +216,12 @@ describe('Gallery.toHtml lightbox focus management (M-2)', () => { expect(closeFnMatch![0]).toMatch(/\.focus\(\)/); }); }); + +describe('Gallery.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ images: [{ src: 'a.png', alt: 'a', caption: 'c' }] }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#f1f5f9"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/sections/Gallery.tsx b/craft/src/components/sections/Gallery.tsx index 10ceee3..073edbb 100644 --- a/craft/src/components/sections/Gallery.tsx +++ b/craft/src/components/sections/Gallery.tsx @@ -1,4 +1,5 @@ import React, { CSSProperties } from 'react'; +import { tokenRef } from '../../utils/tokens'; import { useNode, UserComponent } from '@craftjs/core'; import { cssPropsToString } from '../../utils/style-helpers'; import { escapeHtml, escapeAttr, safeImageUrl, scopeId, cssValue } from '../../utils/escape'; @@ -56,7 +57,7 @@ export const Gallery: UserComponent = ({ ref={(ref: HTMLElement | null): void => { if (ref) connect(drag(ref)); }} style={{ padding: '60px 20px', - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} @@ -81,7 +82,7 @@ export const Gallery: UserComponent = ({ objectFit: 'cover', display: 'block', borderRadius: '8px', - backgroundColor: '#f1f5f9', + backgroundColor: tokenRef('bg'), }} /> {img.caption && ( @@ -118,7 +119,7 @@ Gallery.craft = { columns: 3, gap: '16px', style: { - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), marginTop: '', marginRight: '', marginBottom: '', marginLeft: '', paddingTop: '', paddingRight: '', paddingBottom: '', paddingLeft: '', border: 'none', @@ -174,7 +175,7 @@ Gallery.craft = { const lbAttr = lightbox ? ` data-lb-src="${escapeAttr(safeImageUrl(img.src || ''))}" role="button" tabindex="0"` : ''; const itemStyle = lightbox ? 'cursor:pointer;position:relative;overflow:hidden;border-radius:8px' : 'position:relative;overflow:hidden;border-radius:8px'; return ` - ${escapeAttr(img.alt)} + ${escapeAttr(img.alt)} ${caption}
`; }).join('\n '); diff --git a/craft/src/components/sections/NumberCounter.toHtml.test.ts b/craft/src/components/sections/NumberCounter.toHtml.test.ts index 5a7cec9..47c39e2 100644 --- a/craft/src/components/sections/NumberCounter.toHtml.test.ts +++ b/craft/src/components/sections/NumberCounter.toHtml.test.ts @@ -94,3 +94,12 @@ describe('NumberCounter.toHtml palette token fallbacks (Refs #44)', () => { expect(html).not.toContain('#6b7280'); }); }); + +describe('NumberCounter.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ counters: [{ number: 1, suffix: '+', label: 'L' }] }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#ffffff"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/sections/NumberCounter.tsx b/craft/src/components/sections/NumberCounter.tsx index 132f78a..3ec4878 100644 --- a/craft/src/components/sections/NumberCounter.tsx +++ b/craft/src/components/sections/NumberCounter.tsx @@ -53,7 +53,7 @@ export const NumberCounter: UserComponent = ({ ref={(ref: HTMLElement | null): void => { if (ref) connect(drag(ref)); }} style={{ padding: '60px 20px', - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} @@ -104,7 +104,7 @@ NumberCounter.craft = { numberColor: tokenRef('primary'), labelColor: tokenRef('muted'), numberSize: '48px', - style: { backgroundColor: '#ffffff' }, + style: { backgroundColor: tokenRef('bg') }, animation: '', animationDelay: '', hideOnDesktop: false, @@ -146,7 +146,7 @@ NumberCounter.craft = { const sectionStyle = cssPropsToString({ padding: '60px 20px', - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), ...style, }); diff --git a/craft/src/components/sections/PricingTable.toHtml.test.ts b/craft/src/components/sections/PricingTable.toHtml.test.ts index 9f4b6a8..c3713db 100644 --- a/craft/src/components/sections/PricingTable.toHtml.test.ts +++ b/craft/src/components/sections/PricingTable.toHtml.test.ts @@ -40,13 +40,12 @@ describe('PricingTable.toHtml regular-card color overrides (previously hard-code expect(html).toContain('color:#eeeeee'); }); - test('unset overrides fall back to the exact prior literals (backward compatible)', () => { + test('unset overrides fall back to the design tokens', () => { const { html } = toHtml({ plans }, ''); - // Regular (non-featured) card literals unchanged from before these props existed. - expect(html).toContain('background-color:#ffffff'); - expect(html).toContain('color:#18181b'); - expect(html).toContain('color:#64748b'); - expect(html).toContain('color:#4b5563'); + // Regular (non-featured) card fallbacks are now design tokens (#44), not hex literals. + expect(html).toContain('background-color:var(--wsb-bg)'); + expect(html).toContain('color:var(--wsb-text)'); + expect(html).toContain('color:var(--wsb-muted)'); expect(html).toContain('color:var(--wsb-success)'); }); @@ -118,3 +117,12 @@ describe('PricingTable.toHtml palette token fallbacks (Refs #44)', () => { expect(html).toContain('color:#222222'); }); }); + +describe('PricingTable.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ plans: [{ name: 'A', price: '$1', period: '/m', features: ['f'], buttonText: 'B', buttonHref: '#', isFeatured: false }, { name: 'P', price: '$2', period: '/m', features: ['f'], buttonText: 'B', buttonHref: '#', isFeatured: true }] }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "border", "text", "muted", "success", "warning", "primary"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#e2e8f0", "#18181b", "#64748b", "#4b5563", "#bbf7d0", "#facc15", "#ffffff"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/sections/PricingTable.tsx b/craft/src/components/sections/PricingTable.tsx index 7743c7b..b36f215 100644 --- a/craft/src/components/sections/PricingTable.tsx +++ b/craft/src/components/sections/PricingTable.tsx @@ -94,13 +94,13 @@ export const PricingTable: UserComponent = ({ selected: node.events.selected, })); - const regCardBg = cardBg || '#ffffff'; - const regTextColor = textColor || '#18181b'; - const regSubColor = subColor || '#64748b'; - const regFeatColor = featColor || '#4b5563'; + const regCardBg = cardBg || tokenRef('bg'); + const regTextColor = textColor || tokenRef('text'); + const regSubColor = subColor || tokenRef('muted'); + const regFeatColor = featColor || tokenRef('muted'); const regCheckColor = checkColor || tokenRef('success'); const regBtnBg = btnBg || featuredBg; - const regBtnColor = btnColor || '#ffffff'; + const regBtnColor = btnColor || tokenRef('bg'); return (
= ({ id={anchorId || undefined} style={{ padding: '80px 20px', - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} @@ -129,7 +129,7 @@ export const PricingTable: UserComponent = ({ flex: '1 1 280px', maxWidth: '360px', backgroundColor: plan.isFeatured ? featuredBg : regCardBg, - border: plan.isFeatured ? 'none' : '1px solid #e2e8f0', + border: plan.isFeatured ? 'none' : `1px solid ${tokenRef('border')}`, borderRadius: '16px', padding: '40px 32px', display: 'flex', @@ -145,8 +145,8 @@ export const PricingTable: UserComponent = ({
= ({

@@ -170,7 +170,7 @@ export const PricingTable: UserComponent = ({ {plan.price} @@ -199,7 +199,7 @@ export const PricingTable: UserComponent = ({ alignItems: 'center', gap: '8px', }}> - {bulletChars[bulletType] || '✓'} + {bulletChars[bulletType] || '✓'} {feature} ))} @@ -211,7 +211,7 @@ export const PricingTable: UserComponent = ({ marginTop: 'auto', display: 'inline-block', padding: '14px 32px', - backgroundColor: plan.isFeatured ? '#ffffff' : regBtnBg, + backgroundColor: plan.isFeatured ? tokenRef('bg') : regBtnBg, color: plan.isFeatured ? featuredBg : regBtnColor, textDecoration: 'none', borderRadius: '8px', @@ -236,7 +236,7 @@ PricingTable.craft = { displayName: 'Pricing Table', props: { plans: defaultPlans, - style: { backgroundColor: '#ffffff' }, + style: { backgroundColor: tokenRef('bg') }, featuredBg: tokenRef('primary'), bulletType: 'check', anchorId: '', @@ -278,22 +278,22 @@ PricingTable.craft = { // the exact literal the card was previously hard-coded to (or, for the // button, to featuredBg) when unset, so unmodified pricing tables render // identically to before these props existed. - const regCardBg = cssValue(props.cardBg) || '#ffffff'; - const regTextColor = cssValue(props.textColor) || '#18181b'; - const regSubColor = cssValue(props.subColor) || '#64748b'; - const regFeatColor = cssValue(props.featColor) || '#4b5563'; + const regCardBg = cssValue(props.cardBg) || tokenRef('bg'); + const regTextColor = cssValue(props.textColor) || tokenRef('text'); + const regSubColor = cssValue(props.subColor) || tokenRef('muted'); + const regFeatColor = cssValue(props.featColor) || tokenRef('muted'); const regCheckColor = cssValue(props.checkColor) || tokenRef('success'); const regBtnBg = cssValue(props.btnBg) || featuredBg; - const regBtnColor = cssValue(props.btnColor) || '#ffffff'; + const regBtnColor = cssValue(props.btnColor) || tokenRef('bg'); const cards = plans.map((plan) => { const cardBg = plan.isFeatured ? featuredBg : regCardBg; - const cardBorder = plan.isFeatured ? 'border:none;' : 'border:1px solid #e2e8f0;'; - const textColor = plan.isFeatured ? '#ffffff' : regTextColor; + const cardBorder = plan.isFeatured ? 'border:none;' : `border:1px solid ${tokenRef('border')};`; + const textColor = plan.isFeatured ? tokenRef('bg') : regTextColor; const subColor = plan.isFeatured ? 'rgba(255,255,255,0.8)' : regSubColor; const featColor = plan.isFeatured ? 'rgba(255,255,255,0.9)' : regFeatColor; - const checkColor = plan.isFeatured ? '#bbf7d0' : regCheckColor; - const btnBg = plan.isFeatured ? '#ffffff' : regBtnBg; + const checkColor = plan.isFeatured ? tokenRef('success') : regCheckColor; + const btnBg = plan.isFeatured ? tokenRef('bg') : regBtnBg; const btnColor = plan.isFeatured ? featuredBg : regBtnColor; const scale = plan.isFeatured ? 'transform:scale(1.05);' : ''; const shadow = plan.isFeatured ? 'box-shadow:0 20px 60px rgba(59,130,246,0.3);' : 'box-shadow:0 1px 3px rgba(0,0,0,0.06);'; @@ -303,7 +303,7 @@ PricingTable.craft = { ).join('\n '); const badge = plan.isFeatured - ? `
Most Popular
` + ? `
Most Popular
` : ''; return `
diff --git a/craft/src/components/sections/Tabs.toHtml.test.ts b/craft/src/components/sections/Tabs.toHtml.test.ts index 0abe735..18b7144 100644 --- a/craft/src/components/sections/Tabs.toHtml.test.ts +++ b/craft/src/components/sections/Tabs.toHtml.test.ts @@ -99,3 +99,12 @@ describe('Tabs.toHtml palette token fallback (Refs #44)', () => { expect(html).not.toContain('#3b82f6'); }); }); + +describe('Tabs.toHtml design tokens (#44)', () => { + test('default export references var(--wsb-*) and no longer carries the old hard-coded hex', () => { + const { html } = toHtml({ tabs: [{ title: 'A', content: 'B' }, { title: 'C', content: 'D' }] }, ''); + const lower = html.toLowerCase(); + for (const t of ["bg", "border", "muted"]) expect(html, `token ${t}`).toContain(`var(--wsb-${t})`); + for (const hex of ["#e2e8f0", "#4b5563", "#64748b", "#f1f5f9"]) expect(lower, `old ${hex}`).not.toContain(hex); + }); +}); diff --git a/craft/src/components/sections/Tabs.tsx b/craft/src/components/sections/Tabs.tsx index 9860857..8adb61b 100644 --- a/craft/src/components/sections/Tabs.tsx +++ b/craft/src/components/sections/Tabs.tsx @@ -35,10 +35,10 @@ export const Tabs: UserComponent = ({ tabs = defaultTabs, style = {}, activeTabBg = tokenRef('primary'), - activeTabColor = '#ffffff', - inactiveTabBg = '#f1f5f9', - inactiveTabColor = '#64748b', - contentBg = '#ffffff', + activeTabColor = tokenRef('bg'), + inactiveTabBg = tokenRef('bg'), + inactiveTabColor = tokenRef('muted'), + contentBg = tokenRef('bg'), anchorId, }) => { const { @@ -56,14 +56,14 @@ export const Tabs: UserComponent = ({ id={anchorId || undefined} style={{ padding: '60px 20px', - backgroundColor: '#ffffff', + backgroundColor: tokenRef('bg'), outline: selected ? '2px solid #3b82f6' : 'none', ...style, }} >
{/* Tab buttons */} -
+
{tabs.map((tab, i) => (