From 6770d7234ec881dcf968f16bfe50dbd0d744244b Mon Sep 17 00:00:00 2001 From: Sanne de Vries Date: Mon, 17 Feb 2025 15:45:24 +0100 Subject: [PATCH 1/2] Organised CTA card settings panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ref https://linear.app/ghost/issue/PLG-355/improve-cta-card-settings-panel - Updated ButtonGroup styles. - Added a button-style image upload placeholder to MediaPlaceholder. Also updated the VideoCard to follow the same style. - Refactored ColorOptionButtons and ColorPicker to display swatches and colorpicker inside toolbar. This also affects the callout, signup and header cards and the image-background selector. - Added pink and purple as background colors to the CTA card. - Changed CTA card link styles to currentColor instead of accent color – both for the label and the body text. --- .../nodes/call-to-action/CallToActionNode.js | 6 +- .../src/components/ui/ButtonGroup.jsx | 4 +- .../src/components/ui/ColorOptionButtons.jsx | 75 ++++++--- .../src/components/ui/ColorPicker.jsx | 142 +++++++++++++++--- .../src/components/ui/MediaPlaceholder.jsx | 125 ++++++++++++--- .../src/components/ui/MediaUploader.jsx | 3 + .../src/components/ui/SettingsPanel.jsx | 45 +++--- .../components/ui/cards/CallToActionCard.jsx | 49 +++--- .../src/components/ui/cards/CalloutCard.jsx | 15 +- .../ui/cards/HeaderCard/v2/HeaderCard.jsx | 58 +++---- .../src/components/ui/cards/SignupCard.jsx | 57 +++---- .../src/components/ui/cards/VideoCard.jsx | 3 +- .../src/styles/components/kg-prose.css | 8 + packages/koenig-lexical/tailwind.config.cjs | 1 + 14 files changed, 412 insertions(+), 179 deletions(-) diff --git a/packages/kg-default-nodes/lib/nodes/call-to-action/CallToActionNode.js b/packages/kg-default-nodes/lib/nodes/call-to-action/CallToActionNode.js index 5176bc258b..1f9726f798 100644 --- a/packages/kg-default-nodes/lib/nodes/call-to-action/CallToActionNode.js +++ b/packages/kg-default-nodes/lib/nodes/call-to-action/CallToActionNode.js @@ -8,10 +8,10 @@ export class CallToActionNode extends generateDecoratorNode({ properties: [ {name: 'layout', default: 'minimal'}, {name: 'textValue', default: '', wordCount: true}, - {name: 'showButton', default: false}, - {name: 'buttonText', default: ''}, + {name: 'showButton', default: true}, + {name: 'buttonText', default: 'Learn more'}, {name: 'buttonUrl', default: ''}, - {name: 'buttonColor', default: ''}, + {name: 'buttonColor', default: 'black'}, {name: 'buttonTextColor', default: ''}, {name: 'hasSponsorLabel', default: true}, {name: 'sponsorLabel', default: '

SPONSORED

'}, diff --git a/packages/koenig-lexical/src/components/ui/ButtonGroup.jsx b/packages/koenig-lexical/src/components/ui/ButtonGroup.jsx index 3a2aee8352..ad95b6d4bb 100644 --- a/packages/koenig-lexical/src/components/ui/ButtonGroup.jsx +++ b/packages/koenig-lexical/src/components/ui/ButtonGroup.jsx @@ -7,7 +7,7 @@ import {usePreviousFocus} from '../../hooks/usePreviousFocus'; export function ButtonGroup({buttons = [], selectedName, onClick}) { return (
-
); } @@ -35,7 +66,7 @@ export function ColorButton({onClick, label, name, color, selectedName}) { const {handleMousedown, handleClick} = usePreviousFocus(onClick, name); return ( -
  • +
  • + + {isOpen && ( +
    + {showColorPicker && ( + + )} + {showChildren && children} +
    +
    + {swatches.map(({customContent, ...swatch}) => ( + customContent ? + {customContent} : + { + onSwatchChange(value); + setShowColorPicker(false); + setIsOpen(false); + }} + {...swatch} + /> + ))} +
    + +
    +
    + )} ); } diff --git a/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx b/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx index 794d1ce45b..b1fe9fe312 100644 --- a/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx @@ -6,6 +6,7 @@ import ProductPlaceholderIcon from '../../assets/icons/kg-product-placeholder.sv import PropTypes from 'prop-types'; import React from 'react'; import VideoPlaceholderIcon from '../../assets/icons/kg-video-placeholder.svg?react'; +import clsx from 'clsx'; export const PLACEHOLDER_ICONS = { image: ImgPlaceholderIcon, @@ -16,17 +17,24 @@ export const PLACEHOLDER_ICONS = { product: ProductPlaceholderIcon }; -export const CardText = ({text}) => { - return ( - {text} - ); -}; +export const CardText = ({text, type}) => ( + + {text} + +); export function MediaPlaceholder({ desc, icon, filePicker, size, + type, borderStyle, isDraggedOver, errors = [], @@ -38,33 +46,99 @@ export function MediaPlaceholder({ }) { const Icon = PLACEHOLDER_ICONS[icon]; + const containerClasses = clsx( + 'relative flex h-full items-center justify-center', + type === 'button' ? 'rounded-lg bg-grey-100' : 'border bg-grey-50', + size === 'xsmall' && type !== 'button' && 'before:pb-[12.5%] dark:bg-grey-900', + size !== 'xsmall' && type !== 'button' && 'before:pb-[62.5%] dark:bg-grey-950', + borderStyle === 'rounded' && type !== 'button' && 'rounded-lg border-grey/20 dark:border-transparent', + borderStyle !== 'rounded' && type !== 'button' && 'border-grey/20 dark:border-grey/10' + ); + + const iconClasses = clsx( + 'shrink-0 opacity-80 transition-all ease-linear hover:scale-105 group-hover:opacity-100', + size === 'large' && 'size-20 text-grey', + size === 'small' && 'size-14 text-grey', + size === 'xsmall' && 'size-5 text-grey-700', + !['large', 'small', 'xsmall'].includes(size) && 'size-16 text-grey', + (size === 'xsmall' && desc) && 'mr-3' + ); + + const descriptionClasses = clsx( + 'flex min-w-[auto] !font-sans !text-sm !font-normal text-grey-700 opacity-80 transition-all group-hover:opacity-100', + size === 'xsmall' && '!mt-0', + size !== 'xsmall' && '!mt-4' + ); + + const buttonClasses = clsx( + 'group flex cursor-pointer select-none items-center justify-center', + size === 'xsmall' && 'p-4', + size !== 'xsmall' && 'flex-col p-20' + ); + + const errorClasses = clsx( + 'font-sans text-sm font-semibold text-red', + size !== 'xsmall' && 'mt-3 max-w-[65%]' + ); + return (
    -
    - {isDraggedOver ? - : +
    + {isDraggedOver ? ( + + ) : ( <> - + {type === 'button' ? ( + + ) : ( + + )} - } + )}
    ); @@ -74,6 +148,7 @@ MediaPlaceholder.propTypes = { icon: PropTypes.oneOf(['image', 'gallery', 'video', 'audio', 'file', 'product']), desc: PropTypes.string, size: PropTypes.oneOf(['xsmall', 'small', 'medium', 'large']), + type: PropTypes.oneOf(['image', 'button']), borderStyle: PropTypes.oneOf(['squared', 'rounded']) }; diff --git a/packages/koenig-lexical/src/components/ui/MediaUploader.jsx b/packages/koenig-lexical/src/components/ui/MediaUploader.jsx index ca108223c8..1c1cfc606d 100644 --- a/packages/koenig-lexical/src/components/ui/MediaUploader.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaUploader.jsx @@ -17,6 +17,7 @@ export function MediaUploader({ desc, icon, size, + type, borderStyle = 'squared', backgroundSize = 'cover', mimeTypes, @@ -64,6 +65,7 @@ export function MediaUploader({ isDraggedOver={dragHandler?.isDraggedOver} placeholderRef={dragHandler?.setRef} size={size} + type={type} /> { - onTogglePicker(true); - }; - +export function ColorPickerSetting({label, isExpanded, onSwatchChange, onPickerChange, onTogglePicker, value, swatches, eyedropper, hasTransparentOption, dataTestId, customToolbarContent, children}) { const markClickedInside = (event) => { event.stopPropagation(); }; - // Close on click outside - React.useEffect(() => { - if (isExpanded) { - const closePicker = (event) => { - onTogglePicker(false); - }; - document.addEventListener('click', closePicker); - - return () => { - document.removeEventListener('click', closePicker); - }; - } - }, [isExpanded, onTogglePicker]); - return (
    @@ -273,28 +255,36 @@ export function ColorPickerSetting({label, isExpanded, onSwatchChange, onPickerC
    + onTogglePicker={onTogglePicker} + > + {children} +
    - {isExpanded && }
    ); } -export function MediaUploadSetting({className, label, hideLabel, onFileChange, isDraggedOver, placeholderRef, src, alt, isLoading, errors = [], progress, onRemoveMedia, icon, desc = '', size, stacked, borderStyle, mimeTypes, isPinturaEnabled, openImageEditor, setFileInputRef}) { +export function MediaUploadSetting({className, label, hideLabel, onFileChange, isDraggedOver, placeholderRef, src, alt, isLoading, errors = [], progress, onRemoveMedia, icon, desc, size, type, stacked, borderStyle, mimeTypes, isPinturaEnabled, openImageEditor, setFileInputRef}) { return ( -
    -
    {label}
    +
    +
    {label}
    diff --git a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx index 79e611eb20..c92c8a00dc 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx @@ -21,7 +21,9 @@ export const CALLTOACTION_COLORS = { blue: 'bg-blue/10 border-transparent', green: 'bg-green/10 border-transparent', yellow: 'bg-yellow/10 border-transparent', - red: 'bg-red/10 border-transparent' + red: 'bg-red/10 border-transparent', + pink: 'bg-pink/10 border-transparent', + purple: 'bg-purple/10 border-transparent' }; const sponsoredLabelTheme = { @@ -43,27 +45,37 @@ export const callToActionColorPicker = [ { label: 'Grey', name: 'grey', - color: 'bg-grey/15 border-black/[.08] dark:border-white/10' + color: 'bg-grey/20 border-black/[.08] dark:border-white/10' }, { label: 'Blue', name: 'blue', - color: 'bg-blue/15 border-black/[.08] dark:border-white/10' + color: 'bg-blue/20 border-black/[.08] dark:border-white/10' }, { label: 'Green', name: 'green', - color: 'bg-green/15 border-black/[.08] dark:border-white/10' + color: 'bg-green/20 border-black/[.08] dark:border-white/10' }, { label: 'Yellow', name: 'yellow', - color: 'bg-yellow/15 border-black/[.08] dark:border-white/10' + color: 'bg-yellow/20 border-black/[.08] dark:border-white/10' }, { label: 'Red', name: 'red', - color: 'bg-red/15 border-black/[.08] dark:border-white/10' + color: 'bg-red/20 border-black/[.08] dark:border-white/10' + }, + { + label: 'Pink', + name: 'pink', + color: 'bg-pink/20 border-black/[0.08] dark:border-white/10' + }, + { + label: 'Purple', + name: 'purple', + color: 'bg-purple/20 border-black/[0.08] dark:border-white/10' } ]; @@ -123,13 +135,6 @@ export function CallToActionCard({ const designSettings = ( <> - {/* Color picker */} - {/* Layout settings */} + {/* Color picker */} + {/* Sponsor label setting */} @@ -237,7 +250,7 @@ export function CallToActionCard({ initialTheme={sponsoredLabelTheme} nodes='basic' textClassName={clsx( - 'not-kg-prose w-full whitespace-normal font-sans !text-xs font-semibold uppercase leading-8 tracking-normal text-grey-900/40 dark:text-grey-100/40' + 'koenig-lexical-cta-label not-kg-prose w-full whitespace-normal font-sans !text-xs font-semibold uppercase leading-8 tracking-normal text-grey-900/40 dark:text-grey-200/40' )} useDefaultClasses={false} > @@ -280,7 +293,7 @@ export function CallToActionCard({ placeholderClassName={`bg-transparent whitespace-normal font-serif text-xl !text-grey-500 !dark:text-grey-800 ` } placeholderText="Write something worth clicking..." textClassName={clsx( - 'w-full whitespace-normal text-pretty bg-transparent font-serif text-xl text-grey-900 dark:text-grey-200', + 'koenig-lexical-cta-text w-full whitespace-normal text-pretty bg-transparent font-serif text-xl text-grey-900 dark:text-grey-200', layout === 'immersive' ? 'text-center' : 'text-left' )} > @@ -332,7 +345,7 @@ CallToActionCard.propTypes = { buttonUrl: PropTypes.string, buttonColor: PropTypes.string, buttonTextColor: PropTypes.string, - color: PropTypes.oneOf(['none', 'grey', 'white', 'blue', 'green', 'yellow', 'red']), + color: PropTypes.oneOf(['none', 'grey', 'white', 'blue', 'green', 'yellow', 'red', 'pink', 'purple']), hasSponsorLabel: PropTypes.bool, imageSrc: PropTypes.string, isEditing: PropTypes.bool, @@ -366,7 +379,7 @@ CallToActionCard.defaultProps = { imageSrc: '', isEditing: false, layout: 'immersive', - showButton: false, + showButton: true, updateHasSponsorLabel: () => {}, updateShowButton: () => {}, updateLayout: () => {}, diff --git a/packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx index eec21adf8a..7b1b6b4542 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx @@ -43,37 +43,37 @@ export const calloutColorPicker = [ { label: 'Grey', name: 'grey', - color: 'bg-grey/15 border-black/[0.08] dark:border-white/10' + color: 'bg-grey/20 border-black/[0.08] dark:border-white/10' }, { label: 'Blue', name: 'blue', - color: 'bg-blue/15 border-black/[0.08] dark:border-white/10' + color: 'bg-blue/20 border-black/[0.08] dark:border-white/10' }, { label: 'Green', name: 'green', - color: 'bg-green/15 border-black/[0.08] dark:border-white/10' + color: 'bg-green/20 border-black/[0.08] dark:border-white/10' }, { label: 'Yellow', name: 'yellow', - color: 'bg-yellow/15 border-black/[0.08] dark:border-white/10' + color: 'bg-yellow/20 border-black/[0.08] dark:border-white/10' }, { label: 'Red', name: 'red', - color: 'bg-red/15 border-black/[0.08] dark:border-white/10' + color: 'bg-red/20 border-black/[0.08] dark:border-white/10' }, { label: 'Pink', name: 'pink', - color: 'bg-pink/15 border-black/[0.08] dark:border-white/10' + color: 'bg-pink/20 border-black/[0.08] dark:border-white/10' }, { label: 'Purple', name: 'purple', - color: 'bg-purple/15 border-black/[0.08] dark:border-white/10' + color: 'bg-purple/20 border-black/[0.08] dark:border-white/10' }, { label: 'Accent', @@ -158,7 +158,6 @@ export function CalloutCard({ buttons={calloutColorPicker} dataTestId='callout-color-picker' label='Background' - layout='stacked' selectedName={color} onClick={handleColorChange} /> diff --git a/packages/koenig-lexical/src/components/ui/cards/HeaderCard/v2/HeaderCard.jsx b/packages/koenig-lexical/src/components/ui/cards/HeaderCard/v2/HeaderCard.jsx index 1f20d78929..1af4e8f9ac 100644 --- a/packages/koenig-lexical/src/components/ui/cards/HeaderCard/v2/HeaderCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/HeaderCard/v2/HeaderCard.jsx @@ -415,7 +415,7 @@ export function HeaderCard({alignment, type="button" onClick={() => { handleShowBackgroundImage(); - setBackgroundColorPickerExpanded(false); + setBackgroundColorPickerExpanded(true); setButtonColorPickerExpanded(false); }} > @@ -434,7 +434,7 @@ export function HeaderCard({alignment, handleBackgroundColor(color, matchingTextColor(color)); setBackgroundColorPickerExpanded(false); }} - onTogglePicker={ (isExpanded) => { + onTogglePicker={(isExpanded) => { if (isExpanded) { if (layout !== 'split') { handleHideBackgroundImage(); @@ -450,32 +450,34 @@ export function HeaderCard({alignment, setButtonColorPickerExpanded(!isExpanded); } }} - /> - { - handleClearBackgroundImage(); - handleTextColor(matchingTextColor(backgroundColor)); - }} - /> + > + {layout !== 'split' && showBackgroundImage && ( + { + handleClearBackgroundImage(); + handleTextColor(matchingTextColor(backgroundColor)); + }} + /> + )} + {/* Button settings */} { handleShowBackgroundImage(); - setBackgroundColorPickerExpanded(false); + setBackgroundColorPickerExpanded(true); setButtonColorPickerExpanded(false); }} > @@ -448,33 +448,34 @@ export function SignupCard({alignment, setButtonColorPickerExpanded(!isExpanded); } }} - /> - - { - handleClearBackgroundImage(); - handleTextColor(matchingTextColor(backgroundColor)); - }} - /> + > + {layout !== 'split' && showBackgroundImage && ( + { + handleClearBackgroundImage(); + handleTextColor(matchingTextColor(backgroundColor)); + }} + /> + )} + diff --git a/packages/koenig-lexical/src/styles/components/kg-prose.css b/packages/koenig-lexical/src/styles/components/kg-prose.css index d183aad1d3..b7f945150c 100644 --- a/packages/koenig-lexical/src/styles/components/kg-prose.css +++ b/packages/koenig-lexical/src/styles/components/kg-prose.css @@ -981,6 +981,14 @@ } } +.koenig-lexical-cta-label a { + @apply !text-grey-900 dark:!text-grey-200 underline; +} + +.koenig-lexical-cta-text a { + @apply !text-grey-900 dark:!text-grey-200; +} + /* stylelint-enable at-rule-disallowed-list */ diff --git a/packages/koenig-lexical/tailwind.config.cjs b/packages/koenig-lexical/tailwind.config.cjs index f6133fb4fe..925d003c2e 100644 --- a/packages/koenig-lexical/tailwind.config.cjs +++ b/packages/koenig-lexical/tailwind.config.cjs @@ -91,6 +91,7 @@ module.exports = { }, boxShadow: { DEFAULT: '0 0 1px rgba(0,0,0,.15), 0px 13px 27px -5px rgba(50, 50, 93, 0.08), 0px 8px 16px -8px rgba(0, 0, 0, 0.12)', + xs: '0px 1px 2px rgba(0, 0, 0, 0.06)', sm: '0px 2px 5px -1px rgba(50, 50, 93, 0.2), 0px 1px 3px -1px rgba(0, 0, 0, 0.25)', md: '0px 13px 27px -5px rgba(50, 50, 93, 0.25), 0px 8px 16px -8px rgba(0, 0, 0, 0.3)', lg: '0px 50px 100px -25px rgba(50, 50, 93, 0.2), 0px 30px 60px -20px rgba(0, 0, 0, 0.25)', From 743cf276d2eab26b727262e2230491a1b9456501 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 18 Feb 2025 15:23:17 +0900 Subject: [PATCH 2/2] Fixed callout card tests --- .../src/nodes/CallToActionNodeComponent.jsx | 1 - .../koenig-lexical/test/e2e/cards/callout-card.test.js | 10 ++++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx b/packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx index b441153870..466d1811a3 100644 --- a/packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx +++ b/packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx @@ -25,7 +25,6 @@ export const CallToActionNodeComponent = ({ htmlEditor, htmlEditorInitialState, buttonTextColor, - href, sponsorLabelHtmlEditor, sponsorLabelHtmlEditorInitialState }) => { diff --git a/packages/koenig-lexical/test/e2e/cards/callout-card.test.js b/packages/koenig-lexical/test/e2e/cards/callout-card.test.js index 467af9c8c1..85c7eba1b8 100644 --- a/packages/koenig-lexical/test/e2e/cards/callout-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/callout-card.test.js @@ -139,8 +139,10 @@ test.describe('Callout Card', async () => { await focusEditor(page); await insertCard(page, {cardName: 'callout'}); - const colorPicker = page.locator(`[data-test-id="color-picker-green"]`); - await colorPicker.click(); + const colorSetting = page.locator('[data-testid="callout-color-picker"]'); + const colorButton = colorSetting.locator('button'); + await colorButton.click(); + await page.locator('[data-test-id="color-picker-green"]').click(); // ensure data-test-id="callout-bg-blue" is visible const greenCallout = page.locator('[data-testid="callout-bg-green"]'); @@ -314,6 +316,10 @@ test.describe('Callout Card', async () => { // Start editing the content await page.keyboard.type('Hello '); + const colorSetting = page.locator('[data-testid="callout-color-picker"]'); + const colorButton = colorSetting.locator('button'); + await colorButton.click(); + // Change color await page.locator(`[data-test-id="color-picker-green"]`).click();