From aa9c55e12de1eba0ac417969c484ea68172d30e4 Mon Sep 17 00:00:00 2001 From: Sanne de Vries Date: Mon, 17 Feb 2025 15:45:24 +0100 Subject: [PATCH 01/41] 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 3a042fdce9..ee22c6b176 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 a669fa9533fad1f4624159643320ea1fd283dac6 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Wed, 19 Feb 2025 15:42:30 +0900 Subject: [PATCH 02/41] Fixed Colour selection tests --- .../src/components/ui/ColorPicker.jsx | 7 ++-- .../ui/cards/HeaderCard/v1/HeaderCard.jsx | 1 + .../test/e2e/cards/callout-card.test.js | 38 +++++++++---------- .../test/e2e/cards/header-card.test.js | 27 ++++++------- .../test/e2e/cards/signup-card.test.js | 22 +++++++---- .../test/utils/background-color-helper.js | 28 ++++++++++++++ 6 files changed, 80 insertions(+), 43 deletions(-) create mode 100644 packages/koenig-lexical/test/utils/background-color-helper.js diff --git a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx index 2aceaaa19b..66009e73f3 100644 --- a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx @@ -239,9 +239,10 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, /> ))}
    - + ); +}; 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 1af4e8f9ac..04ff8eec30 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 @@ -1,6 +1,5 @@ import CenterAlignIcon from '../../../../../assets/icons/kg-align-center.svg?react'; import ExpandIcon from '../../../../../assets/icons/kg-expand.svg?react'; -import ImgBgIcon from '../../../../../assets/icons/kg-img-bg.svg?react'; import ImgFullIcon from '../../../../../assets/icons/kg-img-full.svg?react'; import ImgRegularIcon from '../../../../../assets/icons/kg-img-regular.svg?react'; import ImgWideIcon from '../../../../../assets/icons/kg-img-wide.svg?react'; @@ -17,9 +16,9 @@ import {ButtonGroupSetting, ColorPickerSetting, InputSetting, InputUrlSetting, M import {Color, textColorForBackgroundColor} from '@tryghost/color-utils'; import {FastAverageColor} from 'fast-average-color'; import {IconButton} from '../../../IconButton'; +import {ImageUploadSwatch} from '../../../ImageUploadSwatch'; import {MediaUploader} from '../../../MediaUploader'; import {ReadOnlyOverlay} from '../../../ReadOnlyOverlay'; -import {Tooltip} from '../../../Tooltip'; import {getAccentColor} from '../../../../../utils/getAccentColor'; import {isEditorEmpty} from '../../../../../utils/isEditorEmpty'; // Header Card Version 2 @@ -229,6 +228,12 @@ export function HeaderCard({alignment, } }; + const onBackgroundImageClickHandler = () => { + handleShowBackgroundImage(); + setBackgroundColorPickerExpanded(true); + setButtonColorPickerExpanded(false); + }; + return ( <>
    @@ -405,23 +410,10 @@ export function HeaderCard({alignment, (layout !== 'split' && { title: 'Image', customContent: ( - + ) }), {title: 'Black', hex: '#000000'}, From 3cf445208379d706050e93772ef61f06182ecb21 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Thu, 20 Feb 2025 16:09:30 +0900 Subject: [PATCH 04/41] Fixed image upload related tests and bugs --- .../src/components/ui/ColorPicker.jsx | 27 ++++++++++++++-- .../ui/cards/HeaderCard/v2/HeaderCard.jsx | 2 +- .../test/e2e/cards/header-card.test.js | 28 +++++++++-------- .../test/e2e/cards/signup-card.test.js | 31 +++++++++++++------ .../test/utils/background-color-helper.js | 20 ++++++------ 5 files changed, 71 insertions(+), 37 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx index 66009e73f3..efc6da9379 100644 --- a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx @@ -141,6 +141,27 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, const [showChildren, setShowChildren] = useState(false); const popoverRef = useRef(null); + // const stopPropagation = useCallback((e) => { + // e.stopPropagation(); + // e.preventDefault(); + // }, []); + + const handleDocumentClick = useCallback((event) => { + if (popoverRef.current && !popoverRef.current.contains(event.target)) { + setIsOpen(false); + } + }, []); + + // Add and remove document click listener based on isOpen + useEffect(() => { + if (isOpen) { + document.addEventListener('click', handleDocumentClick); + return () => { + document.removeEventListener('click', handleDocumentClick); + }; + } + }, [isOpen, handleDocumentClick]); + const stopPropagation = useCallback((e) => { e.stopPropagation(); e.preventDefault(); @@ -230,10 +251,10 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, { - onSwatchChange(value); + onSelect={(val) => { + onSwatchChange(val); setShowColorPicker(false); - setIsOpen(false); + // setIsOpen(false); }} {...swatch} /> 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 04ff8eec30..109fbd6cb6 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 @@ -458,7 +458,7 @@ export function HeaderCard({alignment, openImageEditor={openImageEditor} placeholderRef={imageDragHandler?.setRef} progress={progress} - setFileInputRef={setFileInputRef} + setFileInputRef={setFileInputRef} // Ensure ref is properly passed size='xsmall' src={backgroundImageSrc} stacked={true} diff --git a/packages/koenig-lexical/test/e2e/cards/header-card.test.js b/packages/koenig-lexical/test/e2e/cards/header-card.test.js index 15d75e6419..6e8f750fed 100644 --- a/packages/koenig-lexical/test/e2e/cards/header-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/header-card.test.js @@ -187,8 +187,8 @@ test.describe('Header card V1', async () => { await createHeaderCard({page}); // Check that the default size is small - await expect(page.getByLabel('S')).toHaveClass(/ bg-grey-150 /); - await expect(page.getByLabel('M')).not.toHaveClass(/ bg-grey-150 /); + await expect(page.getByLabel('S')).toHaveClass(/ shadow-xs /); + await expect(page.getByLabel('M')).not.toHaveClass(/ shadow-xs /); // Get height of the card const box = await page.locator('[data-kg-card="header"] > div:first-child').nth(0).boundingBox(); @@ -196,7 +196,7 @@ test.describe('Header card V1', async () => { // Click on the medium button await page.getByLabel('M').click(); - await expect(page.getByLabel('M')).toHaveClass(/ bg-grey-150 /); + await expect(page.getByLabel('M')).toHaveClass(/ shadow-xs /); // Check that the height has changed const box2 = await page.locator('[data-kg-card="header"] > div:first-child').nth(0).boundingBox(); @@ -207,7 +207,7 @@ test.describe('Header card V1', async () => { // Switch to large const largeButton = page.locator('[aria-label="L"]'); await largeButton.click(); - await expect(largeButton).toHaveClass(/ bg-grey-150 /); + await expect(largeButton).toHaveClass(/ shadow-xs /); // Check that the height has changed const box3 = await page.locator('[data-kg-card="header"] > div:first-child').nth(0).boundingBox(); @@ -248,8 +248,9 @@ test.describe('Header card V1', async () => { const fileChooserPromise = page.waitForEvent('filechooser'); - // Click data-testid="background-image-color-button" - await page.click('[data-testid="background-image-color-button"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'header-background-color', imageUploadId: 'background-image-color-button'}); + // click on text saying Click to upload background image + await page.getByText('Click to upload background image').click(); // Set files const fileChooser = await fileChooserPromise; @@ -570,12 +571,12 @@ test.describe('Header card V2', () => { test('can switch between background image and color', async function () { const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); await createHeaderCard({page, version: 2}); - // Choose an image + // Choose an image const fileChooserPromise = page.waitForEvent('filechooser'); - await page.click('[data-testid="header-background-image-toggle"]'); - + await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'header-background-color', imageUploadId: 'header-background-image-toggle'}); + await page.click('[data-testid="media-upload-placeholder"]'); const fileChooser = await fileChooserPromise; await fileChooser.setFiles([filePath]); @@ -585,15 +586,16 @@ test.describe('Header card V2', () => { // Switch to a color swatch - await page.click('[data-testid="header-background-color"] button[title="Black"]'); + // await page.click('[data-testid="header-background-color"] button[title="Black"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'header-background-color', findByColorTitle: 'Black'}); await expect(page.locator('[data-kg-card="header"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); await expect(page.locator('[data-testid="media-upload-setting"]')).not.toBeVisible(); - // Switch back to the image + // // Switch back to the image - await page.click('[data-testid="header-background-image-toggle"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'header-background-color', imageUploadId: 'header-background-image-toggle'}); await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-testid="media-upload-setting"]')).toBeVisible(); @@ -601,7 +603,7 @@ test.describe('Header card V2', () => { // Open the color picker - await page.click('[data-testid="header-background-color"] [aria-label="Pick color"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'header-background-color', customColor: '000000'}); await expect(page.locator('[data-kg-card="header"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); diff --git a/packages/koenig-lexical/test/e2e/cards/signup-card.test.js b/packages/koenig-lexical/test/e2e/cards/signup-card.test.js index 86150b7470..802a91e44a 100644 --- a/packages/koenig-lexical/test/e2e/cards/signup-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/signup-card.test.js @@ -255,7 +255,8 @@ test.describe('Signup card', async () => { const fileChooserPromise = page.waitForEvent('filechooser'); - await page.click('[data-testid="signup-background-image-toggle"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); + await page.click('[data-testid="media-upload-placeholder"]'); // Set files const fileChooser = await fileChooserPromise; @@ -295,7 +296,8 @@ test.describe('Signup card', async () => { const fileChooserPromise = page.waitForEvent('filechooser'); - await page.click('[data-testid="signup-background-image-toggle"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); + await page.click('[data-testid="media-upload-placeholder"]'); const fileChooser = await fileChooserPromise; await fileChooser.setFiles([filePath]); @@ -306,23 +308,24 @@ test.describe('Signup card', async () => { // Switch to a color swatch - await page.click('[data-testid="signup-background-color"] button[title="Black"]'); + // await page.click('[data-testid="signup-background-color"] button[title="Black"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'signup-background-color', findByColorTitle: 'Black'}); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); await expect(page.locator('[data-testid="media-upload-setting"]')).not.toBeVisible(); - // Switch back to the image + // // Switch back to the image - await page.click('[data-testid="signup-background-image-toggle"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-testid="media-upload-setting"]')).toBeVisible(); await expect(page.locator('[data-testid="media-upload-filled"] img')).toHaveAttribute('src', /blob:/); - // Open the color picker + // // Open the color picker - await page.click('[data-testid="signup-background-color"] [aria-label="Pick color"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'signup-background-color', customColor: '000000'}); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); @@ -337,7 +340,8 @@ test.describe('Signup card', async () => { // Text colour is updated based on the background colour - await page.click('[data-testid="signup-background-color"] button[title="Grey"]'); + // await page.click('[data-testid="signup-background-color"] button[title="Grey"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'signup-background-color', findByColorTitle: 'Grey'}); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-color', 'rgb(240, 240, 240)'); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('color', 'rgb(0, 0, 0)'); @@ -346,8 +350,9 @@ test.describe('Signup card', async () => { const fileChooserPromise = page.waitForEvent('filechooser'); - await page.click('[data-testid="signup-background-image-toggle"]'); - + // await page.click('[data-testid="signup-background-image-toggle"]'); + await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); + await page.click('[data-testid="media-upload-placeholder"]'); const fileChooser = await fileChooserPromise; await fileChooser.setFiles([filePath]); @@ -356,6 +361,8 @@ test.describe('Signup card', async () => { // When switching to split layout, text colour is set based on the background colour + await page.locator('[data-testid="settings-panel"]').click(); + await page.locator('[data-testid="signup-layout-split"]').click(); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); @@ -364,6 +371,10 @@ test.describe('Signup card', async () => { // When switching back from split layout, text colour is set based on the background colour + // data-testid="settings-panel" + + await page.locator('[data-testid="settings-panel"]').click(); // click here to close the colour swatch + await page.locator('[data-testid="signup-layout-wide"]').click(); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-image', /blob:/); diff --git a/packages/koenig-lexical/test/utils/background-color-helper.js b/packages/koenig-lexical/test/utils/background-color-helper.js index c2a84437ae..9f7cd0b5d7 100644 --- a/packages/koenig-lexical/test/utils/background-color-helper.js +++ b/packages/koenig-lexical/test/utils/background-color-helper.js @@ -1,9 +1,9 @@ -// import {expect} from '@playwright/test'; - -export async function cardBackgroundColorSettings(page, {cardColorPickerTestId, customColor, colorTestId, findByColorTitle}) { - const colorSetting = page.locator(`[data-testid="${cardColorPickerTestId}"]`); - const colorButton = colorSetting.locator('button'); - await colorButton.click(); +export async function cardBackgroundColorSettings(page, {cardColorPickerTestId, customColor, colorTestId, findByColorTitle, imageUploadId, fireColorSetting = true}) { + if (fireColorSetting) { + const colorSetting = page.locator(`[data-testid="${cardColorPickerTestId}"]`); + const colorButton = colorSetting.locator('button'); + await colorButton.click(); + } if (findByColorTitle) { const colorTitle = page.locator(`[title="${findByColorTitle}"]`); @@ -17,12 +17,12 @@ export async function cardBackgroundColorSettings(page, {cardColorPickerTestId, await colorInput.click({clickCount: 3}); await colorInput.type(customColor); } + if (colorTestId) { await page.locator(`[data-test-id="${colorTestId}"]`).click(); } - // await page.locator(`[data-test-id="${colorTestId}"]`).click(); - // Ensure the expected background color is applied - // const coloredCard = page.locator(`[data-testid="${expectedBgTestId}"]`); - // await expect(coloredCard).toBeVisible(); + if (imageUploadId) { + await page.locator(`[data-testid="${imageUploadId}"]`).click(); + } } From 6538fbc86abb60daf11b5a701b44cb9d726187f8 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Thu, 20 Feb 2025 18:48:49 +0900 Subject: [PATCH 05/41] Updated CTA Card tests --- .../components/ui/cards/CallToActionCard.jsx | 1 + .../test/e2e/cards/cta-card.test.js | 55 ++++++++----------- 2 files changed, 23 insertions(+), 33 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx index c92c8a00dc..5d3abc7c80 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx @@ -145,6 +145,7 @@ export function CallToActionCard({ {/* Color picker */} { `, {ignoreCardContents: true}); }); - test('can toggle button on card', async function () { + test('button and button settings is visible by default', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); expect(await page.isVisible('[data-testid="cta-button"]')).toBe(true); - - await page.click('[data-testid="button-settings"]'); - - expect(await page.isVisible('[data-testid="cta-button"]')).toBe(false); + expect(await page.isVisible('[data-testid="cta-button-color"]')).toBe(true); + expect(await page.isVisible('[data-testid="button-text"]')).toBe(true); + expect(await page.isVisible('[data-testid="button-url"]')).toBe(true); }); - test('button settings expands and collapses when toggled', async function () { + test('can toggle button on card and expands settings', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); + expect(await page.isVisible('[data-testid="cta-button"]')).toBe(true); await page.click('[data-testid="button-settings"]'); - // determine if settings are open byy looking for cta-button-color, button-text & button-url - expect(await page.isVisible('[data-testid="cta-button-color"]')).toBe(true); - expect(await page.isVisible('[data-testid="button-text"]')).toBe(true); - expect(await page.isVisible('[data-testid="button-url"]')).toBe(true); + expect(await page.isVisible('[data-testid="cta-button"]')).toBe(false); await page.click('[data-testid="button-settings"]'); - // determine if settings are closed by looking for cta-button-color, button-text & button-url - expect(await page.isVisible('[data-testid="cta-button-color"]')).toBe(false); - expect(await page.isVisible('[data-testid="button-text"]')).toBe(false); - expect(await page.isVisible('[data-testid="button-url"]')).toBe(false); + + expect(await page.isVisible('[data-testid="cta-button"]')).toBe(true); }); test('can set button text', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); await page.fill('[data-testid="button-text"]', 'Click me'); expect(await page.textContent('[data-testid="cta-button"]')).toBe('Click me'); }); @@ -156,7 +150,6 @@ test.describe('Call To Action Card', async () => { test('can set button url', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); await page.fill('[data-testid="button-url"]', 'https://example.com/somepost'); const buttonContainer = await page.$('[data-test-cta-button-current-url]'); const currentUrl = await buttonContainer.getAttribute('data-test-cta-button-current-url'); @@ -167,7 +160,6 @@ test.describe('Call To Action Card', async () => { test('suggested urls display', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); const buttonTextInput = await page.getByTestId('button-url'); await expect(buttonTextInput).toHaveValue(''); @@ -188,7 +180,6 @@ test.describe('Call To Action Card', async () => { test('button doesnt disappear when toggled, has text, has url and loses focus', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); await page.fill('[data-testid="button-text"]', 'Click me'); await page.fill('[data-testid="button-url"]', 'https://example.com/somepost'); expect(await page.isVisible('[data-testid="cta-button"]')).toBe(true); @@ -217,16 +208,15 @@ test.describe('Call To Action Card', async () => { test('default button colour is accent', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); expect(await page.getAttribute('[data-testid="cta-button"]', 'class')).toContain('bg-accent'); }); test('can change button colour to black', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); // find the parent element cta-button-color and select child button with title=black - await page.click('[data-testid="cta-button-color"] button[title="Black"]'); + // await page.click('[data-testid="cta-button-color"] button[title="Black"]'); + await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'cta-button-color', findByColorTitle: 'Black'}); // check if the button has style="background-color: rgb(0, 0, 0);" expect(await page.getAttribute('[data-testid="cta-button"]', 'style')).toContain('background-color: rgb(0, 0, 0);'); }); @@ -234,9 +224,8 @@ test.describe('Call To Action Card', async () => { test('can change button colour to grey', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); // find the parent element cta-button-color and select child button with title=white - await page.click('[data-testid="cta-button-color"] button[title="Grey"]'); + await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'cta-button-color', findByColorTitle: 'Grey'}); // check if the button has style="background-color: rgb(255, 255, 255);" expect(await page.getAttribute('[data-testid="cta-button"]', 'style')).toContain('background-color: rgb(240, 240, 240);'); }); @@ -244,19 +233,16 @@ test.describe('Call To Action Card', async () => { test('can use colour picker to change button colour', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); - await page.click('button[aria-label="Pick color"]'); - await page.fill('input[aria-label="Color value"]', 'ff0000'); + await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'cta-button-color', customColor: 'ff0000'}); expect(await page.getAttribute('[data-testid="cta-button"]', 'style')).toContain('background-color: rgb(255, 0, 0);'); }); test('button text colour changes with button colour', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); - await page.click('[data-testid="button-settings"]'); await page.fill('[data-testid="button-text"]', 'Click me'); - await page.click('button[aria-label="Pick color"]'); - await page.fill('input[aria-label="Color value"]', 'FFFFFF'); + + await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'cta-button-color', customColor: 'FFFFFF'}); expect(await page.getAttribute('[data-testid="cta-button"]', 'style')).toContain('color: rgb(255, 255, 255);'); // change button colour to black @@ -326,15 +312,18 @@ test.describe('Call To Action Card', async () => { {testId: 'color-picker-green', expectedClass: 'bg-green'}, {testId: 'color-picker-blue', expectedClass: 'bg-blue'}, {testId: 'color-picker-yellow', expectedClass: 'bg-yellow'}, - {testId: 'color-picker-red', expectedClass: 'bg-red'} + {testId: 'color-picker-red', expectedClass: 'bg-red'}, + {testId: 'color-picker-pink', expectedClass: 'bg-pink'}, + {testId: 'color-picker-purple', expectedClass: 'bg-purple'} ]; await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); const firstChildSelector = '[data-kg-card="call-to-action"] > :first-child'; - await expect(page.locator(firstChildSelector)).not.toHaveClass(/bg-(green|blue|yellow|red)/); // shouldn't have any of the classes yet + await expect(page.locator(firstChildSelector)).not.toHaveClass(/bg-(green|blue|yellow|red|pink|purple)/); // shouldn't have any of the classes yet for (const color of colors) { - await page.click(`[data-test-id="${color.testId}"]`); + await page.locator('[data-testid="cta-background-color-picker"] button').click(); + await page.locator(`[data-test-id="${color.testId}"]`).click(); await expect(page.locator(firstChildSelector)).toHaveClass(new RegExp(color.expectedClass)); } }); From a762a4e8cae897fa6f12ead0826bf6882e12ee24 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Thu, 20 Feb 2025 19:01:00 +0900 Subject: [PATCH 06/41] Fixed CTA Node tests --- .../kg-default-nodes/test/nodes/call-to-action.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/kg-default-nodes/test/nodes/call-to-action.test.js b/packages/kg-default-nodes/test/nodes/call-to-action.test.js index 29b8638b03..81520a9f78 100644 --- a/packages/kg-default-nodes/test/nodes/call-to-action.test.js +++ b/packages/kg-default-nodes/test/nodes/call-to-action.test.js @@ -83,11 +83,11 @@ describe('CallToActionNode', function () { callToActionNode.textValue = 'This is a cool advertisement'; callToActionNode.textValue.should.equal('This is a cool advertisement'); - callToActionNode.showButton.should.equal(false); - callToActionNode.showButton = true; callToActionNode.showButton.should.equal(true); + callToActionNode.showButton = false; + callToActionNode.showButton.should.equal(false); - callToActionNode.buttonText.should.equal(''); + callToActionNode.buttonText.should.equal('Learn more'); callToActionNode.buttonText = 'click me'; callToActionNode.buttonText.should.equal('click me'); @@ -99,7 +99,7 @@ describe('CallToActionNode', function () { callToActionNode.sponsorLabel = 'This post is brought to you by our sponsors'; callToActionNode.sponsorLabel.should.equal('This post is brought to you by our sponsors'); - callToActionNode.buttonColor.should.equal(''); + callToActionNode.buttonColor.should.equal('black'); callToActionNode.buttonColor = 'red'; callToActionNode.buttonColor.should.equal('red'); From d691484db983287e9f07e80777201f8f8f3eec9c Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Thu, 20 Feb 2025 19:09:31 +0900 Subject: [PATCH 07/41] Fixed linting --- .../koenig-lexical/src/components/ui/ColorOptionButtons.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx b/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx index 1fa756cfc0..33dd190cd2 100644 --- a/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx @@ -41,8 +41,8 @@ export function ColorOptionButtons({buttons = [], selectedName, onClick}) { label={label} name={name} selectedName={selectedName} - onClick={(name) => { - onClick(name); + onClick={(title) => { + onClick(title); setIsOpen(false); }} /> From 2eaba1d958283845d2c78a0947bc2cc6cfd64f51 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Mon, 24 Feb 2025 15:48:31 +0900 Subject: [PATCH 08/41] Added button color as hex --- .../lib/nodes/call-to-action/CallToActionNode.js | 4 ++-- .../kg-default-nodes/test/nodes/call-to-action.test.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 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 ee22c6b176..4feba96661 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 @@ -11,11 +11,11 @@ export class CallToActionNode extends generateDecoratorNode({ {name: 'showButton', default: true}, {name: 'buttonText', default: 'Learn more'}, {name: 'buttonUrl', default: ''}, - {name: 'buttonColor', default: 'black'}, + {name: 'buttonColor', default: '#000000'}, // Where colour is customisable, we should use hex values {name: 'buttonTextColor', default: ''}, {name: 'hasSponsorLabel', default: true}, {name: 'sponsorLabel', default: '

    SPONSORED

    '}, - {name: 'backgroundColor', default: 'grey'}, + {name: 'backgroundColor', default: 'grey'}, // Since this is one of a few fixed options, we stick to colour names. {name: 'hasImage', default: false}, {name: 'imageUrl', default: ''}, {name: 'imageWidth', default: null}, diff --git a/packages/kg-default-nodes/test/nodes/call-to-action.test.js b/packages/kg-default-nodes/test/nodes/call-to-action.test.js index 81520a9f78..4841bbfce3 100644 --- a/packages/kg-default-nodes/test/nodes/call-to-action.test.js +++ b/packages/kg-default-nodes/test/nodes/call-to-action.test.js @@ -99,9 +99,9 @@ describe('CallToActionNode', function () { callToActionNode.sponsorLabel = 'This post is brought to you by our sponsors'; callToActionNode.sponsorLabel.should.equal('This post is brought to you by our sponsors'); - callToActionNode.buttonColor.should.equal('black'); - callToActionNode.buttonColor = 'red'; - callToActionNode.buttonColor.should.equal('red'); + callToActionNode.buttonColor.should.equal('#000000'); + callToActionNode.buttonColor = '#ffffff'; + callToActionNode.buttonColor.should.equal('#ffffff'); callToActionNode.buttonTextColor.should.equal(''); callToActionNode.buttonTextColor = 'black'; @@ -112,8 +112,8 @@ describe('CallToActionNode', function () { callToActionNode.hasSponsorLabel.should.equal(false); callToActionNode.backgroundColor.should.equal('grey'); - callToActionNode.backgroundColor = '#654321'; - callToActionNode.backgroundColor.should.equal('#654321'); + callToActionNode.backgroundColor = 'red'; + callToActionNode.backgroundColor.should.equal('red'); callToActionNode.hasImage.should.equal(false); callToActionNode.hasImage = true; From cc9490a5673de0fb74fc900b788f4a5cc1f2160e Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Mon, 24 Feb 2025 16:55:59 +0900 Subject: [PATCH 09/41] Added drag drop to CTA card img button --- .../components/ui/cards/CallToActionCard.jsx | 11 +++++-- .../src/components/ui/cards/SignupCard.jsx | 30 +++++++------------ .../src/nodes/CallToActionNodeComponent.jsx | 8 ++++- .../test/e2e/cards/cta-card.test.js | 19 +++++++++++- 4 files changed, 45 insertions(+), 23 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx index 5d3abc7c80..09882bfd11 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx @@ -105,7 +105,8 @@ export function CallToActionCard({ updateHasSponsorLabel = () => {}, updateLayout = () => {}, updateShowButton = () => {}, - toggleVisibility = () => {} + toggleVisibility = () => {}, + imageDragHandler = () => {} }) { const [buttonColorPickerExpanded, setButtonColorPickerExpanded] = useState(false); @@ -163,8 +164,11 @@ export function CallToActionCard({ borderStyle={'rounded'} desc='Upload' icon='file' + isDraggedOver={imageDragHandler.isDraggedOver} + isLoading={imageDragHandler.isLoading} label='Image' mimeTypes={['image/*']} + placeholderRef={imageDragHandler.setRef} setFileInputRef={setFileInputRef} src={imageSrc} type='button' @@ -278,6 +282,7 @@ export function CallToActionCard({ layout === 'immersive' ? 'h-auto w-full' : 'aspect-square w-16 object-cover', 'rounded-md' )} + data-testId="cta-card-image" src={imageSrc} />
    @@ -367,7 +372,8 @@ CallToActionCard.propTypes = { sponsorLabelHtmlEditor: PropTypes.object, sponsorLabelHtmlEditorInitialState: PropTypes.object, visibilityOptions: PropTypes.object, - toggleVisibility: PropTypes.func + toggleVisibility: PropTypes.func, + imageUploadHandler: PropTypes.func }; CallToActionCard.defaultProps = { @@ -389,6 +395,7 @@ CallToActionCard.defaultProps = { onFileChange: () => {}, setFileInputRef: () => {}, onRemoveMedia: () => {}, + imageDragHandler: () => {}, visibilityOptions: PropTypes.object, toggleVisibility: PropTypes.func }; diff --git a/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx b/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx index 7443f04516..1b35cb63a6 100644 --- a/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx @@ -1,6 +1,5 @@ import CenterAlignIcon from '../../../assets/icons/kg-align-center.svg?react'; import ExpandIcon from '../../../assets/icons/kg-expand.svg?react'; -import ImgBgIcon from '../../../assets/icons/kg-img-bg.svg?react'; import ImgFullIcon from '../../../assets/icons/kg-img-full.svg?react'; import ImgRegularIcon from '../../../assets/icons/kg-img-regular.svg?react'; import ImgWideIcon from '../../../assets/icons/kg-img-wide.svg?react'; @@ -16,10 +15,10 @@ import {ButtonGroupSetting, ColorPickerSetting, InputSetting, MediaUploadSetting import {Color, textColorForBackgroundColor} from '@tryghost/color-utils'; import {FastAverageColor} from 'fast-average-color'; import {IconButton} from '../IconButton'; +import {ImageUploadSwatch} from '../ImageUploadSwatch'; import {MediaUploader} from '../MediaUploader'; import {ReadOnlyOverlay} from '../ReadOnlyOverlay'; import {SubscribeForm} from '../SubscribeForm'; -import {Tooltip} from '../Tooltip'; import {getAccentColor} from '../../../utils/getAccentColor'; import {isEditorEmpty} from '../../../utils/isEditorEmpty'; @@ -208,6 +207,12 @@ export function SignupCard({alignment, const correctedBackgroundSize = backgroundSize === 'contain' && backgroundImageSrc ? 'contain' : 'cover'; + const onBackgroundImageClickHandler = () => { + handleShowBackgroundImage(); + setBackgroundColorPickerExpanded(true); + setButtonColorPickerExpanded(false); + }; + return ( <>
    @@ -403,23 +408,10 @@ export function SignupCard({alignment, (layout !== 'split' && { title: 'Image', customContent: ( - + ) }), {title: 'Grey', hex: '#F0F0F0'}, diff --git a/packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx b/packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx index b441153870..90514e91c0 100644 --- a/packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx +++ b/packages/koenig-lexical/src/nodes/CallToActionNodeComponent.jsx @@ -1,6 +1,7 @@ import CardContext from '../context/CardContext'; import KoenigComposerContext from '../context/KoenigComposerContext.jsx'; import React, {useRef} from 'react'; +import useFileDragAndDrop from '../hooks/useFileDragAndDrop'; import {$getNodeByKey} from 'lexical'; import {ActionToolbar} from '../components/ui/ActionToolbar.jsx'; import {CallToActionCard} from '../components/ui/cards/CallToActionCard.jsx'; @@ -25,7 +26,6 @@ export const CallToActionNodeComponent = ({ htmlEditor, htmlEditorInitialState, buttonTextColor, - href, sponsorLabelHtmlEditor, sponsorLabelHtmlEditorInitialState }) => { @@ -33,6 +33,7 @@ export const CallToActionNodeComponent = ({ const {isEditing, isSelected, setEditing} = React.useContext(CardContext); const {fileUploader, cardConfig} = React.useContext(KoenigComposerContext); const [showSnippetToolbar, setShowSnippetToolbar] = React.useState(false); + const imageDragHandler = useFileDragAndDrop({handleDrop: handleImageDrop}); const {visibilityOptions, toggleVisibility} = useVisibilityToggle(editor, nodeKey, cardConfig); @@ -127,6 +128,10 @@ export const CallToActionNodeComponent = ({ }); }; + async function handleImageDrop(files) { + await handleImageChange(files); + } + React.useEffect(() => { htmlEditor.setEditable(isEditing); }, [isEditing, htmlEditor]); @@ -145,6 +150,7 @@ export const CallToActionNodeComponent = ({ hasSponsorLabel={hasSponsorLabel} htmlEditor={htmlEditor} htmlEditorInitialState={htmlEditorInitialState} + imageDragHandler={imageDragHandler} imageSrc={imageUrl} imageUploader={imageUploader} isEditing={isEditing} diff --git a/packages/koenig-lexical/test/e2e/cards/cta-card.test.js b/packages/koenig-lexical/test/e2e/cards/cta-card.test.js index 8f471bf451..4963f1fddb 100644 --- a/packages/koenig-lexical/test/e2e/cards/cta-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/cta-card.test.js @@ -1,5 +1,5 @@ import path from 'path'; -import {assertHTML, focusEditor, getEditorStateJSON, html, initialize, insertCard} from '../../utils/e2e'; +import {assertHTML, createDataTransfer, focusEditor, getEditorStateJSON, html, initialize, insertCard} from '../../utils/e2e'; import {cardBackgroundColorSettings} from '../../utils/background-color-helper'; import {expect, test} from '@playwright/test'; import {fileURLToPath} from 'url'; @@ -348,6 +348,23 @@ test.describe('Call To Action Card', async () => { await expect(imgLocator).not.toBeVisible(); }); + test('can drag and drop image over upload button', async function () { + const filePath = path.relative(process.cwd(), __dirname + '/../fixtures/large-image.png'); + await focusEditor(page); + await insertCard(page, {cardName: 'call-to-action'}); + + // Create and dispatch data transfer + const dataTransfer = await createDataTransfer(page, [{filePath, fileName: 'large-image.png', fileType: 'image/png'}]); + await page.getByTestId('media-upload-placeholder').dispatchEvent('dragover', {dataTransfer}); + // Dragover text should be visible + // check that "Drop it like it's hot" is visible + await expect(await page.locator('[data-kg-card-drag-text="true"]')).toBeVisible(); + + await page.getByTestId('media-upload-placeholder').dispatchEvent('drop', {dataTransfer}); + + await expect (await page.getByTestId('cta-card-image')).toBeVisible(); + }); + test('default layout is minimal', async function () { await focusEditor(page); await insertCard(page, {cardName: 'call-to-action'}); From 32a3ee992a58fde1a0be819c25da3827d29960cf Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 25 Feb 2025 13:10:33 +0900 Subject: [PATCH 10/41] Added img icon to color picker --- .../koenig-lexical/src/components/ui/ColorPicker.jsx | 9 +++++++++ .../src/components/ui/cards/SignupCard.jsx | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx index efc6da9379..2aae69f383 100644 --- a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx @@ -1,4 +1,5 @@ import EyedropperIcon from '../../assets/icons/kg-eyedropper.svg?react'; +import ImgBgIcon from '../../assets/icons/kg-img-bg.svg?react'; import React, {Fragment, useCallback, useEffect, useRef, useState} from 'react'; import clsx from 'clsx'; import {Button} from './Button'; @@ -181,6 +182,9 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, if (value === 'accent') { backgroundColor = getAccentColor(); selectedSwatch = swatches.find(swatch => swatch.accent)?.title; + } else if (value === 'image') { + backgroundColor = 'transparent'; + selectedSwatch = swatches.find(swatch => swatch.image)?.title; } else if (value === 'transparent') { backgroundColor = 'white'; selectedSwatch = swatches.find(swatch => swatch.transparent)?.title; @@ -219,6 +223,11 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, className="block size-full rounded-full border-2 border-white" style={{backgroundColor}} > + { + value === 'image' && ( + + ) + } {value === 'transparent' &&
    } diff --git a/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx b/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx index 1b35cb63a6..78fda88ef3 100644 --- a/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx @@ -418,7 +418,7 @@ export function SignupCard({alignment, {title: 'Black', hex: '#000000'}, {title: 'Brand color', accent: true} ].filter(Boolean)} - value={(showBackgroundImage && layout !== 'split') ? '' : backgroundColor} + value={(showBackgroundImage && layout !== 'split') ? 'image' : backgroundColor} onPickerChange={color => handleBackgroundColor(color, matchingTextColor(color))} onSwatchChange={(color) => { handleBackgroundColor(color, matchingTextColor(color)); From efc5d4aeefe92bb73184697c6b3f2873f40bc720 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 25 Feb 2025 13:35:45 +0900 Subject: [PATCH 11/41] Revert "Added img icon to color picker" This reverts commit 32a3ee992a58fde1a0be819c25da3827d29960cf. --- .../koenig-lexical/src/components/ui/ColorPicker.jsx | 9 --------- .../src/components/ui/cards/SignupCard.jsx | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx index 2aae69f383..efc6da9379 100644 --- a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx @@ -1,5 +1,4 @@ import EyedropperIcon from '../../assets/icons/kg-eyedropper.svg?react'; -import ImgBgIcon from '../../assets/icons/kg-img-bg.svg?react'; import React, {Fragment, useCallback, useEffect, useRef, useState} from 'react'; import clsx from 'clsx'; import {Button} from './Button'; @@ -182,9 +181,6 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, if (value === 'accent') { backgroundColor = getAccentColor(); selectedSwatch = swatches.find(swatch => swatch.accent)?.title; - } else if (value === 'image') { - backgroundColor = 'transparent'; - selectedSwatch = swatches.find(swatch => swatch.image)?.title; } else if (value === 'transparent') { backgroundColor = 'white'; selectedSwatch = swatches.find(swatch => swatch.transparent)?.title; @@ -223,11 +219,6 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, className="block size-full rounded-full border-2 border-white" style={{backgroundColor}} > - { - value === 'image' && ( - - ) - } {value === 'transparent' &&
    } diff --git a/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx b/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx index 78fda88ef3..1b35cb63a6 100644 --- a/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx @@ -418,7 +418,7 @@ export function SignupCard({alignment, {title: 'Black', hex: '#000000'}, {title: 'Brand color', accent: true} ].filter(Boolean)} - value={(showBackgroundImage && layout !== 'split') ? 'image' : backgroundColor} + value={(showBackgroundImage && layout !== 'split') ? '' : backgroundColor} onPickerChange={color => handleBackgroundColor(color, matchingTextColor(color))} onSwatchChange={(color) => { handleBackgroundColor(color, matchingTextColor(color)); From 62adaf27bf725d5580b73dfc9930d31df690dd0a Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 25 Feb 2025 13:57:16 +0900 Subject: [PATCH 12/41] Fixed swatch test --- .../koenig-lexical/src/components/ui/ImageUploadSwatch.jsx | 5 +++-- .../koenig-lexical/src/components/ui/cards/SignupCard.jsx | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ImageUploadSwatch.jsx b/packages/koenig-lexical/src/components/ui/ImageUploadSwatch.jsx index 45025ad7ee..114cb672ec 100644 --- a/packages/koenig-lexical/src/components/ui/ImageUploadSwatch.jsx +++ b/packages/koenig-lexical/src/components/ui/ImageUploadSwatch.jsx @@ -4,7 +4,8 @@ import {Tooltip} from './Tooltip'; export const ImageUploadSwatch = ({ showBackgroundImage, - onClickHandler + onClickHandler, + dataTestId }) => { return ( diff --git a/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx b/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx index e72f8693ce..3a6c7e8b41 100644 --- a/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/SignupCard.jsx @@ -419,7 +419,7 @@ export function SignupCard({alignment, {title: 'Black', hex: '#000000'}, {title: 'Brand color', accent: true} ].filter(Boolean)} - value={(showBackgroundImage && layout !== 'split') ? '' : backgroundColor} + value={(showBackgroundImage && layout !== 'split') ? 'image' : backgroundColor} onPickerChange={color => handleBackgroundColor(color, matchingTextColor(color))} onSwatchChange={(color) => { handleBackgroundColor(color, matchingTextColor(color)); From 51f4a98ff9c2616ccb2bae82d5518ee46be3bd87 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 25 Feb 2025 13:59:25 +0900 Subject: [PATCH 14/41] Added test for finding img icon --- .../test/e2e/cards/signup-card.test.js | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/koenig-lexical/test/e2e/cards/signup-card.test.js b/packages/koenig-lexical/test/e2e/cards/signup-card.test.js index 802a91e44a..b40fc6f8d8 100644 --- a/packages/koenig-lexical/test/e2e/cards/signup-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/signup-card.test.js @@ -224,11 +224,6 @@ test.describe('Signup card', async () => { await focusEditor(page); await insertCard(page, {cardName: 'signup'}); - // await page.click('[data-testid="signup-background-color"] [aria-label="Pick color"]'); - - // await page.fill('[data-testid="signup-background-color"] input', ''); - // await page.keyboard.type('ff0000'); - await cardBackgroundColorSettings(page, { customColor: 'ff0000', cardColorPickerTestId: 'signup-background-color' @@ -286,6 +281,37 @@ test.describe('Signup card', async () => { await expect(page.locator('[data-testid="media-upload-filled"] img')).toHaveAttribute('src', /blob:/); }); + test('has image icon when background image is selected', async function (){ + const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); + + await focusEditor(page); + await insertCard(page, {cardName: 'signup'}); + + const fileChooserPromise = page.waitForEvent('filechooser'); + + await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); + await page.click('[data-testid="media-upload-placeholder"]'); + + // Set files + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles([filePath]); + + // Check if it is set as a background image + await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-image', /blob:/); + + // Check if it is also set as an image in the panel + await expect(page.locator('[data-testid="media-upload-filled"] img')).toHaveAttribute('src', /blob:/); + + const parentLocator = page.locator('[data-testid="signup-background-color"]'); + + // Check if it contains the exact element + const hasPath = await parentLocator.evaluate((parent) => { + return !!parent.querySelector(`path[fill="currentColor"][fill-rule="evenodd"][clip-rule="evenodd"][d="M22.883 19.771a.786.786 0 0 1-.668.372H1.785a.786.786 0 0 1-.666-1.202l3.93-6.286a.785.785 0 0 1 1.269-.086l3.292 3.95 6.476-8.633a.81.81 0 0 1 .7-.315.786.786 0 0 1 .628.431l5.5 11a.785.785 0 0 1-.03.769Z"]`); + }); + + expect(hasPath).toBe(true); + }); + test('can switch between background image and color', async function () { const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); From 49b1443d241126e02e3d74806b5be5699fa9a974 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 25 Feb 2025 14:22:44 +0900 Subject: [PATCH 15/41] Added image icon on Header --- .../ui/cards/HeaderCard/v2/HeaderCard.jsx | 1 + .../test/e2e/cards/header-card.test.js | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) 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 109fbd6cb6..8bb356d16d 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 @@ -411,6 +411,7 @@ export function HeaderCard({alignment, title: 'Image', customContent: ( diff --git a/packages/koenig-lexical/test/e2e/cards/header-card.test.js b/packages/koenig-lexical/test/e2e/cards/header-card.test.js index 6e8f750fed..ff200b3884 100644 --- a/packages/koenig-lexical/test/e2e/cards/header-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/header-card.test.js @@ -609,7 +609,33 @@ test.describe('Header card V2', () => { await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); await expect(page.locator('[data-testid="media-upload-setting"]')).not.toBeVisible(); }); + + test('has image icon when background image is selected', async function (){ + const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); + await createHeaderCard({page, version: 2}); + + // Choose an image + const fileChooserPromise = page.waitForEvent('filechooser'); + + await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'header-background-color', imageUploadId: 'header-background-image-toggle'}); + await page.click('[data-testid="media-upload-placeholder"]'); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles([filePath]); + + // Check if it is set as a background image + // Check if it is also set as an image in the panel + await expect(page.locator('[data-testid="media-upload-filled"] img')).toHaveAttribute('src', /blob:/); + + const parentLocator = page.locator('[data-testid="header-background-color"]'); + + // Check if it contains the exact element + const hasPath = await parentLocator.evaluate((parent) => { + return !!parent.querySelector(`path[fill="currentColor"][fill-rule="evenodd"][clip-rule="evenodd"][d="M22.883 19.771a.786.786 0 0 1-.668.372H1.785a.786.786 0 0 1-.666-1.202l3.93-6.286a.785.785 0 0 1 1.269-.086l3.292 3.95 6.476-8.633a.81.81 0 0 1 .7-.315.786.786 0 0 1 .628.431l5.5 11a.785.785 0 0 1-.03.769Z"]`); + }); + + expect(hasPath).toBe(true); + }); test('can add and remove background image in split layout', async function () { const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); const fileChooserPromise = page.waitForEvent('filechooser'); From d5b958dc59ef1e8b324db0cd3e42cf7aa52b6d06 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 25 Feb 2025 14:30:49 +0900 Subject: [PATCH 16/41] Updated proptypes --- .../src/components/ui/cards/CallToActionCard.jsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx index 09882bfd11..75da8d29a0 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx @@ -373,7 +373,8 @@ CallToActionCard.propTypes = { sponsorLabelHtmlEditorInitialState: PropTypes.object, visibilityOptions: PropTypes.object, toggleVisibility: PropTypes.func, - imageUploadHandler: PropTypes.func + imageUploadHandler: PropTypes.func, + imageDragHandler: PropTypes.func }; CallToActionCard.defaultProps = { From 09bb0249bcc45b9167e5b22e70f51e91ce1afeb7 Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 25 Feb 2025 15:24:07 +0900 Subject: [PATCH 17/41] Added image string to headercard --- .../src/components/ui/cards/HeaderCard/v2/HeaderCard.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8bb356d16d..3336c1b826 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 @@ -421,7 +421,7 @@ export function HeaderCard({alignment, {title: 'Grey', hex: '#F0F0F0'}, {title: 'Brand color', accent: true} ].filter(Boolean)} - value={(showBackgroundImage && layout !== 'split') ? '' : backgroundColor} + value={(showBackgroundImage && layout !== 'split') ? 'image' : backgroundColor} onPickerChange={color => handleBackgroundColor(color, matchingTextColor(color))} onSwatchChange={(color) => { handleBackgroundColor(color, matchingTextColor(color)); From 3cc4b08d57df896dde3590df707bc9e1ade4f44c Mon Sep 17 00:00:00 2001 From: Ronald Langeveld Date: Tue, 25 Feb 2025 15:39:14 +0900 Subject: [PATCH 18/41] Improved button icon test --- .../koenig-lexical/src/components/ui/ColorPicker.jsx | 2 +- .../koenig-lexical/test/e2e/cards/header-card.test.js | 11 ++++------- .../koenig-lexical/test/e2e/cards/signup-card.test.js | 11 ++++------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx index 2aae69f383..d4c60a8e59 100644 --- a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx @@ -200,7 +200,7 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, }; return ( -
    +
    @@ -371,32 +371,8 @@ CallToActionCard.propTypes = { onRemoveMedia: PropTypes.func, sponsorLabelHtmlEditor: PropTypes.object, sponsorLabelHtmlEditorInitialState: PropTypes.object, - visibilityOptions: PropTypes.object, + visibilityOptions: PropTypes.array, toggleVisibility: PropTypes.func, imageUploadHandler: PropTypes.func, - imageDragHandler: PropTypes.func -}; - -CallToActionCard.defaultProps = { - buttonText: '', - buttonUrl: '', - buttonColor: '', - buttonTextColor: '', - color: 'none', - hasSponsorLabel: false, - imageSrc: '', - isEditing: false, - layout: 'immersive', - showButton: true, - updateHasSponsorLabel: () => {}, - updateShowButton: () => {}, - updateLayout: () => {}, - handleColorChange: () => {}, - handleButtonColor: () => {}, - onFileChange: () => {}, - setFileInputRef: () => {}, - onRemoveMedia: () => {}, - imageDragHandler: () => {}, - visibilityOptions: PropTypes.object, - toggleVisibility: PropTypes.func + imageDragHandler: PropTypes.object }; From 3ea83f263887735fd1972473d78e32ff577e5767 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Tue, 25 Feb 2025 18:10:03 +0000 Subject: [PATCH 20/41] Cleaned up repetition in `` - large parts of the component were repeated in order to surround varying contents - extracted contents to separate children to make behaviour clearer - improved classes assignment to remove duplication of the button's class list --- .../src/components/ui/MediaPlaceholder.jsx | 129 +++++++++--------- 1 file changed, 62 insertions(+), 67 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx b/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx index 48354f7790..9d57dfb05a 100644 --- a/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx @@ -18,7 +18,7 @@ export const PLACEHOLDER_ICONS = { }; export const CardText = ({text, type}) => ( - ( ); +const ButtonContents = ({desc, hasErrors}) => { + if (hasErrors) { + return; + } + return

    {desc}

    ; +}; + +const StandardContents = ({desc, hasErrors, icon, size}) => { + if (size === 'xsmall' && hasErrors) { + return; + } + + const Icon = PLACEHOLDER_ICONS[icon]; + + 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' + ); + + return <> + +

    {desc}

    + ; +}; + export function MediaPlaceholder({ desc, icon, filePicker, size, type, - borderStyle, + borderStyle = 'squared', isDraggedOver, errors = [], placeholderRef, @@ -44,8 +79,6 @@ export function MediaPlaceholder({ multiple = false, ...props }) { - 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', @@ -55,25 +88,10 @@ export function MediaPlaceholder({ 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' + type === 'button' && 'px-3 py-1', + type !== 'button' && (size === 'xsmall' ? 'p-4' : 'flex-col p-20') ); const errorClasses = clsx( @@ -81,6 +99,16 @@ export function MediaPlaceholder({ size !== 'xsmall' && 'mt-3 max-w-[65%]' ); + const errorMessages = errors.map(error => ( + + {error.message} + + )); + return (
    ) : ( - <> - {type === 'button' ? ( - - ) : ( - - )} - + )}
    From c9f3dbe6a59b6a425ce740d60cbc69d469f3cfde Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Tue, 25 Feb 2025 21:52:11 +0000 Subject: [PATCH 21/41] Renamed call-to-action card test file - match node/component naming --- .../e2e/cards/{cta-card.test.js => call-to-action-card.test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/koenig-lexical/test/e2e/cards/{cta-card.test.js => call-to-action-card.test.js} (100%) diff --git a/packages/koenig-lexical/test/e2e/cards/cta-card.test.js b/packages/koenig-lexical/test/e2e/cards/call-to-action-card.test.js similarity index 100% rename from packages/koenig-lexical/test/e2e/cards/cta-card.test.js rename to packages/koenig-lexical/test/e2e/cards/call-to-action-card.test.js From 60cb43a437745867914f86ba4afba179301b70df Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Tue, 25 Feb 2025 21:54:18 +0000 Subject: [PATCH 22/41] Fixed errors coming from useMoveable - in some cases the hook's callbacks would be called when the ref'd element had already been removed, added guard to avoid trying to set style in that case --- packages/koenig-lexical/src/hooks/useMovable.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/koenig-lexical/src/hooks/useMovable.js b/packages/koenig-lexical/src/hooks/useMovable.js index 720246ebfe..b13149b592 100644 --- a/packages/koenig-lexical/src/hooks/useMovable.js +++ b/packages/koenig-lexical/src/hooks/useMovable.js @@ -125,12 +125,16 @@ export default function useMovable({adjustOnResize, adjustOnDrag} = {}) { // preventing clicks stops any event handlers that may otherwise result in the // movable element being closed when the drag finishes const disablePointerEvents = useCallback(() => { - ref.current.style.pointerEvents = 'none'; + if (ref.current) { + ref.current.style.pointerEvents = 'none'; + } window.addEventListener('click', cancelClick, {capture: true, passive: false}); }, [ref, cancelClick]); const enablePointerEvents = useCallback(() => { - ref.current.style.pointerEvents = ''; + if (ref.current) { + ref.current.style.pointerEvents = ''; + } window.removeEventListener('click', cancelClick, {capture: true, passive: false}); }, [ref, cancelClick]); From acc7d53c99b2bc95d14a8727d87518a2297f6599 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Tue, 25 Feb 2025 22:12:00 +0000 Subject: [PATCH 23/41] Fixed background color popover not closing on click outside - interacting with other inputs or parts of the page did not close the color options popover as expected - added an effect to add a window-level event capture in order to close the popover on any mousedown - does not prevent events on the clicked item from firing as it could lead to the UI feeling unresponsive --- .../src/components/ui/ColorOptionButtons.jsx | 24 +++++++++++++++++-- .../components/ui/cards/CallToActionCard.jsx | 2 +- .../e2e/cards/call-to-action-card.test.js | 16 +++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx b/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx index 33dd190cd2..fc104f7fb9 100644 --- a/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx @@ -5,12 +5,31 @@ import {usePreviousFocus} from '../../hooks/usePreviousFocus'; export function ColorOptionButtons({buttons = [], selectedName, onClick}) { const [isOpen, setIsOpen] = useState(false); + const componentRef = React.useRef(null); + const selectedButton = buttons.find(button => button.name === selectedName); + // Close the swatch popover when clicking outside of it + React.useEffect(() => { + if (!isOpen) { + return; + } + + const handleClickOutside = (event) => { + if (componentRef.current && !componentRef.current.contains(event.target)) { + setIsOpen(false); + } + }; + + window.addEventListener('mousedown', handleClickOutside, {capture: true}); + return () => window.removeEventListener('mousedown', handleClickOutside, {capture: true}); + }, [isOpen, setIsOpen]); + return ( -
    +
    {isOpen && ( -
    {showColorPicker && ( -
    {swatches.map(({customContent, ...swatch}) => ( - customContent ? - {customContent} : - {customContent} : + { onSwatchChange(val); setShowColorPicker(false); - // setIsOpen(false); - }} - {...swatch} + }} + {...swatch} /> ))}
    + ); +} + +export function IconButton({dataTestId, onClick, label, name, selectedName, Icon}) { + const isActive = name === selectedName; + + const {handleMousedown, handleClick} = usePreviousFocus(onClick, name); + + return ( +
  • + +
  • + ); +} + +ButtonGroupBeta.propTypes = { + selectedName: PropTypes.oneOf(['regular', 'wide', 'full', 'split', 'center', 'left', 'small', 'medium', 'large', 'grid', 'list', 'minimal', 'immersive']) +}; diff --git a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.stories.jsx b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.stories.jsx new file mode 100644 index 0000000000..28209e6657 --- /dev/null +++ b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.stories.jsx @@ -0,0 +1,48 @@ +import ImgFullIcon from '../../assets/icons/kg-img-full.svg?react'; +import ImgRegularIcon from '../../assets/icons/kg-img-regular.svg?react'; +import ImgWideIcon from '../../assets/icons/kg-img-wide.svg?react'; +import React from 'react'; +import {ButtonGroupBeta, IconButton} from './ButtonGroupBeta'; + +const story = { + title: 'Generic/Button group (beta)', + component: ButtonGroupBeta, + subcomponents: {IconButton}, + parameters: { + status: { + type: 'functional' + } + }, + argTypes: { + selectedName: {control: 'select', options: ['regular', 'wide', 'full']} + } +}; +export default story; + +const Template = (args) => { + return ( + + ); +}; + +export const CardWidth = Template.bind({}); +CardWidth.args = { + selectedName: 'regular', + buttons: [ + { + label: 'Regular', + name: 'regular', + Icon: ImgRegularIcon + }, + { + label: 'Wide', + name: 'wide', + Icon: ImgWideIcon + }, + { + label: 'Full', + name: 'full', + Icon: ImgFullIcon + } + ] +}; diff --git a/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx b/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx index a87fc620e0..595eda9aa8 100644 --- a/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorOptionButtons.jsx @@ -1,70 +1,31 @@ import PlusIcon from '../../assets/icons/plus.svg?react'; -import React, {useState} from 'react'; +import React from 'react'; import {Tooltip} from './Tooltip'; -import {useClickOutside} from '../../hooks/useClickOutside'; import {usePreviousFocus} from '../../hooks/usePreviousFocus'; export function ColorOptionButtons({buttons = [], selectedName, onClick}) { - const [isOpen, setIsOpen] = useState(false); - const componentRef = React.useRef(null); - - const selectedButton = buttons.find(button => button.name === selectedName); - - // Close the swatch popover when clicking outside of it - useClickOutside(isOpen, componentRef, () => setIsOpen(false)); - return ( -
    - +
    +
      + {buttons.map(({label, name, color}) => ( + name !== 'image' ? + + : +
    • onClick(name)}> + + + +
    • - {/* Color options popover */} - {isOpen && ( -
      -
      -
        - {buttons.map(({label, name, color}) => ( - name !== 'image' ? - { - onClick(title); - setIsOpen(false); - }} - /> - : -
      • onClick(name)}> - - - -
      • - ))} -
      -
      -
      - )} + ))} +
    ); } @@ -74,7 +35,7 @@ export function ColorButton({onClick, label, name, color, selectedName}) { const {handleMousedown, handleClick} = usePreviousFocus(onClick, name); return ( -
  • +
  • + + {/* Color options popover */} + {isOpen && ( +
    +
    +
      + {buttons.map(({label, name, color}) => ( + name !== 'image' ? + { + onClick(title); + setIsOpen(false); + }} + /> + : +
    • onClick(name)}> + + + +
    • + ))} +
    +
    +
    + )} +
  • + ); +} + +export function ColorButton({onClick, label, name, color, selectedName}) { + const isActive = name === selectedName; + + const {handleMousedown, handleClick} = usePreviousFocus(onClick, name); + return ( +
  • + +
  • + ); +} diff --git a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx index 3aa8f596c5..288889701d 100644 --- a/packages/koenig-lexical/src/components/ui/ColorPicker.jsx +++ b/packages/koenig-lexical/src/components/ui/ColorPicker.jsx @@ -1,14 +1,12 @@ import EyedropperIcon from '../../assets/icons/kg-eyedropper.svg?react'; -import ImgBgIcon from '../../assets/icons/kg-img-bg.svg?react'; -import React, {Fragment, useCallback, useEffect, useRef, useState} from 'react'; +import React, {Fragment, useCallback, useEffect, useRef} from 'react'; import clsx from 'clsx'; import {Button} from './Button'; import {HexColorInput, HexColorPicker} from 'react-colorful'; import {Tooltip} from './Tooltip'; import {getAccentColor} from '../../utils/getAccentColor'; -import {useClickOutside} from '../../hooks/useClickOutside'; -export function ColorPicker({value, eyedropper, hasTransparentOption, onChange, children}) { +export function ColorPicker({value, eyedropper, hasTransparentOption, onChange}) { // HexColorInput doesn't support adding a ref on the input itself const inputWrapperRef = useRef(null); @@ -78,7 +76,7 @@ export function ColorPicker({value, eyedropper, hasTransparentOption, onChange, }, []); return ( -
    +
    @@ -96,7 +94,6 @@ export function ColorPicker({value, eyedropper, hasTransparentOption, onChange,
    {hasTransparentOption &&
    ); @@ -137,36 +134,12 @@ function ColorSwatch({hex, accent, transparent, title, isSelected, onSelect}) { ); } -export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, onChange, isExpanded, eyedropper, hasTransparentOption, children}) { - const [isOpen, setIsOpen] = useState(false); - const [showColorPicker, setShowColorPicker] = useState(false); - const [showChildren, setShowChildren] = useState(false); - const popoverRef = useRef(null); - - useClickOutside(isOpen, popoverRef, () => setIsOpen(false)); - - const stopPropagation = useCallback((e) => { - e.stopPropagation(); - e.preventDefault(); - }, []); - - useEffect(() => { - if (isExpanded) { - setIsOpen(true); - setShowChildren(true); - setShowColorPicker(false); - } - }, [isExpanded]); - +export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, isExpanded}) { let backgroundColor = value; let selectedSwatch = swatches.find(swatch => swatch.hex === value)?.title; - if (value === 'accent') { backgroundColor = getAccentColor(); selectedSwatch = swatches.find(swatch => swatch.accent)?.title; - } else if (value === 'image') { - backgroundColor = 'transparent'; - selectedSwatch = swatches.find(swatch => swatch.image)?.title; } else if (value === 'transparent') { backgroundColor = 'white'; selectedSwatch = swatches.find(swatch => swatch.transparent)?.title; @@ -176,115 +149,22 @@ export function ColorIndicator({value, swatches, onSwatchChange, onTogglePicker, selectedSwatch = null; } - const handleColorPickerChange = (newValue) => { - onChange(newValue); - // Don't close the popover when using the color picker - }; - return ( -
    - - - {isOpen && ( -
    - {showColorPicker && ( - - )} - {showChildren && children} -
    -
    - {swatches.map(({customContent, ...swatch}) => ( - customContent ? - {customContent} : - { - onSwatchChange(val); - setShowColorPicker(false); - }} - {...swatch} - /> - ))} -
    - -
    -
    - )}
    ); } diff --git a/packages/koenig-lexical/src/components/ui/ColorPickerBeta.jsx b/packages/koenig-lexical/src/components/ui/ColorPickerBeta.jsx new file mode 100644 index 0000000000..73d5d62095 --- /dev/null +++ b/packages/koenig-lexical/src/components/ui/ColorPickerBeta.jsx @@ -0,0 +1,290 @@ +import EyedropperIcon from '../../assets/icons/kg-eyedropper.svg?react'; +import ImgBgIcon from '../../assets/icons/kg-img-bg.svg?react'; +import React, {Fragment, useCallback, useEffect, useRef, useState} from 'react'; +import clsx from 'clsx'; +import {Button} from './Button'; +import {HexColorInput, HexColorPicker} from 'react-colorful'; +import {Tooltip} from './Tooltip'; +import {getAccentColor} from '../../utils/getAccentColor'; +import {useClickOutside} from '../../hooks/useClickOutside'; + +export function ColorPickerBeta({value, eyedropper, hasTransparentOption, onChange, children}) { + // HexColorInput doesn't support adding a ref on the input itself + const inputWrapperRef = useRef(null); + + const stopPropagation = useCallback((e) => { + e.stopPropagation(); + + const inputElement = inputWrapperRef.current?.querySelector('input'); + const isInputField = e.target === inputElement; + + // Allow text selection for events on the input field + if (isInputField) { + return; + } + + // Prevent closing the color picker when clicking somewhere inside it + inputWrapperRef.current?.querySelector('input')?.focus(); + + e.preventDefault(); + }, []); + + const isUsingColorPicker = useRef(false); + + const stopUsingColorPicker = useCallback(() => { + isUsingColorPicker.current = false; + inputWrapperRef.current?.querySelector('input')?.focus(); + + document.removeEventListener('mouseup', stopUsingColorPicker); + document.removeEventListener('touchend', stopUsingColorPicker); + }, []); + + const startUsingColorPicker = useCallback(() => { + isUsingColorPicker.current = true; + + document.addEventListener('mouseup', stopUsingColorPicker); + document.addEventListener('touchend', stopUsingColorPicker); + }, [stopUsingColorPicker]); + + const openColorPicker = useCallback((e) => { + e.preventDefault(); + + isUsingColorPicker.current = true; + document.body.style.setProperty('pointer-events', 'none'); + + const eyeDropper = new window.EyeDropper(); + eyeDropper.open() + .then(result => onChange(result.sRGBHex)) + .finally(() => { + isUsingColorPicker.current = false; + document.body.style.removeProperty('pointer-events'); + inputWrapperRef.current?.querySelector('input')?.focus(); + }); + }, [onChange]); + + useEffect(() => { + inputWrapperRef.current?.querySelector('input')?.focus(); + }, []); + + let hexValue = value; + if (value === 'accent') { + hexValue = getAccentColor(); + } else if (value === 'transparent') { + hexValue = ''; + } + + const focusHexInputOnClick = useCallback((e) => { + inputWrapperRef.current?.querySelector('input')?.focus(); + }, []); + + return ( +
    + +
    +
    + # + + {eyedropper && !!window.EyeDropper && ( + + )} +
    + + {hasTransparentOption &&
    +
    + ); +} + +function ColorSwatch({hex, accent, transparent, title, isSelected, onSelect}) { + const backgroundColor = accent ? getAccentColor() : hex; + + const ref = useRef(null); + + const onSelectHandler = (e) => { + e.preventDefault(); + + if (accent) { + onSelect('accent'); + } else if (transparent) { + onSelect('transparent'); + } else { + onSelect(hex); + } + }; + + return ( + + ); +} + +export function ColorIndicatorBeta({value, swatches, onSwatchChange, onTogglePicker, onChange, isExpanded, eyedropper, hasTransparentOption, children}) { + const [isOpen, setIsOpen] = useState(false); + const [showColorPicker, setShowColorPicker] = useState(false); + const [showChildren, setShowChildren] = useState(false); + const popoverRef = useRef(null); + + useClickOutside(isOpen, popoverRef, () => setIsOpen(false)); + + const stopPropagation = useCallback((e) => { + e.stopPropagation(); + e.preventDefault(); + }, []); + + useEffect(() => { + if (isExpanded) { + setIsOpen(true); + setShowChildren(true); + setShowColorPicker(false); + } + }, [isExpanded]); + + let backgroundColor = value; + let selectedSwatch = swatches.find(swatch => swatch.hex === value)?.title; + + if (value === 'accent') { + backgroundColor = getAccentColor(); + selectedSwatch = swatches.find(swatch => swatch.accent)?.title; + } else if (value === 'image') { + backgroundColor = 'transparent'; + selectedSwatch = swatches.find(swatch => swatch.image)?.title; + } else if (value === 'transparent') { + backgroundColor = 'white'; + selectedSwatch = swatches.find(swatch => swatch.transparent)?.title; + } + + if (isExpanded) { + selectedSwatch = null; + } + + const handleColorPickerChange = (newValue) => { + onChange(newValue); + // Don't close the popover when using the color picker + }; + + return ( +
    + + + {isOpen && ( +
    + {showColorPicker && ( + + )} + {showChildren && children} +
    +
    + {swatches.map(({customContent, ...swatch}) => ( + customContent ? + {customContent} : + { + onSwatchChange(val); + setShowColorPicker(false); + }} + {...swatch} + /> + ))} +
    + +
    +
    + )} +
    + ); +} diff --git a/packages/koenig-lexical/src/components/ui/ColorPickerBeta.stories.jsx b/packages/koenig-lexical/src/components/ui/ColorPickerBeta.stories.jsx new file mode 100644 index 0000000000..9ef599ecc2 --- /dev/null +++ b/packages/koenig-lexical/src/components/ui/ColorPickerBeta.stories.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import {ColorPickerBeta} from './ColorPickerBeta'; + +const story = { + title: 'Generic/Color picker (beta)', + component: ColorPickerBeta, + parameters: { + status: { + type: 'uiReady' + } + }, + argTypes: { + selectedName: {control: 'select', options: ['grey', 'blue', 'green', 'yellow', 'red', 'pink', 'purple']} + } +}; +export default story; + +const Template = (args) => { + return ( +
    + +
    + ); +}; + +export const Default = Template.bind({}); +Default.args = { + swatches: [ + {title: 'Brand color', accent: true}, + {title: 'Black', hex: '#000000'}, + {title: 'Transparent', transparent: true} + ] +}; diff --git a/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx b/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx index d46af44f50..a7452220ca 100644 --- a/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaPlaceholder.jsx @@ -6,7 +6,6 @@ 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, @@ -17,51 +16,10 @@ export const PLACEHOLDER_ICONS = { product: ProductPlaceholderIcon }; -export const CardText = ({text, type}) => ( - - {text} - -); - -const ButtonContents = ({desc, hasErrors}) => { - if (hasErrors) { - return null; - } - return

    {desc}

    ; -}; - -const StandardContents = ({desc, hasErrors, icon, size}) => { - if (size === 'xsmall' && hasErrors) { - return null; - } - - const Icon = PLACEHOLDER_ICONS[icon]; - - 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' +export const CardText = ({text}) => { + return ( + {text} ); - - return <> - -

    {desc}

    - ; }; export function MediaPlaceholder({ @@ -69,7 +27,6 @@ export function MediaPlaceholder({ icon, filePicker, size, - type, borderStyle = 'squared', isDraggedOver, errors = [], @@ -79,61 +36,35 @@ export function MediaPlaceholder({ multiple = false, ...props }) { - 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 buttonClasses = clsx( - 'group flex cursor-pointer select-none items-center justify-center', - type === 'button' && 'px-3 py-1', - type !== 'button' && (size === 'xsmall' ? 'p-4' : 'flex-col p-20') - ); - - const errorClasses = clsx( - 'font-sans text-sm font-semibold text-red', - size !== 'xsmall' && 'mt-3 max-w-[65%]' - ); - - const errorMessages = errors.map(error => ( - - {error.message} - - )); + const Icon = PLACEHOLDER_ICONS[icon]; return (
    -
    - {isDraggedOver ? ( - - ) : ( - - )} +
    + {isDraggedOver ? + : + <> + + + }
    ); @@ -143,6 +74,5 @@ 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/MediaPlaceholderBeta.jsx b/packages/koenig-lexical/src/components/ui/MediaPlaceholderBeta.jsx new file mode 100644 index 0000000000..62bf4332c5 --- /dev/null +++ b/packages/koenig-lexical/src/components/ui/MediaPlaceholderBeta.jsx @@ -0,0 +1,148 @@ +import AudioPlaceholderIcon from '../../assets/icons/kg-audio-placeholder.svg?react'; +import FilePlaceholderIcon from '../../assets/icons/kg-file-placeholder.svg?react'; +import GalleryPlaceholderIcon from '../../assets/icons/kg-gallery-placeholder.svg?react'; +import ImgPlaceholderIcon from '../../assets/icons/kg-img-placeholder.svg?react'; +import ProductPlaceholderIcon from '../../assets/icons/kg-product-placeholder.svg?react'; +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, + gallery: GalleryPlaceholderIcon, + video: VideoPlaceholderIcon, + audio: AudioPlaceholderIcon, + file: FilePlaceholderIcon, + product: ProductPlaceholderIcon +}; + +export const CardText = ({text, type}) => ( + + {text} + +); + +const ButtonContents = ({desc, hasErrors}) => { + if (hasErrors) { + return null; + } + return

    {desc}

    ; +}; + +const StandardContents = ({desc, hasErrors, icon, size}) => { + if (size === 'xsmall' && hasErrors) { + return null; + } + + const Icon = PLACEHOLDER_ICONS[icon]; + + 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' + ); + + return <> + +

    {desc}

    + ; +}; + +export function MediaPlaceholderBeta({ + desc, + icon, + filePicker, + size, + type, + borderStyle = 'squared', + isDraggedOver, + errors = [], + placeholderRef, + dataTestId = 'media-placeholder', + errorDataTestId = 'media-placeholder-errors', + multiple = false, + ...props +}) { + 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 buttonClasses = clsx( + 'group flex cursor-pointer select-none items-center justify-center', + type === 'button' && 'px-3 py-1', + type !== 'button' && (size === 'xsmall' ? 'p-4' : 'flex-col p-20') + ); + + const errorClasses = clsx( + 'font-sans text-sm font-semibold text-red', + size !== 'xsmall' && 'mt-3 max-w-[65%]' + ); + + const errorMessages = errors.map(error => ( + + {error.message} + + )); + + return ( +
    +
    + {isDraggedOver ? ( + + ) : ( + + )} +
    +
    + ); +} + +MediaPlaceholderBeta.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/MediaPlaceholderBeta.stories.jsx b/packages/koenig-lexical/src/components/ui/MediaPlaceholderBeta.stories.jsx new file mode 100644 index 0000000000..c0c073e0c8 --- /dev/null +++ b/packages/koenig-lexical/src/components/ui/MediaPlaceholderBeta.stories.jsx @@ -0,0 +1,91 @@ +import React from 'react'; + +import {MediaPlaceholderBeta} from './MediaPlaceholderBeta'; + +const story = { + title: 'Generic/Media placeholder (beta)', + component: MediaPlaceholderBeta, + argTypes: { + icon: { + options: ['image', 'gallery', 'video', 'audio', 'file', 'product'], + control: {type: 'select'} + }, + size: { + options: ['xsmall', 'small', 'medium', 'large'], + control: {type: 'select'} + }, + borderStyle: { + options: ['squared', 'rounded'], + control: {type: 'radio'} + } + }, + parameters: { + status: { + type: 'functional' + } + } +}; +export default story; + +const Template = args => ( +
    + +
    +); + +export const Image = Template.bind({}); +Image.args = { + icon: 'image', + desc: 'Click to select an image', + size: 'medium', + borderStyle: 'squared' +}; + +export const Gallery = Template.bind({}); +Gallery.args = { + icon: 'gallery', + desc: 'Click to select up to 9 images', + size: 'large', + borderStyle: 'squared' +}; + +export const Video = Template.bind({}); +Video.args = { + icon: 'video', + desc: 'Click to select a video', + size: 'medium', + borderStyle: 'squared' +}; + +export const Audio = Template.bind({}); +Audio.args = { + icon: 'audio', + desc: 'Click to upload an audio file', + size: 'xsmall', + borderStyle: 'squared' +}; + +export const File = Template.bind({}); +File.args = { + icon: 'file', + desc: 'Click to upload a file', + size: 'xsmall', + borderStyle: 'squared' +}; + +export const Product = Template.bind({}); +Product.args = { + icon: 'product', + desc: 'Click to upload a product image', + size: 'small', + borderStyle: 'squared' +}; + +export const Error = Template.bind({}); +Error.args = { + icon: 'video', + desc: 'Click to select a video', + size: 'medium', + borderStyle: 'squared', + errors: [{message: 'The file type you uploaded is not supported. Please use .MP4, .WEBM, .OGV'}] +}; diff --git a/packages/koenig-lexical/src/components/ui/MediaUploader.jsx b/packages/koenig-lexical/src/components/ui/MediaUploader.jsx index 1c1cfc606d..ca108223c8 100644 --- a/packages/koenig-lexical/src/components/ui/MediaUploader.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaUploader.jsx @@ -17,7 +17,6 @@ export function MediaUploader({ desc, icon, size, - type, borderStyle = 'squared', backgroundSize = 'cover', mimeTypes, @@ -65,7 +64,6 @@ export function MediaUploader({ isDraggedOver={dragHandler?.isDraggedOver} placeholderRef={dragHandler?.setRef} size={size} - type={type} /> { + fileInputRef.current = element; + setFileInputRef?.(element); + }; + + const progressStyle = { + width: `${progress?.toFixed(0)}%` + }; + + const onRemove = (e) => { + e.stopPropagation(); // prevents card from losing selected state + onRemoveMedia(); + }; + + const isEmpty = !isLoading && !src; + + if (isEmpty) { + return ( +
    + openFileSelection({fileInputRef})} + icon={icon} + isDraggedOver={dragHandler?.isDraggedOver} + placeholderRef={dragHandler?.setRef} + size={size} + type={type} + /> + openFileSelection({fileInputRef})} + mimeTypes={mimeTypes} + onFileChange={onFileChange} + /> +
    + ); + } + + return ( +
    + {src && ( + <> + {alt} +
    + + )} + + {!isLoading && ( +
    + {additionalActions} + { isPinturaEnabled && openImageEditor({ + image: src, + handleSave: (editedImage) => { + onFileChange({ + target: { + files: [editedImage] + } + }); + } + })} /> } + +
    + )} + + {isLoading && ( +
    + +
    + )} +
    + ); +} + +MediaUploaderBeta.propTypes = { + className: PropTypes.string, + src: PropTypes.string, + alt: PropTypes.string, + desc: PropTypes.string, + icon: PropTypes.string, + size: PropTypes.string, + type: PropTypes.oneOf(['image', 'button']), + borderStyle: PropTypes.string, + mimeTypes: PropTypes.arrayOf(PropTypes.string), + onFileChange: PropTypes.func, + dragHandler: PropTypes.shape({ + isDraggedOver: PropTypes.bool, + setRef: PropTypes.func + }), + isLoading: PropTypes.bool, + progress: PropTypes.number, + errors: PropTypes.arrayOf(PropTypes.shape({ + message: PropTypes.string + })), + onRemoveMedia: PropTypes.func +}; diff --git a/packages/koenig-lexical/src/components/ui/SettingsPanel.jsx b/packages/koenig-lexical/src/components/ui/SettingsPanel.jsx index dca5109966..bda5cc9d06 100644 --- a/packages/koenig-lexical/src/components/ui/SettingsPanel.jsx +++ b/packages/koenig-lexical/src/components/ui/SettingsPanel.jsx @@ -3,12 +3,16 @@ import React from 'react'; import clsx from 'clsx'; import useSettingsPanelReposition from '../../hooks/useSettingsPanelReposition'; import {ButtonGroup} from './ButtonGroup'; -import {ColorIndicator} from './ColorPicker'; +import {ButtonGroupBeta} from './ButtonGroupBeta'; +import {ColorIndicator, ColorPicker} from './ColorPicker'; +import {ColorIndicatorBeta} from './ColorPickerBeta'; import {ColorOptionButtons} from './ColorOptionButtons'; +import {ColorOptionButtonsBeta} from './ColorOptionButtonsBeta.jsx'; import {Dropdown} from './Dropdown'; import {Input} from './Input'; import {InputList, InputListItem} from './InputList.jsx'; import {MediaUploader} from './MediaUploader'; +import {MediaUploaderBeta} from './MediaUploaderBeta'; import {MultiSelectDropdown} from './MultiSelectDropdown'; import {Slider} from './Slider.jsx'; import {TabView} from './TabView'; @@ -231,6 +235,18 @@ export function ButtonGroupSetting({label, onClick, selectedName, buttons}) { ); } +export function ButtonGroupSettingBeta({label, onClick, selectedName, buttons}) { + return ( +
    +
    {label}
    + +
    + +
    +
    + ); +} + export function ColorOptionSetting({label, onClick, selectedName, buttons, layout, dataTestId}) { return (
    @@ -243,11 +259,41 @@ export function ColorOptionSetting({label, onClick, selectedName, buttons, layou ); } -export function ColorPickerSetting({label, isExpanded, onSwatchChange, onPickerChange, onTogglePicker, value, swatches, eyedropper, hasTransparentOption, dataTestId, customToolbarContent, children}) { +export function ColorOptionSettingBeta({label, onClick, selectedName, buttons, layout, dataTestId}) { + return ( +
    +
    {label}
    + +
    + +
    +
    + ); +} + +export function ColorPickerSetting({label, isExpanded, onSwatchChange, onPickerChange, onTogglePicker, value, swatches, eyedropper, hasTransparentOption, dataTestId}) { + const mappedPicker = (event) => { + onTogglePicker(true); + }; + 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 (
    @@ -255,6 +301,31 @@ export function ColorPickerSetting({label, isExpanded, onSwatchChange, onPickerC
    +
    +
    + {isExpanded && } +
    + ); +} + +export function ColorPickerSettingBeta({label, isExpanded, onSwatchChange, onPickerChange, onTogglePicker, value, swatches, eyedropper, hasTransparentOption, dataTestId, customToolbarContent, children}) { + const markClickedInside = (event) => { + event.stopPropagation(); + }; + + return ( +
    +
    +
    {label}
    + +
    + {children} - +
    @@ -304,3 +375,36 @@ export function MediaUploadSetting({className, label, hideLabel, onFileChange, i
    ); } + +export function MediaUploadSettingBeta({className, label, hideLabel, onFileChange, isDraggedOver, placeholderRef, src, alt, isLoading, errors = [], progress, onRemoveMedia, icon, desc, size, type, stacked, borderStyle, mimeTypes, isPinturaEnabled, openImageEditor, setFileInputRef}) { + return ( +
    +
    {label}
    + + +
    + ); +} diff --git a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx index 8933d87a21..317d817742 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx @@ -7,7 +7,7 @@ import ReplacementStringsPlugin from '../../../plugins/ReplacementStringsPlugin. import clsx from 'clsx'; import defaultTheme from '../../../themes/default.js'; import {Button} from '../Button.jsx'; -import {ButtonGroupSetting, ColorOptionSetting, ColorPickerSetting, InputSetting, InputUrlSetting, MediaUploadSetting, SettingsPanel, ToggleSetting} from '../SettingsPanel.jsx'; +import {ButtonGroupSettingBeta, ColorOptionSettingBeta, ColorPickerSettingBeta, InputSetting, InputUrlSetting, MediaUploadSettingBeta, SettingsPanel, ToggleSetting} from '../SettingsPanel.jsx'; import {ReadOnlyOverlay} from '../ReadOnlyOverlay.jsx'; import {RestrictContentPlugin} from '../../../index.js'; import {VisibilitySettings} from '../VisibilitySettings.jsx'; @@ -137,14 +137,14 @@ export function CallToActionCard({ const designSettings = ( <> {/* Layout settings */} - {/* Color picker */} - {/* Image setting */} - {showButton && ( <> - { - handleShowBackgroundImage(); - setBackgroundColorPickerExpanded(true); - setButtonColorPickerExpanded(false); - }; - return ( <>
    @@ -410,24 +405,36 @@ export function HeaderCard({alignment, (layout !== 'split' && { title: 'Image', customContent: ( - + ) }), {title: 'Black', hex: '#000000'}, {title: 'Grey', hex: '#F0F0F0'}, {title: 'Brand color', accent: true} ].filter(Boolean)} - value={(showBackgroundImage && layout !== 'split') ? 'image' : backgroundColor} + value={(showBackgroundImage && layout !== 'split') ? '' : backgroundColor} onPickerChange={color => handleBackgroundColor(color, matchingTextColor(color))} onSwatchChange={(color) => { handleBackgroundColor(color, matchingTextColor(color)); setBackgroundColorPickerExpanded(false); }} - onTogglePicker={(isExpanded) => { + onTogglePicker={ (isExpanded) => { if (isExpanded) { if (layout !== 'split') { handleHideBackgroundImage(); @@ -443,34 +450,32 @@ export function HeaderCard({alignment, setButtonColorPickerExpanded(!isExpanded); } }} - > - {layout !== 'split' && showBackgroundImage && ( - { - handleClearBackgroundImage(); - handleTextColor(matchingTextColor(backgroundColor)); - }} - /> - )} - + /> + { + handleClearBackgroundImage(); + handleTextColor(matchingTextColor(backgroundColor)); + }} + /> {/* Button settings */} { - handleShowBackgroundImage(); - setBackgroundColorPickerExpanded(true); - setButtonColorPickerExpanded(false); - }; - return ( <>
    @@ -408,18 +403,30 @@ export function SignupCard({alignment, (layout !== 'split' && { title: 'Image', customContent: ( - + ) }), {title: 'Grey', hex: '#F0F0F0'}, {title: 'Black', hex: '#000000'}, {title: 'Brand color', accent: true} ].filter(Boolean)} - value={(showBackgroundImage && layout !== 'split') ? 'image' : backgroundColor} + value={(showBackgroundImage && layout !== 'split') ? '' : backgroundColor} onPickerChange={color => handleBackgroundColor(color, matchingTextColor(color))} onSwatchChange={(color) => { handleBackgroundColor(color, matchingTextColor(color)); @@ -441,34 +448,33 @@ export function SignupCard({alignment, setButtonColorPickerExpanded(!isExpanded); } }} - > - {layout !== 'split' && showBackgroundImage && ( - { - handleClearBackgroundImage(); - handleTextColor(matchingTextColor(backgroundColor)); - }} - /> - )} - + /> + + { + handleClearBackgroundImage(); + handleTextColor(matchingTextColor(backgroundColor)); + }} + /> 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 3cc5bdb024..467af9c8c1 100644 --- a/packages/koenig-lexical/test/e2e/cards/callout-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/callout-card.test.js @@ -1,5 +1,5 @@ import {assertHTML, createSnippet, focusEditor, html, initialize, insertCard, isMac} from '../../utils/e2e'; -import {cardBackgroundColorSettings} from '../../utils/background-color-helper'; +// import {calloutColorPicker} from '../../../src/components/ui/cards/CalloutCardx'; import {expect, test} from '@playwright/test'; test.describe('Callout Card', async () => { @@ -125,23 +125,26 @@ test.describe('Callout Card', async () => { await expect(emojiPickerContainer).toBeVisible(); }); - // test('colour picker renders all colours', async function () { - // await focusEditor(page); - // await insertCard(page, {cardName: 'callout'}); + test('colour picker renders all colours', async function () { + await focusEditor(page); + await insertCard(page, {cardName: 'callout'}); - // // await Promise.all(calloutColorPicker.map(async (color) => { - // // const colorPicker = page.locator(`[data-test-id="color-picker-${color.name}"]`); - // // await expect(colorPicker).toBeVisible(); - // // })); - // }); + // await Promise.all(calloutColorPicker.map(async (color) => { + // const colorPicker = page.locator(`[data-test-id="color-picker-${color.name}"]`); + // await expect(colorPicker).toBeVisible(); + // })); + }); - test('can change background color to green', async function () { + test('can change background color', async function () { await focusEditor(page); await insertCard(page, {cardName: 'callout'}); - await cardBackgroundColorSettings(page, { - cardColorPickerTestId: 'callout-color-picker', - colorTestId: 'color-picker-green' - }); + + const colorPicker = page.locator(`[data-test-id="color-picker-green"]`); + await colorPicker.click(); + + // ensure data-test-id="callout-bg-blue" is visible + const greenCallout = page.locator('[data-testid="callout-bg-green"]'); + await expect(greenCallout).toBeVisible(); }); test('can select an emoji', async function () { @@ -312,11 +315,8 @@ test.describe('Callout Card', async () => { await page.keyboard.type('Hello '); // Change color - // await page.locator(`[data-test-id="color-picker-green"]`).click(); - await cardBackgroundColorSettings(page, { - cardColorPickerTestId: 'callout-color-picker', - colorTestId: 'color-picker-green' - }); + await page.locator(`[data-test-id="color-picker-green"]`).click(); + // Continue editing the content await page.keyboard.type('world'); diff --git a/packages/koenig-lexical/test/e2e/cards/gallery-card.test.js b/packages/koenig-lexical/test/e2e/cards/gallery-card.test.js index 50d610f135..9c14befb78 100644 --- a/packages/koenig-lexical/test/e2e/cards/gallery-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/gallery-card.test.js @@ -507,7 +507,8 @@ test.describe('Gallery card', async () => { const fileChooser = await fileChooserPromise; await fileChooser.setFiles(filePaths); - await expect(page.locator('[data-testid="gallery-image"]')).toHaveCount(9); + await expect(page.getByTestId('progress-bar')).not.toBeVisible(); + await expect(page.getByTestId('gallery-image')).toHaveCount(9); }); const editorState = await getEditorState(page); diff --git a/packages/koenig-lexical/test/e2e/cards/header-card.test.js b/packages/koenig-lexical/test/e2e/cards/header-card.test.js index cb7ca3f33d..a7c0c8fdf8 100644 --- a/packages/koenig-lexical/test/e2e/cards/header-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/header-card.test.js @@ -1,6 +1,5 @@ import path from 'path'; import {assertHTML, focusEditor, html, initialize, isMac} from '../../utils/e2e'; -import {cardBackgroundColorSettings} from '../../utils/background-color-helper'; import {expect, test} from '@playwright/test'; import {fileURLToPath} from 'url'; const __filename = fileURLToPath(import.meta.url); @@ -187,8 +186,8 @@ test.describe('Header card V1', async () => { await createHeaderCard({page}); // Check that the default size is small - await expect(page.getByLabel('S')).toHaveClass(/ shadow-xs /); - await expect(page.getByLabel('M')).not.toHaveClass(/ shadow-xs /); + await expect(page.getByLabel('S')).toHaveClass(/ bg-grey-150 /); + await expect(page.getByLabel('M')).not.toHaveClass(/ bg-grey-150 /); // Get height of the card const box = await page.locator('[data-kg-card="header"] > div:first-child').nth(0).boundingBox(); @@ -196,7 +195,7 @@ test.describe('Header card V1', async () => { // Click on the medium button await page.getByLabel('M').click(); - await expect(page.getByLabel('M')).toHaveClass(/ shadow-xs /); + await expect(page.getByLabel('M')).toHaveClass(/ bg-grey-150 /); // Check that the height has changed const box2 = await page.locator('[data-kg-card="header"] > div:first-child').nth(0).boundingBox(); @@ -207,7 +206,7 @@ test.describe('Header card V1', async () => { // Switch to large const largeButton = page.locator('[aria-label="L"]'); await largeButton.click(); - await expect(largeButton).toHaveClass(/ shadow-xs /); + await expect(largeButton).toHaveClass(/ bg-grey-150 /); // Check that the height has changed const box3 = await page.locator('[data-kg-card="header"] > div:first-child').nth(0).boundingBox(); @@ -219,23 +218,27 @@ test.describe('Header card V1', async () => { test('can change the background color', async function () { await createHeaderCard({page}); + const lightButton = page.locator('[aria-label="Light"]'); + const darkButton = page.locator('[aria-label="Dark"]'); + const accentButton = page.locator('[aria-label="Accent"]'); + // Default class should be 'bg-black' on the card await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveClass(/ bg-black /); // Switch to light - await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'header-background-color', colorTestId: 'color-picker-light'}); + await lightButton.click(); // Check that the background color has changed await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveClass(/ bg-grey-100 /); // Switch back to dark - await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'header-background-color', colorTestId: 'color-picker-dark'}); + await darkButton.click(); // Check that the background color has changed await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveClass(/ bg-black /); // Switch to accent - await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'header-background-color', colorTestId: 'color-picker-accent'}); + await accentButton.click(); // Check that the background color has changed await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveClass(/ bg-accent /); @@ -248,9 +251,8 @@ test.describe('Header card V1', async () => { const fileChooserPromise = page.waitForEvent('filechooser'); - await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'header-background-color', imageUploadId: 'background-image-color-button'}); - // click on text saying Click to upload background image - await page.getByText('Click to upload background image').click(); + // Click data-testid="background-image-color-button" + await page.click('[data-testid="background-image-color-button"]'); // Set files const fileChooser = await fileChooserPromise; @@ -526,12 +528,10 @@ test.describe('Header card V2', () => { await page.click('[data-testid="header-button-toggle"]'); - // await page.click('[data-testid="header-button-color"] [aria-label="Pick color"]'); - - // await page.fill('[data-testid="header-button-color"] input', ''); - // await page.keyboard.type('ff0000'); + await page.click('[data-testid="header-button-color"] [aria-label="Pick color"]'); - await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'header-button-color', customColor: 'ff0000'}); + await page.fill('[data-testid="header-button-color"] input', ''); + await page.keyboard.type('ff0000'); // Selected colour should be applied inline await expect(page.locator('[data-testid="header-card-button"]')).toHaveCSS('background-color', 'rgb(255, 0, 0)'); @@ -548,12 +548,10 @@ test.describe('Header card V2', () => { test('can change the background color and text color', async function () { await createHeaderCard({page, version: 2}); - await cardBackgroundColorSettings(page, {cardColorPickerTestId: 'header-background-color', customColor: 'ff0000'}); - - // await page.click('[data-testid="header-background-color"] [aria-label="Pick color"]'); + await page.click('[data-testid="header-background-color"] [aria-label="Pick color"]'); - // await page.fill('[data-testid="header-background-color"] input', ''); - // await page.keyboard.type('ff0000'); + await page.fill('[data-testid="header-background-color"] input', ''); + await page.keyboard.type('ff0000'); // Selected colour should be applied inline const container = page.getByTestId('header-card-container'); @@ -571,12 +569,12 @@ test.describe('Header card V2', () => { test('can switch between background image and color', async function () { const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); await createHeaderCard({page, version: 2}); - // Choose an image + const fileChooserPromise = page.waitForEvent('filechooser'); - await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'header-background-color', imageUploadId: 'header-background-image-toggle'}); - await page.click('[data-testid="media-upload-placeholder"]'); + await page.click('[data-testid="header-background-image-toggle"]'); + const fileChooser = await fileChooserPromise; await fileChooser.setFiles([filePath]); @@ -586,16 +584,15 @@ test.describe('Header card V2', () => { // Switch to a color swatch - // await page.click('[data-testid="header-background-color"] button[title="Black"]'); - await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'header-background-color', findByColorTitle: 'Black'}); + await page.click('[data-testid="header-background-color"] button[title="Black"]'); await expect(page.locator('[data-kg-card="header"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); await expect(page.locator('[data-testid="media-upload-setting"]')).not.toBeVisible(); - // // Switch back to the image + // Switch back to the image - await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'header-background-color', imageUploadId: 'header-background-image-toggle'}); + await page.click('[data-testid="header-background-image-toggle"]'); await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-testid="media-upload-setting"]')).toBeVisible(); @@ -603,36 +600,13 @@ test.describe('Header card V2', () => { // Open the color picker - await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'header-background-color', customColor: '000000'}); + await page.click('[data-testid="header-background-color"] [aria-label="Pick color"]'); await expect(page.locator('[data-kg-card="header"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-kg-card="header"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); await expect(page.locator('[data-testid="media-upload-setting"]')).not.toBeVisible(); }); - - test('has image icon when background image is selected', async function (){ - const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); - await createHeaderCard({page, version: 2}); - // Choose an image - const fileChooserPromise = page.waitForEvent('filechooser'); - - await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'header-background-color', imageUploadId: 'header-background-image-toggle'}); - await page.click('[data-testid="media-upload-placeholder"]'); - const fileChooser = await fileChooserPromise; - await fileChooser.setFiles([filePath]); - - // Check if it is set as a background image - - // Check if it is also set as an image in the panel - await expect(page.locator('[data-testid="media-upload-filled"] img')).toHaveAttribute('src', /blob:/); - - const parentLocator = page.locator('[data-testid="color-selector-button"]'); - const iconHtml = ''; - const parentHtml = await parentLocator.innerHTML(); - - await expect(parentHtml).toContain(iconHtml); - }); test('can add and remove background image in split layout', async function () { const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); const fileChooserPromise = page.waitForEvent('filechooser'); diff --git a/packages/koenig-lexical/test/e2e/cards/signup-card.test.js b/packages/koenig-lexical/test/e2e/cards/signup-card.test.js index c4461127c3..dcade04015 100644 --- a/packages/koenig-lexical/test/e2e/cards/signup-card.test.js +++ b/packages/koenig-lexical/test/e2e/cards/signup-card.test.js @@ -1,6 +1,5 @@ import path from 'path'; import {assertHTML, focusEditor, html, initialize, insertCard} from '../../utils/e2e'; -import {cardBackgroundColorSettings} from '../../utils/background-color-helper'; import {expect, test} from '@playwright/test'; import {fileURLToPath} from 'url'; const __filename = fileURLToPath(import.meta.url); @@ -199,14 +198,14 @@ test.describe('Signup card', async () => { await expect(page.getByTestId('signup-card-button')).toHaveText('Subscribe now'); }); - test('can change the button color and text color', async function () { + test('can change the button background color and text color', async function () { await focusEditor(page); await insertCard(page, {cardName: 'signup'}); - await cardBackgroundColorSettings(page, { - customColor: 'ff0000', - cardColorPickerTestId: 'signup-button-color' - }); + await page.click('[data-testid="signup-button-color"] [aria-label="Pick color"]'); + + await page.fill('[data-testid="signup-button-color"] input', ''); + await page.keyboard.type('ff0000'); // Selected colour should be applied inline await expect(page.locator('[data-testid="signup-card-button"]')).toHaveCSS('background-color', 'rgb(255, 0, 0)'); @@ -224,10 +223,10 @@ test.describe('Signup card', async () => { await focusEditor(page); await insertCard(page, {cardName: 'signup'}); - await cardBackgroundColorSettings(page, { - customColor: 'ff0000', - cardColorPickerTestId: 'signup-background-color' - }); + await page.click('[data-testid="signup-background-color"] [aria-label="Pick color"]'); + + await page.fill('[data-testid="signup-background-color"] input', ''); + await page.keyboard.type('ff0000'); // Selected colour should be applied inline const container = page.getByTestId('signup-card-container'); @@ -250,8 +249,7 @@ test.describe('Signup card', async () => { const fileChooserPromise = page.waitForEvent('filechooser'); - await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); - await page.click('[data-testid="media-upload-placeholder"]'); + await page.click('[data-testid="signup-background-image-toggle"]'); // Set files const fileChooser = await fileChooserPromise; @@ -281,34 +279,6 @@ test.describe('Signup card', async () => { await expect(page.locator('[data-testid="media-upload-filled"] img')).toHaveAttribute('src', /blob:/); }); - test('has image icon when background image is selected', async function (){ - const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); - - await focusEditor(page); - await insertCard(page, {cardName: 'signup'}); - - const fileChooserPromise = page.waitForEvent('filechooser'); - - await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); - await page.click('[data-testid="media-upload-placeholder"]'); - - // Set files - const fileChooser = await fileChooserPromise; - await fileChooser.setFiles([filePath]); - - // Check if it is set as a background image - await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-image', /blob:/); - - // Check if it is also set as an image in the panel - await expect(page.locator('[data-testid="media-upload-filled"] img')).toHaveAttribute('src', /blob:/); - - const parentLocator = page.locator('[data-testid="color-selector-button"]'); - const iconHtml = ''; - const parentHtml = await parentLocator.innerHTML(); - - await expect(parentHtml).toContain(iconHtml); - }); - test('can switch between background image and color', async function () { const filePath = path.relative(process.cwd(), __dirname + `/../fixtures/large-image.jpeg`); @@ -319,8 +289,7 @@ test.describe('Signup card', async () => { const fileChooserPromise = page.waitForEvent('filechooser'); - await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); - await page.click('[data-testid="media-upload-placeholder"]'); + await page.click('[data-testid="signup-background-image-toggle"]'); const fileChooser = await fileChooserPromise; await fileChooser.setFiles([filePath]); @@ -331,24 +300,23 @@ test.describe('Signup card', async () => { // Switch to a color swatch - // await page.click('[data-testid="signup-background-color"] button[title="Black"]'); - await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'signup-background-color', findByColorTitle: 'Black'}); + await page.click('[data-testid="signup-background-color"] button[title="Black"]'); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); await expect(page.locator('[data-testid="media-upload-setting"]')).not.toBeVisible(); - // // Switch back to the image + // Switch back to the image - await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); + await page.click('[data-testid="signup-background-image-toggle"]'); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-testid="media-upload-setting"]')).toBeVisible(); await expect(page.locator('[data-testid="media-upload-filled"] img')).toHaveAttribute('src', /blob:/); - // // Open the color picker + // Open the color picker - await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'signup-background-color', customColor: '000000'}); + await page.click('[data-testid="signup-background-color"] [aria-label="Pick color"]'); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-color', 'rgb(0, 0, 0)'); @@ -363,8 +331,7 @@ test.describe('Signup card', async () => { // Text colour is updated based on the background colour - // await page.click('[data-testid="signup-background-color"] button[title="Grey"]'); - await cardBackgroundColorSettings(page, {fireColorSetting: true, cardColorPickerTestId: 'signup-background-color', findByColorTitle: 'Grey'}); + await page.click('[data-testid="signup-background-color"] button[title="Grey"]'); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-color', 'rgb(240, 240, 240)'); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('color', 'rgb(0, 0, 0)'); @@ -373,9 +340,8 @@ test.describe('Signup card', async () => { const fileChooserPromise = page.waitForEvent('filechooser'); - // await page.click('[data-testid="signup-background-image-toggle"]'); - await cardBackgroundColorSettings(page, {fireColorSetting: false, cardColorPickerTestId: 'signup-background-color', imageUploadId: 'signup-background-image-toggle'}); - await page.click('[data-testid="media-upload-placeholder"]'); + await page.click('[data-testid="signup-background-image-toggle"]'); + const fileChooser = await fileChooserPromise; await fileChooser.setFiles([filePath]); @@ -384,8 +350,6 @@ test.describe('Signup card', async () => { // When switching to split layout, text colour is set based on the background colour - await page.locator('[data-testid="settings-panel"]').click(); - await page.locator('[data-testid="signup-layout-split"]').click(); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).not.toHaveCSS('background-image', /blob:/); @@ -394,10 +358,6 @@ test.describe('Signup card', async () => { // When switching back from split layout, text colour is set based on the background colour - // data-testid="settings-panel" - - await page.locator('[data-testid="settings-panel"]').click(); // click here to close the colour swatch - await page.locator('[data-testid="signup-layout-wide"]').click(); await expect(page.locator('[data-kg-card="signup"] > div:first-child')).toHaveCSS('background-image', /blob:/); From f327cb24942903c9ede348b1308f2f27a3bff0af Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 09:26:29 +0000 Subject: [PATCH 28/41] fixed CalloutCard background color setting layout --- packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx index e00576b1ce..f40bb1c50f 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CalloutCard.jsx @@ -158,6 +158,7 @@ export function CalloutCard({ buttons={calloutColorPicker} dataTestId='callout-color-picker' label='Background' + layout='stacked' selectedName={color} onClick={handleColorChange} /> From a67e9d7b58151bc9aeb77eb553b8e895134baf46 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 09:54:26 +0000 Subject: [PATCH 29/41] fixed default button text color --- .../nodes/call-to-action/CallToActionNode.js | 2 +- .../call-to-action/calltoaction-renderer.js | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 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 4feba96661..6b423afec2 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 @@ -12,7 +12,7 @@ export class CallToActionNode extends generateDecoratorNode({ {name: 'buttonText', default: 'Learn more'}, {name: 'buttonUrl', default: ''}, {name: 'buttonColor', default: '#000000'}, // Where colour is customisable, we should use hex values - {name: 'buttonTextColor', default: ''}, + {name: 'buttonTextColor', default: '#ffffff'}, {name: 'hasSponsorLabel', default: true}, {name: 'sponsorLabel', default: '

    SPONSORED

    '}, {name: 'backgroundColor', default: 'grey'}, // Since this is one of a few fixed options, we stick to colour names. diff --git a/packages/kg-default-nodes/lib/nodes/call-to-action/calltoaction-renderer.js b/packages/kg-default-nodes/lib/nodes/call-to-action/calltoaction-renderer.js index 61e9abbe91..337682c651 100644 --- a/packages/kg-default-nodes/lib/nodes/call-to-action/calltoaction-renderer.js +++ b/packages/kg-default-nodes/lib/nodes/call-to-action/calltoaction-renderer.js @@ -33,8 +33,7 @@ function ctaCardTemplate(dataset) {
    ` : ''} ${dataset.showButton ? ` - + ${dataset.buttonText} ` : ''} @@ -46,12 +45,12 @@ function ctaCardTemplate(dataset) { } function emailCTATemplate(dataset, options = {}) { - const buttonStyle = dataset.buttonColor === 'accent' - ? `color: ${dataset.buttonTextColor};` + const buttonStyle = dataset.buttonColor === 'accent' + ? `color: ${dataset.buttonTextColor};` : `background-color: ${dataset.buttonColor}; color: ${dataset.buttonTextColor};`; let imageDimensions; - + if (dataset.imageUrl && dataset.imageWidth && dataset.imageHeight) { imageDimensions = { width: dataset.imageWidth, @@ -98,9 +97,10 @@ function emailCTATemplate(dataset, options = {}) { @@ -146,9 +146,10 @@ function emailCTATemplate(dataset, options = {}) {
    - + style="${buttonStyle}" + > ${dataset.buttonText}
    From 286ff6ea2eb5c1ec371bb59da0735d8848788a2f Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 10:07:15 +0000 Subject: [PATCH 30/41] fixed tests --- .../lib/nodes/call-to-action/CallToActionNode.js | 1 - .../kg-default-nodes/test/nodes/call-to-action.test.js | 10 ++++------ 2 files changed, 4 insertions(+), 7 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 6b423afec2..60547c3922 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 @@ -16,7 +16,6 @@ export class CallToActionNode extends generateDecoratorNode({ {name: 'hasSponsorLabel', default: true}, {name: 'sponsorLabel', default: '

    SPONSORED

    '}, {name: 'backgroundColor', default: 'grey'}, // Since this is one of a few fixed options, we stick to colour names. - {name: 'hasImage', default: false}, {name: 'imageUrl', default: ''}, {name: 'imageWidth', default: null}, {name: 'imageHeight', default: null} diff --git a/packages/kg-default-nodes/test/nodes/call-to-action.test.js b/packages/kg-default-nodes/test/nodes/call-to-action.test.js index 93bb5a8e9f..e844c478ad 100644 --- a/packages/kg-default-nodes/test/nodes/call-to-action.test.js +++ b/packages/kg-default-nodes/test/nodes/call-to-action.test.js @@ -101,7 +101,7 @@ describe('CallToActionNode', function () { callToActionNode.buttonColor = '#ffffff'; callToActionNode.buttonColor.should.equal('#ffffff'); - callToActionNode.buttonTextColor.should.equal(''); + callToActionNode.buttonTextColor.should.equal('#ffffff'); callToActionNode.buttonTextColor = 'black'; callToActionNode.buttonTextColor.should.equal('black'); @@ -113,7 +113,7 @@ describe('CallToActionNode', function () { callToActionNode.backgroundColor = 'red'; callToActionNode.backgroundColor.should.equal('red'); - should(callToActionNode.imageUrl).be.null(); + callToActionNode.imageUrl.should.equal(''); callToActionNode.imageUrl = 'http://blog.com/image1.jpg'; callToActionNode.imageUrl.should.equal('http://blog.com/image1.jpg'); @@ -259,7 +259,6 @@ describe('CallToActionNode', function () { buttonText: 'Get access now', buttonTextColor: '#000000', buttonUrl: 'http://someblog.com/somepost', - hasImage: true, hasSponsorLabel: true, sponsorLabel: '

    SPONSORED

    ', imageUrl: '/content/images/2022/11/koenig-lexical.jpg', @@ -276,7 +275,7 @@ describe('CallToActionNode', function () { html.should.containEql('Get access now'); html.should.containEql('http://someblog.com/somepost'); html.should.containEql('

    SPONSORED

    '); // because hasSponsorLabel is true - html.should.containEql('/content/images/size/w64h64/2022/11/koenig-lexical.jpg'); // because hasImage is true + html.should.containEql('/content/images/size/w64h64/2022/11/koenig-lexical.jpg'); html.should.containEql('This is a new CTA Card via email.'); })); @@ -289,7 +288,6 @@ describe('CallToActionNode', function () { buttonText: 'Get access now', buttonTextColor: '#000000', buttonUrl: 'http://someblog.com/somepost', - hasImage: true, hasSponsorLabel: true, sponsorLabel: '

    SPONSORED

    ', imageUrl: '/content/images/2022/11/koenig-lexical.jpg', @@ -301,7 +299,7 @@ describe('CallToActionNode', function () { const {element} = callToActionNode.exportDOM(exportOptions); const html = element.outerHTML.toString(); - html.should.containEql('/content/images/size/w256h256/2022/11/koenig-lexical.jpg'); // because hasImage is true + html.should.containEql('/content/images/size/w256h256/2022/11/koenig-lexical.jpg'); })); it('renders email with img width and height when immersive', editorTest(function () { From 11cf9a671096bba3048d500f5c573ac76288b5a0 Mon Sep 17 00:00:00 2001 From: Sanne de Vries Date: Mon, 3 Mar 2025 11:10:35 +0100 Subject: [PATCH 31/41] Removed tooltip from layout buttons in call to action card No ref - There's no good way to describe the layout nuances in a single word, so tooltip is confusing rather than helpful - Added more descriptive aria labels to layout buttons --- .../src/components/ui/ButtonGroupBeta.jsx | 15 +++++++++------ .../src/components/ui/SettingsPanel.jsx | 4 ++-- .../src/components/ui/cards/CallToActionCard.jsx | 9 ++++++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx index dc15eed61a..8f0b1026b9 100644 --- a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx @@ -4,14 +4,16 @@ import React from 'react'; import {Tooltip} from './Tooltip'; import {usePreviousFocus} from '../../hooks/usePreviousFocus'; -export function ButtonGroupBeta({buttons = [], selectedName, onClick}) { +export function ButtonGroupBeta({buttons = [], selectedName, onClick, hasTooltip = true}) { return (
      - {buttons.map(({label, name, Icon, dataTestId}) => ( + {buttons.map(({label, name, Icon, dataTestId, ariaLabel}) => ( ); } ButtonGroupBeta.propTypes = { - selectedName: PropTypes.oneOf(['regular', 'wide', 'full', 'split', 'center', 'left', 'small', 'medium', 'large', 'grid', 'list', 'minimal', 'immersive']) + selectedName: PropTypes.oneOf(['regular', 'wide', 'full', 'split', 'center', 'left', 'small', 'medium', 'large', 'grid', 'list', 'minimal', 'immersive']), + hasTooltip: PropTypes.bool }; diff --git a/packages/koenig-lexical/src/components/ui/SettingsPanel.jsx b/packages/koenig-lexical/src/components/ui/SettingsPanel.jsx index bda5cc9d06..4d99536a60 100644 --- a/packages/koenig-lexical/src/components/ui/SettingsPanel.jsx +++ b/packages/koenig-lexical/src/components/ui/SettingsPanel.jsx @@ -235,13 +235,13 @@ export function ButtonGroupSetting({label, onClick, selectedName, buttons}) { ); } -export function ButtonGroupSettingBeta({label, onClick, selectedName, buttons}) { +export function ButtonGroupSettingBeta({label, onClick, selectedName, buttons, hasTooltip}) { return (
      {label}
      - +
      ); diff --git a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx index 317d817742..cdf226104d 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx @@ -120,13 +120,15 @@ export function CallToActionCard({ label: 'Minimal', name: 'minimal', Icon: MinimalLayoutIcon, - dataTestId: 'minimal-layout' + dataTestId: 'minimal-layout', + ariaLabel: 'Left-aligned layout with small, square image' }, { label: 'Immersive', name: 'immersive', Icon: ImmersiveLayoutIcon, - dataTestId: 'immersive-layout' + dataTestId: 'immersive-layout', + ariaLabel: 'Center-aligned layout with full-width image and button' } ]; @@ -139,6 +141,7 @@ export function CallToActionCard({ {/* Layout settings */} From ec790b29ced21daef6c3478afc5bee26cdd2c08a Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 10:17:15 +0000 Subject: [PATCH 32/41] added missing prop types to --- .../src/components/ui/MediaUploaderBeta.jsx | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx b/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx index 57b5fae7d8..1a8460f52c 100644 --- a/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx @@ -116,24 +116,25 @@ export function MediaUploaderBeta({ } MediaUploaderBeta.propTypes = { - className: PropTypes.string, - src: PropTypes.string, + additionalActions: PropTypes.node, alt: PropTypes.string, + backgroundSize: PropTypes.oneOf(['cover', 'contain']), + borderStyle: PropTypes.string, + className: PropTypes.string, desc: PropTypes.string, + dragHandler: PropTypes.shape({isDraggedOver: PropTypes.bool, setRef: PropTypes.func}), + errors: PropTypes.arrayOf(PropTypes.shape({message: PropTypes.string})), icon: PropTypes.string, - size: PropTypes.string, - type: PropTypes.oneOf(['image', 'button']), - borderStyle: PropTypes.string, + imgClassName: PropTypes.string, + isEditing: PropTypes.bool, + isLoading: PropTypes.bool, + isPinturaEnabled: PropTypes.bool, mimeTypes: PropTypes.arrayOf(PropTypes.string), onFileChange: PropTypes.func, - dragHandler: PropTypes.shape({ - isDraggedOver: PropTypes.bool, - setRef: PropTypes.func - }), - isLoading: PropTypes.bool, + onRemoveMedia: PropTypes.func, progress: PropTypes.number, - errors: PropTypes.arrayOf(PropTypes.shape({ - message: PropTypes.string - })), - onRemoveMedia: PropTypes.func + setFileInputRef: PropTypes.func, + size: PropTypes.string, + src: PropTypes.string, + type: PropTypes.oneOf(['image', 'button']) }; From ba2fc9567149312e01d32b21a410249029ae2e5d Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 10:18:47 +0000 Subject: [PATCH 33/41] avoid global Error override in MediaPlaceholderBeta.stories --- .../src/components/ui/MediaPlaceholderBeta.stories.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/MediaPlaceholderBeta.stories.jsx b/packages/koenig-lexical/src/components/ui/MediaPlaceholderBeta.stories.jsx index c0c073e0c8..3cb3163406 100644 --- a/packages/koenig-lexical/src/components/ui/MediaPlaceholderBeta.stories.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaPlaceholderBeta.stories.jsx @@ -81,8 +81,8 @@ Product.args = { borderStyle: 'squared' }; -export const Error = Template.bind({}); -Error.args = { +export const ErrorState = Template.bind({}); +ErrorState.args = { icon: 'video', desc: 'Click to select a video', size: 'medium', From 5c48803fe195c6ed33339ab79f50464f9e6ede04 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 10:22:18 +0000 Subject: [PATCH 34/41] renamed IconButton export in ButtonGroupBeta to avoid duplicate naming --- packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx | 4 ++-- .../src/components/ui/ButtonGroupBeta.stories.jsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx index 8f0b1026b9..6386ac0cca 100644 --- a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx @@ -9,7 +9,7 @@ export function ButtonGroupBeta({buttons = [], selectedName, onClick, hasTooltip
        {buttons.map(({label, name, Icon, dataTestId, ariaLabel}) => ( - Date: Mon, 3 Mar 2025 10:22:55 +0000 Subject: [PATCH 35/41] added missing prop types to ButtonGroupBeta --- .../src/components/ui/ButtonGroupBeta.jsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx index 6386ac0cca..72f3975c28 100644 --- a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx @@ -50,5 +50,13 @@ export function ButtonGroupIconButton({dataTestId, onClick, label, ariaLabel, na ButtonGroupBeta.propTypes = { selectedName: PropTypes.oneOf(['regular', 'wide', 'full', 'split', 'center', 'left', 'small', 'medium', 'large', 'grid', 'list', 'minimal', 'immersive']), - hasTooltip: PropTypes.bool + hasTooltip: PropTypes.bool, + onClick: PropTypes.func, + buttons: PropTypes.arrayOf(PropTypes.shape({ + label: PropTypes.string, + name: PropTypes.string, + Icon: PropTypes.func, + dataTestId: PropTypes.string, + ariaLabel: PropTypes.string + })) }; From 467aee873e826d846673ff2e92d6f8bae6d98fa8 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 10:27:54 +0000 Subject: [PATCH 36/41] added default fn for `onRemoveMedia` prop to avoid errors if omitted --- packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx b/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx index 1a8460f52c..98f9c99d34 100644 --- a/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx @@ -29,7 +29,7 @@ export function MediaUploaderBeta({ openImageEditor, progress, errors, - onRemoveMedia, + onRemoveMedia = () => {}, additionalActions, setFileInputRef }) { From 993d118529ed961d25393459adb9111138092bf3 Mon Sep 17 00:00:00 2001 From: Sanne de Vries Date: Mon, 3 Mar 2025 11:47:48 +0100 Subject: [PATCH 37/41] Added a divider to call-to-action card settings panel No ref - Added a divider to separate the button settings from the other design settings --- .../koenig-lexical/src/components/ui/cards/CallToActionCard.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx index cdf226104d..e37c5897ec 100644 --- a/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx +++ b/packages/koenig-lexical/src/components/ui/cards/CallToActionCard.jsx @@ -178,6 +178,7 @@ export function CallToActionCard({ onFileChange={onFileChange} onRemoveMedia={onRemoveMedia} /> +
        {/* Button settings */} Date: Mon, 3 Mar 2025 10:53:28 +0000 Subject: [PATCH 38/41] added isRequired to required props in ButtonGroupBeta --- packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx index 72f3975c28..760537e49f 100644 --- a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx @@ -49,9 +49,9 @@ export function ButtonGroupIconButton({dataTestId, onClick, label, ariaLabel, na } ButtonGroupBeta.propTypes = { - selectedName: PropTypes.oneOf(['regular', 'wide', 'full', 'split', 'center', 'left', 'small', 'medium', 'large', 'grid', 'list', 'minimal', 'immersive']), + selectedName: PropTypes.oneOf(['regular', 'wide', 'full', 'split', 'center', 'left', 'small', 'medium', 'large', 'grid', 'list', 'minimal', 'immersive']).isRequired, hasTooltip: PropTypes.bool, - onClick: PropTypes.func, + onClick: PropTypes.func.isRequired, buttons: PropTypes.arrayOf(PropTypes.shape({ label: PropTypes.string, name: PropTypes.string, From c18f8de28c80a28988e5c659b35a65a5f06dcbf1 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 10:54:05 +0000 Subject: [PATCH 39/41] fixed incorrect borderStyle conditionals in MediaUploaderBeta --- .../koenig-lexical/src/components/ui/MediaUploaderBeta.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx b/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx index 98f9c99d34..874523b2b4 100644 --- a/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx @@ -78,11 +78,11 @@ export function MediaUploaderBeta({ } return ( -
        +
        {src && ( <> {alt} -
        +
        )} @@ -119,7 +119,7 @@ MediaUploaderBeta.propTypes = { additionalActions: PropTypes.node, alt: PropTypes.string, backgroundSize: PropTypes.oneOf(['cover', 'contain']), - borderStyle: PropTypes.string, + borderStyle: PropTypes.oneOf(['squared', 'rounded']), className: PropTypes.string, desc: PropTypes.string, dragHandler: PropTypes.shape({isDraggedOver: PropTypes.bool, setRef: PropTypes.func}), From 044dd2b2ec72afcbefa2cb773244b08060b6cebf Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 10:56:32 +0000 Subject: [PATCH 40/41] added missing openImageEditor prop type to MediaUploaderBeta --- packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx b/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx index 874523b2b4..4b25343776 100644 --- a/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/MediaUploaderBeta.jsx @@ -132,6 +132,7 @@ MediaUploaderBeta.propTypes = { mimeTypes: PropTypes.arrayOf(PropTypes.string), onFileChange: PropTypes.func, onRemoveMedia: PropTypes.func, + openImageEditor: PropTypes.func, progress: PropTypes.number, setFileInputRef: PropTypes.func, size: PropTypes.string, From 44e0a2989f47b566f331154d4724193fca8f26c2 Mon Sep 17 00:00:00 2001 From: Kevin Ansfield Date: Mon, 3 Mar 2025 11:21:15 +0000 Subject: [PATCH 41/41] added roles and aria-checked to ButtonGroupBeta elements --- .../koenig-lexical/src/components/ui/ButtonGroupBeta.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx index 760537e49f..d6361b8712 100644 --- a/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx +++ b/packages/koenig-lexical/src/components/ui/ButtonGroupBeta.jsx @@ -7,7 +7,7 @@ import {usePreviousFocus} from '../../hooks/usePreviousFocus'; export function ButtonGroupBeta({buttons = [], selectedName, onClick, hasTooltip = true}) { return (
        -
          +
            {buttons.map(({label, name, Icon, dataTestId, ariaLabel}) => (
    - + ${dataset.buttonText}