mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 14:43:08 +03:00
AdminX design fixes (#18029)
refs. https://github.com/TryGhost/Product/issues/3349 - Fixed change theme responsive issues - Added marketplace link to themes - Installed theme refinements - Added current theme indicator - Improved disabled textfield a bit
This commit is contained in:
parent
6dc1d08590
commit
c1cc0b59f2
@ -1,5 +1,6 @@
|
||||
import Button from './Button';
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
export type BreadcrumbItem = {
|
||||
label: React.ReactNode;
|
||||
@ -10,35 +11,58 @@ interface BreadcrumbsProps {
|
||||
items: BreadcrumbItem[];
|
||||
backIcon?: boolean;
|
||||
onBack?: () => void;
|
||||
containerClassName?: string;
|
||||
itemClassName?: string;
|
||||
activeItemClassName?: string;
|
||||
separatorClassName?: string;
|
||||
}
|
||||
|
||||
const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
|
||||
items,
|
||||
backIcon = false,
|
||||
onBack
|
||||
onBack,
|
||||
containerClassName,
|
||||
itemClassName,
|
||||
activeItemClassName,
|
||||
separatorClassName
|
||||
}) => {
|
||||
const allItems = items.length;
|
||||
let i = 0;
|
||||
|
||||
containerClassName = clsx(
|
||||
'flex items-center gap-2 text-sm',
|
||||
containerClassName
|
||||
);
|
||||
|
||||
activeItemClassName = clsx(
|
||||
'font-bold',
|
||||
activeItemClassName
|
||||
);
|
||||
|
||||
itemClassName = clsx(
|
||||
'text-sm',
|
||||
itemClassName
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='flex items-center gap-2 text-sm'>
|
||||
<div className={containerClassName}>
|
||||
{backIcon &&
|
||||
<Button className='mr-6' icon='arrow-left' size='sm' link onClick={onBack} />
|
||||
}
|
||||
{items.map((item) => {
|
||||
const bcItem = (i === allItems - 1 ?
|
||||
<span className='font-bold'>{item.label}</span>
|
||||
<span className={activeItemClassName}>{item.label}</span>
|
||||
:
|
||||
<>
|
||||
<button
|
||||
key={`bc-${i}`}
|
||||
className={` text-sm ${item.onClick && '-mx-1 cursor-pointer rounded-sm px-1 py-px hover:bg-grey-100'}`}
|
||||
className={`${itemClassName} ${item.onClick && '-mx-1 cursor-pointer rounded-sm px-1 py-px hover:bg-grey-100'}`}
|
||||
type="button"
|
||||
onClick={item.onClick}
|
||||
>
|
||||
{item.label}
|
||||
</button>
|
||||
<span>/</span>
|
||||
<span className={separatorClassName}>/</span>
|
||||
</>);
|
||||
i = i + 1;
|
||||
return bcItem;
|
||||
|
@ -30,6 +30,14 @@ export const Default: Story = {
|
||||
}
|
||||
};
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
placeholder: `Here's a disabled field`,
|
||||
title: 'Disabled',
|
||||
disabled: true
|
||||
}
|
||||
};
|
||||
|
||||
export const ClearBackground: Story = {
|
||||
args: {
|
||||
placeholder: 'Enter something',
|
||||
|
@ -61,7 +61,7 @@ const TextField: React.FC<TextFieldProps> = ({
|
||||
clearBg ? 'bg-transparent' : 'bg-grey-75 px-[10px]',
|
||||
error && border ? `border-red` : `${disabled ? disabledBorderClasses : enabledBorderClasses}`,
|
||||
(title && !hideTitle && !clearBg) && `mt-2`,
|
||||
(disabled ? 'text-grey-700' : ''),
|
||||
(disabled ? 'cursor-not-allowed text-grey-700' : ''),
|
||||
rightPlaceholder && 'w-0 grow',
|
||||
className
|
||||
);
|
||||
@ -106,9 +106,14 @@ const TextField: React.FC<TextFieldProps> = ({
|
||||
hintClassName
|
||||
);
|
||||
|
||||
containerClassName = clsx(
|
||||
'flex flex-col',
|
||||
containerClassName
|
||||
);
|
||||
|
||||
if (title || hint) {
|
||||
return (
|
||||
<div className={`flex flex-col ${containerClassName}`}>
|
||||
<div className={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={hintClassName} color={error ? 'red' : ''}>{hint}</Hint>}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 336 KiB |
@ -15,6 +15,7 @@ import {PreviewModalContent} from '../../../admin-x-ds/global/modal/PreviewModal
|
||||
import {Setting, SettingValue, getSettingValues, useEditSettings} from '../../../api/settings';
|
||||
import {getHomepageUrl} from '../../../api/site';
|
||||
import {useBrowsePosts} from '../../../api/posts';
|
||||
import {useBrowseThemes} from '../../../api/themes';
|
||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||
|
||||
const Sidebar: React.FC<{
|
||||
@ -36,6 +37,9 @@ const Sidebar: React.FC<{
|
||||
}) => {
|
||||
const {updateRoute} = useRouting();
|
||||
const [selectedTab, setSelectedTab] = useState('brand');
|
||||
const {data: {themes} = {}} = useBrowseThemes();
|
||||
|
||||
const activeTheme = themes?.find(theme => theme.active);
|
||||
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
@ -67,7 +71,10 @@ const Sidebar: React.FC<{
|
||||
modal.remove();
|
||||
updateRoute('design/edit/themes');
|
||||
}}>
|
||||
Change theme
|
||||
<div className='text-left'>
|
||||
<div className='font-semibold'>Change theme</div>
|
||||
<div className='font-sm text-grey-700'>Current theme: {activeTheme?.name}</div>
|
||||
</div>
|
||||
<Icon className='mr-2 transition-all group-hover:translate-x-2' name='chevron-right' size='sm' />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -114,26 +114,31 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
||||
|
||||
const left =
|
||||
<Breadcrumbs
|
||||
activeItemClassName='hidden md:!block md:!visible'
|
||||
itemClassName='hidden md:!block md:!visible'
|
||||
items={[
|
||||
{label: 'Design', onClick: onClose},
|
||||
{label: 'Change theme'}
|
||||
]}
|
||||
separatorClassName='hidden md:!block md:!visible'
|
||||
backIcon
|
||||
onBack={onClose}
|
||||
/>;
|
||||
|
||||
const right =
|
||||
<div className='flex items-center gap-14'>
|
||||
<TabView
|
||||
border={false}
|
||||
selectedTab={currentTab}
|
||||
tabs={[
|
||||
{id: 'official', title: 'Official themes'},
|
||||
{id: 'installed', title: 'Installed'}
|
||||
]}
|
||||
onTabChange={(id: string) => {
|
||||
setCurrentTab(id);
|
||||
}} />
|
||||
<div className='hidden md:!visible md:!block'>
|
||||
<TabView
|
||||
border={false}
|
||||
selectedTab={currentTab}
|
||||
tabs={[
|
||||
{id: 'official', title: 'Official themes'},
|
||||
{id: 'installed', title: 'Installed'}
|
||||
]}
|
||||
onTabChange={(id: string) => {
|
||||
setCurrentTab(id);
|
||||
}} />
|
||||
</div>
|
||||
<div className='flex items-center gap-3'>
|
||||
{uploadConfig && (
|
||||
uploadConfig.enabled ?
|
||||
@ -176,7 +181,21 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
||||
</div>
|
||||
</div>;
|
||||
|
||||
return <PageHeader containerClassName='bg-white' left={left} right={right} />;
|
||||
return (<>
|
||||
<PageHeader containerClassName='bg-white' left={left} right={right} />
|
||||
<div className='px-[8vmin] md:hidden'>
|
||||
<TabView
|
||||
border={false}
|
||||
selectedTab={currentTab}
|
||||
tabs={[
|
||||
{id: 'official', title: 'Official themes'},
|
||||
{id: 'installed', title: 'Installed'}
|
||||
]}
|
||||
onTabChange={(id: string) => {
|
||||
setCurrentTab(id);
|
||||
}} />
|
||||
</div>
|
||||
</>);
|
||||
};
|
||||
|
||||
const ThemeModalContent: React.FC<ThemeModalContentProps> = ({
|
||||
@ -272,6 +291,7 @@ const ChangeThemeModal = NiceModal.create(() => {
|
||||
afterClose={() => {
|
||||
updateRoute('design/edit');
|
||||
}}
|
||||
animate={false}
|
||||
cancelLabel=''
|
||||
footer={false}
|
||||
padding={false}
|
||||
@ -307,11 +327,13 @@ const ChangeThemeModal = NiceModal.create(() => {
|
||||
setSelectedTheme={setSelectedTheme}
|
||||
themes={themes}
|
||||
/>
|
||||
<ThemeModalContent
|
||||
currentTab={currentTab}
|
||||
themes={themes}
|
||||
onSelectTheme={onSelectTheme}
|
||||
/>
|
||||
{!selectedTheme &&
|
||||
<ThemeModalContent
|
||||
currentTab={currentTab}
|
||||
themes={themes}
|
||||
onSelectTheme={onSelectTheme}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
@ -24,14 +24,14 @@ function getThemeLabel(theme: Theme): React.ReactNode {
|
||||
label += ' (default)';
|
||||
} else if (theme.package?.name !== theme.name) {
|
||||
label =
|
||||
<>
|
||||
<span className='text-sm md:text-base'>
|
||||
{label} <span className='text-grey-600'>({theme.name})</span>
|
||||
</>;
|
||||
</span>;
|
||||
}
|
||||
|
||||
if (isActiveTheme(theme)) {
|
||||
label =
|
||||
<span className="font-bold">
|
||||
<span className="text-sm font-bold md:text-base">
|
||||
{label} — <span className='text-green'> Active</span>
|
||||
</span>;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import Heading from '../../../../admin-x-ds/global/Heading';
|
||||
import MarketplaceBgImage from '../../../../assets/images/footer-marketplace-bg.png';
|
||||
import ModalPage from '../../../../admin-x-ds/global/modal/ModalPage';
|
||||
import React from 'react';
|
||||
import {OfficialTheme, useOfficialThemes} from '../../../providers/ServiceProvider';
|
||||
@ -36,6 +37,13 @@ const OfficialThemes: React.FC<{
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className='mx-[-8vmin] mb-[-8vmin] mt-[8vmin] bg-black px-[8vmin] py-16 text-center text-lg text-white' style={
|
||||
{
|
||||
background: `#15171a url(${MarketplaceBgImage}) 100% 100% / 35vw no-repeat`
|
||||
}
|
||||
}>
|
||||
Find and buy third-party, premium themes from independent developers in the <a className='inline-block font-semibold text-lime' href="https://ghost.org/themes/" rel="noopener noreferrer" target="_blank">Ghost Marketplace →</a>
|
||||
</div>
|
||||
</ModalPage>
|
||||
);
|
||||
};
|
||||
|
@ -59,11 +59,14 @@ const ThemePreview: React.FC<{
|
||||
const left =
|
||||
<div className='flex items-center gap-2'>
|
||||
<Breadcrumbs
|
||||
activeItemClassName='hidden md:!block md:!visible'
|
||||
itemClassName='hidden md:!block md:!visible'
|
||||
items={[
|
||||
{label: 'Design', onClick: onClose},
|
||||
{label: 'Change theme', onClick: onBack},
|
||||
{label: selectedTheme.name}
|
||||
]}
|
||||
separatorClassName='hidden md:!block md:!visible'
|
||||
backIcon
|
||||
onBack={onBack}
|
||||
/>
|
||||
|
Loading…
Reference in New Issue
Block a user