-
-
Notifications
You must be signed in to change notification settings - Fork 69
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added new CTA card component (#1410)
* Added new CTA card component Ref https://linear.app/ghost/issue/PLG-309/add-static-html-for-new-cta-card - This is a static card that will form the basis of the new CTA card * Added background toggle to CTA card Ref https://linear.app/ghost/issue/PLG-309/add-static-html-for-new-cta-card - This adds a basic background toggle that will give us something to work with * Add sponsor label toggle to CTA card Ref https://linear.app/ghost/issue/PLG-309/add-static-html-for-new-cta-card * Added image toggle to CTA card Ref https://linear.app/ghost/issue/PLG-309/add-static-html-for-new-cta-card * Added layout options to CTA card Ref https://linear.app/ghost/issue/PLG-309/add-static-html-for-new-cta-card * Centered text in immersive CTA card Ref https://linear.app/ghost/issue/PLG-309/add-static-html-for-new-cta-card
- Loading branch information
Showing
2 changed files
with
295 additions
and
0 deletions.
There are no files selected for viewing
187 changes: 187 additions & 0 deletions
187
packages/koenig-lexical/src/components/ui/cards/CtaCard.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
import CenterAlignIcon from '../../../assets/icons/kg-align-center.svg?react'; | ||
import KoenigNestedEditor from '../../KoenigNestedEditor'; | ||
import LeftAlignIcon from '../../../assets/icons/kg-align-left.svg?react'; | ||
import PropTypes from 'prop-types'; | ||
import React from 'react'; | ||
import ReplacementStringsPlugin from '../../../plugins/ReplacementStringsPlugin'; | ||
import {Button} from '../Button'; | ||
import {ButtonGroupSetting, InputSetting, InputUrlSetting, SettingsPanel, ToggleSetting} from '../SettingsPanel'; | ||
import {ReadOnlyOverlay} from '../ReadOnlyOverlay'; | ||
|
||
export function CtaCard({ | ||
buttonText, | ||
buttonUrl, | ||
hasBackground, | ||
hasImage, | ||
hasSponsorLabel, | ||
htmlEditor, | ||
htmlEditorInitialState, | ||
isEditing, | ||
layout, | ||
showButton, | ||
updateButtonText, | ||
updateButtonUrl, | ||
updateShowButton, | ||
updateHasBackground, | ||
updateHasSponsorLabel, | ||
updateHasImage, | ||
updateLayout | ||
}) { | ||
const layoutOptions = [ | ||
{ | ||
label: 'Minimal', | ||
name: 'minimal', | ||
Icon: LeftAlignIcon, | ||
dataTestId: 'left-align' | ||
}, | ||
{ | ||
label: 'Immersive', | ||
name: 'immersive', | ||
Icon: CenterAlignIcon, | ||
dataTestId: 'immersive' | ||
} | ||
]; | ||
|
||
return ( | ||
<> | ||
<div className={`w-full ${hasBackground ? 'rounded-lg bg-grey-100 dark:bg-grey-900' : ''}`}> | ||
{/* Sponsor label */} | ||
{hasSponsorLabel && ( | ||
<div className={`not-kg-prose py-3 ${hasBackground ? 'mx-5' : ''}`}> | ||
<p className="font-sans text-2xs font-semibold uppercase leading-8 tracking-normal text-grey dark:text-grey-800">Sponsored</p> | ||
</div> | ||
)} | ||
|
||
<div className={`flex ${layout === 'immersive' ? 'flex-col' : 'flex-row'} gap-5 py-5 ${hasSponsorLabel || !hasBackground ? 'border-t border-grey-300 dark:border-grey-800' : ''} ${hasBackground ? 'mx-5' : 'border-b border-grey-300 dark:border-grey-800'}`}> | ||
{hasImage && ( | ||
<div className={`block ${layout === 'immersive' ? 'w-full' : 'w-16 shrink-0'}`}> | ||
<img alt="Placeholder" className={`${layout === 'immersive' ? 'h-auto w-full' : 'aspect-square w-16 object-cover'} rounded-md`} src="https://images.unsplash.com/photo-1511556532299-8f662fc26c06?q=80&w=4431&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" /> | ||
</div> | ||
)} | ||
<div className="flex flex-col gap-5"> | ||
{/* HTML content */} | ||
<KoenigNestedEditor | ||
autoFocus={true} | ||
hasSettingsPanel={true} | ||
initialEditor={htmlEditor} | ||
initialEditorState={htmlEditorInitialState} | ||
nodes='basic' | ||
placeholderClassName={`bg-transparent whitespace-normal font-serif text-xl !text-grey-500 !dark:text-grey-800 ` } | ||
placeholderText="Write something worth clicking..." | ||
textClassName={`w-full bg-transparent whitespace-normal font-serif text-xl text-grey-900 dark:text-grey-200 ${layout === 'immersive' ? 'text-center' : 'text-left'}`} | ||
> | ||
<ReplacementStringsPlugin /> | ||
</KoenigNestedEditor> | ||
|
||
{/* Button */} | ||
{ (showButton && (isEditing || (buttonText && buttonUrl))) && | ||
<div> | ||
<Button | ||
color={'accent'} | ||
dataTestId="cta-button" | ||
placeholder="Add button text" | ||
size={layout === 'immersive' ? 'medium' : 'small'} | ||
value={buttonText} | ||
width={layout === 'immersive' ? 'full' : 'regular'} | ||
/> | ||
</div> | ||
} | ||
</div> | ||
</div> | ||
|
||
{/* Read-only overlay */} | ||
{!isEditing && <ReadOnlyOverlay />} | ||
</div> | ||
|
||
{isEditing && ( | ||
<SettingsPanel> | ||
{/* Layout settings */} | ||
<ButtonGroupSetting | ||
buttons={layoutOptions} | ||
label='Layout' | ||
selectedName={layout} | ||
onClick={updateLayout} | ||
/> | ||
{/* Background setting */} | ||
<ToggleSetting | ||
isChecked={hasBackground} | ||
label='Background' | ||
onChange={updateHasBackground} | ||
/> | ||
{/* Sponsor label setting */} | ||
<ToggleSetting | ||
isChecked={hasSponsorLabel} | ||
label='Sponsor label' | ||
onChange={updateHasSponsorLabel} | ||
/> | ||
{/* Image setting */} | ||
<ToggleSetting | ||
isChecked={hasImage} | ||
label='Image' | ||
onChange={updateHasImage} | ||
/> | ||
{/* Button settings */} | ||
<ToggleSetting | ||
dataTestId="button-settings" | ||
isChecked={showButton} | ||
label='Button' | ||
onChange={updateShowButton} | ||
/> | ||
{showButton && ( | ||
<> | ||
<InputSetting | ||
dataTestId="button-text" | ||
label='Button text' | ||
placeholder='Add button text' | ||
value={buttonText} | ||
onChange={updateButtonText} | ||
/> | ||
<InputUrlSetting | ||
dataTestId="button-url" | ||
label='Button URL' | ||
value={buttonUrl} | ||
onChange={updateButtonUrl} | ||
/> | ||
</> | ||
)} | ||
</SettingsPanel> | ||
)} | ||
</> | ||
); | ||
} | ||
|
||
CtaCard.propTypes = { | ||
buttonText: PropTypes.string, | ||
buttonUrl: PropTypes.string, | ||
hasBackground: PropTypes.bool, | ||
hasImage: PropTypes.bool, | ||
hasSponsorLabel: PropTypes.bool, | ||
isEditing: PropTypes.bool, | ||
layout: PropTypes.oneOf(['minimal', 'immersive']), | ||
showButton: PropTypes.bool, | ||
htmlEditor: PropTypes.object, | ||
htmlEditorInitialState: PropTypes.object, | ||
updateButtonText: PropTypes.func, | ||
updateButtonUrl: PropTypes.func, | ||
updateHasBackground: PropTypes.func, | ||
updateHasSponsorLabel: PropTypes.func, | ||
updateHasImage: PropTypes.func, | ||
updateShowButton: PropTypes.func, | ||
updateLayout: PropTypes.func | ||
}; | ||
|
||
CtaCard.defaultProps = { | ||
buttonText: '', | ||
buttonUrl: '', | ||
hasBackground: false, | ||
hasImage: false, | ||
hasSponsorLabel: false, | ||
isEditing: false, | ||
layout: 'immersive', | ||
showButton: false, | ||
updateHasBackground: () => {}, | ||
updateHasSponsorLabel: () => {}, | ||
updateHasImage: () => {}, | ||
updateShowButton: () => {}, | ||
updateLayout: () => {} | ||
}; |
108 changes: 108 additions & 0 deletions
108
packages/koenig-lexical/src/components/ui/cards/CtaCard.stories.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import EmailIndicatorIcon from '../../../assets/icons/kg-indicator-email.svg?react'; | ||
import React from 'react'; | ||
import populateEditor from '../../../utils/storybook/populate-storybook-editor.js'; | ||
import {BASIC_NODES} from '../../../index.js'; | ||
import {CardWrapper} from './../CardWrapper'; | ||
import {CtaCard} from './CtaCard'; | ||
import {createEditor} from 'lexical'; | ||
|
||
const displayOptions = { | ||
Default: {isSelected: false, isEditing: false}, | ||
Selected: {isSelected: true, isEditing: false}, | ||
Editing: {isSelected: true, isEditing: true} | ||
}; | ||
|
||
const layoutOptions = { | ||
Minimal: 'minimal', | ||
Immersive: 'immersive' | ||
}; | ||
|
||
const story = { | ||
title: 'Primary cards/CTA card', | ||
component: CtaCard, | ||
subcomponent: {CardWrapper}, | ||
argTypes: { | ||
display: { | ||
options: Object.keys(displayOptions), | ||
mapping: displayOptions, | ||
control: { | ||
type: 'radio', | ||
labels: { | ||
Default: 'Default', | ||
Selected: 'Selected', | ||
Editing: 'Editing' | ||
}, | ||
defaultValue: displayOptions.Default | ||
}, | ||
layout: { | ||
options: Object.keys(layoutOptions), | ||
mapping: layoutOptions, | ||
control: { | ||
type: 'radio', | ||
labels: { | ||
Minimal: 'Minimal', | ||
Immersive: 'Immersive' | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
parameters: { | ||
status: { | ||
type: 'uiReady' | ||
} | ||
} | ||
}; | ||
export default story; | ||
|
||
const Template = ({display, value, ...args}) => { | ||
const htmlEditor = createEditor({nodes: BASIC_NODES}); | ||
populateEditor({editor: htmlEditor, initialHtml: `${value}`}); | ||
return ( | ||
<div> | ||
<div className="kg-prose"> | ||
<div className="mx-auto my-8 min-w-[initial] max-w-[740px]"> | ||
<CardWrapper IndicatorIcon={EmailIndicatorIcon} wrapperStyle='wide' {...display} {...args}> | ||
<CtaCard {...display} {...args} htmlEditor={htmlEditor} /> | ||
</CardWrapper> | ||
</div> | ||
</div> | ||
<div className="kg-prose dark bg-black px-4 py-8"> | ||
<div className="mx-auto my-8 min-w-[initial] max-w-[740px]"> | ||
<CardWrapper IndicatorIcon={EmailIndicatorIcon} wrapperStyle='wide' {...display} {...args}> | ||
<CtaCard {...display} {...args} htmlEditor={htmlEditor} /> | ||
</CardWrapper> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
}; | ||
|
||
export const Empty = Template.bind({}); | ||
Empty.args = { | ||
display: 'Editing', | ||
value: '', | ||
showButton: false, | ||
hasBackground: false, | ||
hasImage: false, | ||
hasSponsorLabel: false, | ||
layout: 'immersive', | ||
buttonText: '', | ||
buttonUrl: '', | ||
suggestedUrls: [] | ||
}; | ||
|
||
export const Populated = Template.bind({}); | ||
Populated.args = { | ||
display: 'Editing', | ||
value: 'Want to get access to premium content?', | ||
showButton: true, | ||
hasImage: true, | ||
hasSponsorLabel: true, | ||
hasBackground: false, | ||
layout: 'immersive', | ||
buttonText: 'Upgrade', | ||
buttonUrl: 'https://ghost.org/', | ||
suggestedUrls: [{label: 'Homepage', value: 'https://localhost.org/'}] | ||
}; | ||
|