mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Admin X Tiers Fields (#17332)
refs. https://github.com/TryGhost/Product/issues/3580 - The currency dropdown needed a small version of the regular select - The input field needed a version with a right-side placeholder text
This commit is contained in:
parent
42d87d1437
commit
497d1be2ea
5 changed files with 80 additions and 18 deletions
|
@ -4,7 +4,7 @@ import type {Meta, StoryObj} from '@storybook/react';
|
|||
import Select, {SelectOption} from './Select';
|
||||
|
||||
const meta = {
|
||||
title: 'Global / Simple select',
|
||||
title: 'Global / Form / Select',
|
||||
component: Select,
|
||||
tags: ['autodocs'],
|
||||
decorators: [(_story: any) => (<div style={{maxWidth: '400px'}}>{_story()}</div>)],
|
||||
|
@ -61,6 +61,13 @@ export const WithHint: Story = {
|
|||
}
|
||||
};
|
||||
|
||||
export const ExtraSmall: Story = {
|
||||
args: {
|
||||
options: selectOptions,
|
||||
size: 'xs'
|
||||
}
|
||||
};
|
||||
|
||||
export const WithSelectedOption: Story = {
|
||||
render: function Component(args) {
|
||||
const [, updateArgs] = useArgs();
|
||||
|
|
|
@ -11,6 +11,7 @@ export interface SelectOption {
|
|||
|
||||
export interface SelectProps {
|
||||
title?: string;
|
||||
size?: 'xs' | 'md';
|
||||
prompt?: string;
|
||||
options: SelectOption[];
|
||||
selectedOption?: string
|
||||
|
@ -18,6 +19,7 @@ export interface SelectProps {
|
|||
error?:boolean;
|
||||
hint?: React.ReactNode;
|
||||
clearBg?: boolean;
|
||||
border?: boolean;
|
||||
containerClassName?: string;
|
||||
selectClassName?: string;
|
||||
optionClassName?: string;
|
||||
|
@ -26,6 +28,7 @@ export interface SelectProps {
|
|||
|
||||
const Select: React.FC<SelectProps> = ({
|
||||
title,
|
||||
size = 'md',
|
||||
prompt,
|
||||
options,
|
||||
selectedOption,
|
||||
|
@ -33,6 +36,7 @@ const Select: React.FC<SelectProps> = ({
|
|||
error,
|
||||
hint,
|
||||
clearBg = true,
|
||||
border = true,
|
||||
containerClassName,
|
||||
selectClassName,
|
||||
optionClassName,
|
||||
|
@ -49,7 +53,7 @@ const Select: React.FC<SelectProps> = ({
|
|||
containerClasses = clsx(
|
||||
'relative w-full after:pointer-events-none',
|
||||
`after:absolute after:block after:h-2 after:w-2 after:rotate-45 after:border-[1px] after:border-l-0 after:border-t-0 after:border-grey-900 after:content-['']`,
|
||||
title ? 'after:top-[14px]' : 'after:top-[14px]',
|
||||
size === 'xs' ? 'after:top-[6px]' : 'after:top-[14px]',
|
||||
clearBg ? 'after:right-0' : 'after:right-4'
|
||||
);
|
||||
}
|
||||
|
@ -61,7 +65,9 @@ const Select: React.FC<SelectProps> = ({
|
|||
let selectClasses = '';
|
||||
if (!unstyled) {
|
||||
selectClasses = clsx(
|
||||
'h-10 w-full cursor-pointer appearance-none border-b py-2 pr-5 outline-none',
|
||||
size === 'xs' ? 'h-6 py-0 pr-3 text-xs' : 'h-10 py-2 pr-5',
|
||||
'w-full cursor-pointer appearance-none outline-none',
|
||||
border && 'border-b',
|
||||
!clearBg && 'bg-grey-75 px-[10px]',
|
||||
error ? 'border-red' : 'border-grey-500 hover:border-grey-700 focus:border-black',
|
||||
(title && !clearBg) && 'mt-2'
|
||||
|
|
|
@ -58,6 +58,14 @@ export const WithHint: Story = {
|
|||
}
|
||||
};
|
||||
|
||||
export const WithRightPlaceholder: Story = {
|
||||
args: {
|
||||
title: 'Monthly price',
|
||||
placeholder: '0',
|
||||
rightPlaceholder: 'USD/month'
|
||||
}
|
||||
};
|
||||
|
||||
export const PasswordType: Story = {
|
||||
args: {
|
||||
title: 'Password',
|
||||
|
|
|
@ -12,6 +12,7 @@ export type TextFieldProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
|||
value?: string;
|
||||
error?: boolean;
|
||||
placeholder?: string;
|
||||
rightPlaceholder?: string;
|
||||
hint?: React.ReactNode;
|
||||
clearBg?: boolean;
|
||||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
|
@ -33,6 +34,7 @@ const TextField: React.FC<TextFieldProps> = ({
|
|||
value,
|
||||
error,
|
||||
placeholder,
|
||||
rightPlaceholder,
|
||||
hint,
|
||||
clearBg = true,
|
||||
onChange,
|
||||
|
@ -53,10 +55,13 @@ const TextField: React.FC<TextFieldProps> = ({
|
|||
error ? `border-red` : `${disabled ? 'border-grey-300' : 'border-grey-500 hover:border-grey-700 focus:border-black'}`,
|
||||
(title && !hideTitle && !clearBg) && `mt-2`,
|
||||
(disabled ? 'text-grey-700' : ''),
|
||||
rightPlaceholder && 'peer w-0 grow',
|
||||
className
|
||||
);
|
||||
|
||||
const field = <input
|
||||
let field = <></>;
|
||||
|
||||
const inputField = <input
|
||||
ref={inputRef}
|
||||
className={textFieldClasses || className}
|
||||
disabled={disabled}
|
||||
|
@ -69,6 +74,22 @@ const TextField: React.FC<TextFieldProps> = ({
|
|||
onChange={onChange}
|
||||
{...props} />;
|
||||
|
||||
if (rightPlaceholder) {
|
||||
const rightPHClasses = !unstyled && clsx(
|
||||
'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'}`
|
||||
);
|
||||
|
||||
field = (
|
||||
<div className='flex w-full items-center'>
|
||||
{inputField}
|
||||
<span className={rightPHClasses || ''}>{rightPlaceholder}</span>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
field = inputField;
|
||||
}
|
||||
|
||||
if (title || hint) {
|
||||
let titleGrey = false;
|
||||
if (titleColor === 'auto') {
|
||||
|
|
|
@ -5,6 +5,7 @@ import Icon from '../../../../admin-x-ds/global/Icon';
|
|||
import Modal from '../../../../admin-x-ds/global/modal/Modal';
|
||||
import NiceModal, {useModal} from '@ebay/nice-modal-react';
|
||||
import React from 'react';
|
||||
import Select from '../../../../admin-x-ds/global/form/Select';
|
||||
import SortableList from '../../../../admin-x-ds/global/SortableList';
|
||||
import TextField from '../../../../admin-x-ds/global/form/TextField';
|
||||
import TierDetailPreview from './TierDetailPreview';
|
||||
|
@ -88,22 +89,40 @@ const TierDetailModal: React.FC<TierDetailModalProps> = ({tier}) => {
|
|||
onChange={e => updateForm(state => ({...state, description: e.target.value}))}
|
||||
/>
|
||||
<div className='flex gap-10'>
|
||||
<div className='flex basis-1/2 flex-col gap-2'>
|
||||
<TextField
|
||||
placeholder='1'
|
||||
title='Prices'
|
||||
value={formState.monthly_price}
|
||||
onChange={e => updateForm(state => ({...state, monthly_price: e.target.value.replace(/[^\d.]/, '')}))}
|
||||
/>
|
||||
<TextField
|
||||
placeholder='10'
|
||||
value={formState.yearly_price}
|
||||
onChange={e => updateForm(state => ({...state, yearly_price: e.target.value.replace(/[^\d.]/, '')}))}
|
||||
/>
|
||||
<div className='basis-1/2'>
|
||||
<div className='mb-1 flex h-6 items-center justify-between'>
|
||||
<Heading level={6}>Prices</Heading>
|
||||
<div className='w-10'>
|
||||
<Select
|
||||
border={false}
|
||||
options={[
|
||||
{label: 'USD', value: 'US Dollaz'},
|
||||
{label: 'HUF', value: 'Hungarian Dollaz'}
|
||||
]}
|
||||
selectClassName='font-medium'
|
||||
size='xs'
|
||||
onSelect={() => {}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2'>
|
||||
<TextField
|
||||
placeholder='1'
|
||||
rightPlaceholder='USD/month'
|
||||
value={formState.monthly_price}
|
||||
onChange={e => updateForm(state => ({...state, monthly_price: e.target.value.replace(/[^\d.]/, '')}))}
|
||||
/>
|
||||
<TextField
|
||||
placeholder='10'
|
||||
rightPlaceholder='USD/year'
|
||||
value={formState.yearly_price}
|
||||
onChange={e => updateForm(state => ({...state, yearly_price: e.target.value.replace(/[^\d.]/, '')}))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basis-1/2'>
|
||||
<div className='flex justify-between'>
|
||||
<Heading level={6} grey>Add a free trial</Heading>
|
||||
<div className='mb-1 flex h-6 items-center justify-between'>
|
||||
<Heading level={6}>Add a free trial</Heading>
|
||||
<Toggle onChange={() => {}} />
|
||||
</div>
|
||||
<TextField
|
||||
|
@ -111,6 +130,7 @@ const TierDetailModal: React.FC<TierDetailModalProps> = ({tier}) => {
|
|||
Members will be subscribed at full price once the trial ends. <a href="https://ghost.org/" rel="noreferrer" target="_blank">Learn more</a>
|
||||
</>}
|
||||
placeholder='0'
|
||||
rightPlaceholder='days'
|
||||
value={formState.trial_days}
|
||||
disabled
|
||||
onChange={e => updateForm(state => ({...state, trial_days: e.target.value.replace(/^[\d.]/, '')}))}
|
||||
|
|
Loading…
Add table
Reference in a new issue