mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-28 05:37:34 +03:00
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:
parent
b70f58860e
commit
5b896eb0f7
@ -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>
|
||||
|
@ -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',
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -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;
|
Loading…
Reference in New Issue
Block a user