Static Tips & Donations settings in AdminX (#17757)

refs. https://github.com/TryGhost/Product/issues/3746

- Added staitc UI for Tips & donations in AdminX settings
This commit is contained in:
Peter Zimon 2023-08-18 08:20:46 +02:00 committed by GitHub
parent b70f58860e
commit 5b896eb0f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 157 additions and 9 deletions

View File

@ -59,7 +59,7 @@ function App({ghostVersion, officialThemes, zapierTemplates, externalNavigate}:
</div>
</div>
<div className="relative flex-auto pt-[3vmin] md:ml-[300px] md:pt-[85px]">
<div className='pointer-events-none fixed inset-x-0 top-0 z-[5] h-[130px] bg-gradient-to-t from-transparent to-white to-60%'></div>
<div className='pointer-events-none fixed inset-x-0 top-0 z-[5] h-[80px] bg-gradient-to-t from-transparent to-white to-60%'></div>
<Settings />
</div>
</div>

View File

@ -2,6 +2,8 @@ import {ReactNode} from 'react';
import {useArgs} from '@storybook/preview-api';
import type {Meta, StoryObj} from '@storybook/react';
import Button from '../Button';
import Select from './Select';
import TextField from './TextField';
const meta = {
@ -12,6 +14,9 @@ const meta = {
argTypes: {
hint: {
control: 'text'
},
rightPlaceholder: {
control: 'text'
}
}
} satisfies Meta<typeof TextField>;
@ -67,6 +72,44 @@ export const WithRightPlaceholder: Story = {
}
};
export const WithoutBorder: Story = {
args: {
title: 'Title',
placeholder: 'Enter something',
hint: 'Here\'s some hint',
border: false
}
};
export const WithDropdown: Story = {
args: {
title: 'Monthly price',
placeholder: '0',
rightPlaceholder: (
<Select
border={false}
options={[
{label: 'USD', value: 'usd'},
{label: 'EUR', value: 'eur'}
]}
selectClassName='w-auto'
onSelect={() => {}}
/>
)
}
};
export const WithButton: Story = {
args: {
title: 'Get this URL',
value: 'https://ghost.org',
containerClassName: 'group',
rightPlaceholder: (
<Button className='invisible mt-2 group-hover:visible' color='white' label='Copy' size='sm' />
)
}
};
export const PasswordType: Story = {
args: {
title: 'Password',

View File

@ -12,7 +12,7 @@ export type TextFieldProps = React.InputHTMLAttributes<HTMLInputElement> & {
value?: string;
error?: boolean;
placeholder?: string;
rightPlaceholder?: string;
rightPlaceholder?: React.ReactNode;
hint?: React.ReactNode;
clearBg?: boolean;
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
@ -23,6 +23,7 @@ export type TextFieldProps = React.InputHTMLAttributes<HTMLInputElement> & {
hintClassName?: string;
unstyled?: boolean;
disabled?: boolean;
border?: boolean;
}
const TextField: React.FC<TextFieldProps> = ({
@ -44,14 +45,20 @@ const TextField: React.FC<TextFieldProps> = ({
hintClassName = '',
unstyled = false,
disabled,
border = true,
...props
}) => {
const id = useId();
const disabledBorderClasses = border && 'border-grey-300';
const enabledBorderClasses = border && 'border-grey-500 hover:border-grey-700 focus:border-black';
const textFieldClasses = !unstyled && clsx(
'peer order-2 h-10 w-full border-b py-2',
'peer order-2 h-10 w-full py-2',
border && 'border-b',
!border && '-mb-1.5',
clearBg ? 'bg-transparent' : 'bg-grey-75 px-[10px]',
error ? `border-red` : `${disabled ? 'border-grey-300' : 'border-grey-500 hover:border-grey-700 focus:border-black'}`,
error && border ? `border-red` : `${disabled ? disabledBorderClasses : enabledBorderClasses}`,
(title && !hideTitle && !clearBg) && `mt-2`,
(disabled ? 'text-grey-700' : ''),
rightPlaceholder && 'w-0 grow',
@ -74,9 +81,13 @@ const TextField: React.FC<TextFieldProps> = ({
{...props} />;
if (rightPlaceholder) {
const rightPHEnabledBorderClasses = 'border-grey-500 peer-hover:border-grey-700 peer-focus:border-black';
const rightPHClasses = !unstyled && clsx(
'order-3 h-10 border-b py-2 text-right text-grey-500',
error ? `border-red` : `${disabled ? 'border-grey-300' : 'border-grey-500 peer-hover:border-grey-700 peer-focus:border-black'}`
'order-3',
border && 'border-b',
!border && '-mb-1.5',
(typeof (rightPlaceholder) === 'string') ? 'h-10 py-2 text-right text-grey-500' : 'h-10',
error && border ? `border-red` : `${disabled ? disabledBorderClasses : rightPHEnabledBorderClasses}`
);
field = (
@ -89,12 +100,17 @@ const TextField: React.FC<TextFieldProps> = ({
field = inputField;
}
hintClassName = clsx(
'order-3',
hintClassName
);
if (title || hint) {
return (
<div className={`flex flex-col ${containerClassName}`}>
{field}
{title && <Heading className={hideTitle ? 'sr-only' : 'order-1 !text-grey-700 peer-focus:!text-black'} htmlFor={id} useLabelTag={true}>{title}</Heading>}
{hint && <Hint className={'order-3' + hintClassName} color={error ? 'red' : ''}>{hint}</Hint>}
{hint && <Hint className={hintClassName} color={error ? 'red' : ''}>{hint}</Hint>}
</div>
);
} else {

View File

@ -135,7 +135,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
useEffect(() => {
if (scrollRef.current) {
const rootElement = document.getElementById('admin-x-settings-content');
const rootRect = rootElement?.getBoundingClientRect();
const rootRect = rootElement?.getBoundingClientRect() || DOMRect.fromRect();
const sectionRect = scrollRef.current.getBoundingClientRect();
setCurrentRect({
top: sectionRect.top - rootRect!.top,

View File

@ -48,6 +48,7 @@ const Sidebar: React.FC = () => {
<SettingNavItem navid='portal' title="Portal" onClick={handleSectionClick} />
<SettingNavItem navid='access' title="Access" onClick={handleSectionClick} />
<SettingNavItem navid='tiers' title="Tiers" onClick={handleSectionClick} />
<SettingNavItem navid='tips-or-donations' title="Tips or donations" onClick={handleSectionClick} />
<SettingNavItem navid='analytics' title="Analytics" onClick={handleSectionClick} />
</SettingNavSection>

View File

@ -4,11 +4,13 @@ import Portal from './Portal';
import React from 'react';
import SettingSection from '../../../admin-x-ds/settings/SettingSection';
import Tiers from './Tiers';
import TipsOrDonations from './TipsOrDonations';
const searchKeywords = {
portal: ['portal', 'signup', 'sign up', 'signin', 'sign in', 'login', 'account', 'membership'],
access: ['default', 'access', 'subscription', 'post', 'membership'],
tiers: ['tiers', 'payment', 'paid'],
access: ['access', 'subscription', 'post', 'membership'],
tips: ['tip', 'donation', 'one time', 'payment'],
analytics: ['analytics', 'tracking', 'privacy', 'membership']
};
@ -18,6 +20,7 @@ const MembershipSettings: React.FC = () => {
<Portal keywords={searchKeywords.portal} />
<Access keywords={searchKeywords.access} />
<Tiers keywords={searchKeywords.tiers} />
<TipsOrDonations keywords={searchKeywords.tips} />
<Analytics keywords={searchKeywords.analytics} />
</SettingSection>
);

View File

@ -0,0 +1,85 @@
import Button from '../../../admin-x-ds/global/Button';
import React from 'react';
import Select from '../../../admin-x-ds/global/form/Select';
import SettingGroup from '../../../admin-x-ds/settings/SettingGroup';
import SettingGroupContent from '../../../admin-x-ds/settings/SettingGroupContent';
import TextField from '../../../admin-x-ds/global/form/TextField';
import useSettingGroup from '../../../hooks/useSettingGroup';
const TipsOrDonations: React.FC<{ keywords: string[] }> = ({keywords}) => {
const {
isEditing,
saveState,
handleSave,
handleCancel,
focusRef,
handleEditingChange
} = useSettingGroup();
const values = (
<SettingGroupContent
columns={2}
values={[
{
heading: 'Suggested amount',
key: 'suggested-amount',
value: '$12'
},
{
heading: 'Sharable link',
key: 'sharable-link',
value: (
<div className='w-100 group relative -m-1 overflow-hidden rounded p-1 hover:bg-grey-50'>
https://example.com/tip
<div className='invisible absolute right-0 top-[50%] flex translate-y-[-50%] gap-1 bg-white pl-1 group-hover:visible'>
<Button color='outline' label='Copy' size='sm' />
</div>
</div>
)
}
]}
/>
);
const inputFields = (
<SettingGroupContent className='max-w-[180px]'>
<TextField
inputRef={focusRef}
placeholder="0"
rightPlaceholder={(
<Select
border={false}
options={[
{label: 'USD', value: 'usd'},
{label: 'EUR', value: 'eur'}
]}
selectClassName='w-auto'
onSelect={() => {}}
/>
)}
title='Suggested amount'
value='12'
onChange={() => {}}
/>
</SettingGroupContent>
);
return (
<SettingGroup
description="Give your audience a one-time way to support your work, no membership required."
isEditing={isEditing}
keywords={keywords}
navid='tips-or-donations'
saveState={saveState}
testId='tips-or-donations'
title="Tips or donations"
onCancel={handleCancel}
onEditingChange={handleEditingChange}
onSave={handleSave}
>
{isEditing ? inputFields : values}
</SettingGroup>
);
};
export default TipsOrDonations;