mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05: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
9 changed files with 104 additions and 27 deletions
|
@ -1,5 +1,6 @@
|
||||||
import Button from './Button';
|
import Button from './Button';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import clsx from 'clsx';
|
||||||
|
|
||||||
export type BreadcrumbItem = {
|
export type BreadcrumbItem = {
|
||||||
label: React.ReactNode;
|
label: React.ReactNode;
|
||||||
|
@ -10,35 +11,58 @@ interface BreadcrumbsProps {
|
||||||
items: BreadcrumbItem[];
|
items: BreadcrumbItem[];
|
||||||
backIcon?: boolean;
|
backIcon?: boolean;
|
||||||
onBack?: () => void;
|
onBack?: () => void;
|
||||||
|
containerClassName?: string;
|
||||||
|
itemClassName?: string;
|
||||||
|
activeItemClassName?: string;
|
||||||
|
separatorClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
|
const Breadcrumbs: React.FC<BreadcrumbsProps> = ({
|
||||||
items,
|
items,
|
||||||
backIcon = false,
|
backIcon = false,
|
||||||
onBack
|
onBack,
|
||||||
|
containerClassName,
|
||||||
|
itemClassName,
|
||||||
|
activeItemClassName,
|
||||||
|
separatorClassName
|
||||||
}) => {
|
}) => {
|
||||||
const allItems = items.length;
|
const allItems = items.length;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
|
containerClassName = clsx(
|
||||||
|
'flex items-center gap-2 text-sm',
|
||||||
|
containerClassName
|
||||||
|
);
|
||||||
|
|
||||||
|
activeItemClassName = clsx(
|
||||||
|
'font-bold',
|
||||||
|
activeItemClassName
|
||||||
|
);
|
||||||
|
|
||||||
|
itemClassName = clsx(
|
||||||
|
'text-sm',
|
||||||
|
itemClassName
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex items-center gap-2 text-sm'>
|
<div className={containerClassName}>
|
||||||
{backIcon &&
|
{backIcon &&
|
||||||
<Button className='mr-6' icon='arrow-left' size='sm' link onClick={onBack} />
|
<Button className='mr-6' icon='arrow-left' size='sm' link onClick={onBack} />
|
||||||
}
|
}
|
||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
const bcItem = (i === allItems - 1 ?
|
const bcItem = (i === allItems - 1 ?
|
||||||
<span className='font-bold'>{item.label}</span>
|
<span className={activeItemClassName}>{item.label}</span>
|
||||||
:
|
:
|
||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
key={`bc-${i}`}
|
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"
|
type="button"
|
||||||
onClick={item.onClick}
|
onClick={item.onClick}
|
||||||
>
|
>
|
||||||
{item.label}
|
{item.label}
|
||||||
</button>
|
</button>
|
||||||
<span>/</span>
|
<span className={separatorClassName}>/</span>
|
||||||
</>);
|
</>);
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
return bcItem;
|
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 = {
|
export const ClearBackground: Story = {
|
||||||
args: {
|
args: {
|
||||||
placeholder: 'Enter something',
|
placeholder: 'Enter something',
|
||||||
|
|
|
@ -61,7 +61,7 @@ const TextField: React.FC<TextFieldProps> = ({
|
||||||
clearBg ? 'bg-transparent' : 'bg-grey-75 px-[10px]',
|
clearBg ? 'bg-transparent' : 'bg-grey-75 px-[10px]',
|
||||||
error && border ? `border-red` : `${disabled ? disabledBorderClasses : enabledBorderClasses}`,
|
error && border ? `border-red` : `${disabled ? disabledBorderClasses : enabledBorderClasses}`,
|
||||||
(title && !hideTitle && !clearBg) && `mt-2`,
|
(title && !hideTitle && !clearBg) && `mt-2`,
|
||||||
(disabled ? 'text-grey-700' : ''),
|
(disabled ? 'cursor-not-allowed text-grey-700' : ''),
|
||||||
rightPlaceholder && 'w-0 grow',
|
rightPlaceholder && 'w-0 grow',
|
||||||
className
|
className
|
||||||
);
|
);
|
||||||
|
@ -106,9 +106,14 @@ const TextField: React.FC<TextFieldProps> = ({
|
||||||
hintClassName
|
hintClassName
|
||||||
);
|
);
|
||||||
|
|
||||||
|
containerClassName = clsx(
|
||||||
|
'flex flex-col',
|
||||||
|
containerClassName
|
||||||
|
);
|
||||||
|
|
||||||
if (title || hint) {
|
if (title || hint) {
|
||||||
return (
|
return (
|
||||||
<div className={`flex flex-col ${containerClassName}`}>
|
<div className={containerClassName}>
|
||||||
{field}
|
{field}
|
||||||
{title && <Heading className={hideTitle ? 'sr-only' : 'order-1 !text-grey-700 peer-focus:!text-black'} htmlFor={id} useLabelTag={true}>{title}</Heading>}
|
{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>}
|
{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 {Setting, SettingValue, getSettingValues, useEditSettings} from '../../../api/settings';
|
||||||
import {getHomepageUrl} from '../../../api/site';
|
import {getHomepageUrl} from '../../../api/site';
|
||||||
import {useBrowsePosts} from '../../../api/posts';
|
import {useBrowsePosts} from '../../../api/posts';
|
||||||
|
import {useBrowseThemes} from '../../../api/themes';
|
||||||
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
import {useGlobalData} from '../../providers/GlobalDataProvider';
|
||||||
|
|
||||||
const Sidebar: React.FC<{
|
const Sidebar: React.FC<{
|
||||||
|
@ -36,6 +37,9 @@ const Sidebar: React.FC<{
|
||||||
}) => {
|
}) => {
|
||||||
const {updateRoute} = useRouting();
|
const {updateRoute} = useRouting();
|
||||||
const [selectedTab, setSelectedTab] = useState('brand');
|
const [selectedTab, setSelectedTab] = useState('brand');
|
||||||
|
const {data: {themes} = {}} = useBrowseThemes();
|
||||||
|
|
||||||
|
const activeTheme = themes?.find(theme => theme.active);
|
||||||
|
|
||||||
const tabs: Tab[] = [
|
const tabs: Tab[] = [
|
||||||
{
|
{
|
||||||
|
@ -67,7 +71,10 @@ const Sidebar: React.FC<{
|
||||||
modal.remove();
|
modal.remove();
|
||||||
updateRoute('design/edit/themes');
|
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' />
|
<Icon className='mr-2 transition-all group-hover:translate-x-2' name='chevron-right' size='sm' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -114,26 +114,31 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
||||||
|
|
||||||
const left =
|
const left =
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
|
activeItemClassName='hidden md:!block md:!visible'
|
||||||
|
itemClassName='hidden md:!block md:!visible'
|
||||||
items={[
|
items={[
|
||||||
{label: 'Design', onClick: onClose},
|
{label: 'Design', onClick: onClose},
|
||||||
{label: 'Change theme'}
|
{label: 'Change theme'}
|
||||||
]}
|
]}
|
||||||
|
separatorClassName='hidden md:!block md:!visible'
|
||||||
backIcon
|
backIcon
|
||||||
onBack={onClose}
|
onBack={onClose}
|
||||||
/>;
|
/>;
|
||||||
|
|
||||||
const right =
|
const right =
|
||||||
<div className='flex items-center gap-14'>
|
<div className='flex items-center gap-14'>
|
||||||
<TabView
|
<div className='hidden md:!visible md:!block'>
|
||||||
border={false}
|
<TabView
|
||||||
selectedTab={currentTab}
|
border={false}
|
||||||
tabs={[
|
selectedTab={currentTab}
|
||||||
{id: 'official', title: 'Official themes'},
|
tabs={[
|
||||||
{id: 'installed', title: 'Installed'}
|
{id: 'official', title: 'Official themes'},
|
||||||
]}
|
{id: 'installed', title: 'Installed'}
|
||||||
onTabChange={(id: string) => {
|
]}
|
||||||
setCurrentTab(id);
|
onTabChange={(id: string) => {
|
||||||
}} />
|
setCurrentTab(id);
|
||||||
|
}} />
|
||||||
|
</div>
|
||||||
<div className='flex items-center gap-3'>
|
<div className='flex items-center gap-3'>
|
||||||
{uploadConfig && (
|
{uploadConfig && (
|
||||||
uploadConfig.enabled ?
|
uploadConfig.enabled ?
|
||||||
|
@ -176,7 +181,21 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</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> = ({
|
const ThemeModalContent: React.FC<ThemeModalContentProps> = ({
|
||||||
|
@ -272,6 +291,7 @@ const ChangeThemeModal = NiceModal.create(() => {
|
||||||
afterClose={() => {
|
afterClose={() => {
|
||||||
updateRoute('design/edit');
|
updateRoute('design/edit');
|
||||||
}}
|
}}
|
||||||
|
animate={false}
|
||||||
cancelLabel=''
|
cancelLabel=''
|
||||||
footer={false}
|
footer={false}
|
||||||
padding={false}
|
padding={false}
|
||||||
|
@ -307,11 +327,13 @@ const ChangeThemeModal = NiceModal.create(() => {
|
||||||
setSelectedTheme={setSelectedTheme}
|
setSelectedTheme={setSelectedTheme}
|
||||||
themes={themes}
|
themes={themes}
|
||||||
/>
|
/>
|
||||||
<ThemeModalContent
|
{!selectedTheme &&
|
||||||
currentTab={currentTab}
|
<ThemeModalContent
|
||||||
themes={themes}
|
currentTab={currentTab}
|
||||||
onSelectTheme={onSelectTheme}
|
themes={themes}
|
||||||
/>
|
onSelectTheme={onSelectTheme}
|
||||||
|
/>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -24,14 +24,14 @@ function getThemeLabel(theme: Theme): React.ReactNode {
|
||||||
label += ' (default)';
|
label += ' (default)';
|
||||||
} else if (theme.package?.name !== theme.name) {
|
} else if (theme.package?.name !== theme.name) {
|
||||||
label =
|
label =
|
||||||
<>
|
<span className='text-sm md:text-base'>
|
||||||
{label} <span className='text-grey-600'>({theme.name})</span>
|
{label} <span className='text-grey-600'>({theme.name})</span>
|
||||||
</>;
|
</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActiveTheme(theme)) {
|
if (isActiveTheme(theme)) {
|
||||||
label =
|
label =
|
||||||
<span className="font-bold">
|
<span className="text-sm font-bold md:text-base">
|
||||||
{label} — <span className='text-green'> Active</span>
|
{label} — <span className='text-green'> Active</span>
|
||||||
</span>;
|
</span>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import Heading from '../../../../admin-x-ds/global/Heading';
|
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 ModalPage from '../../../../admin-x-ds/global/modal/ModalPage';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {OfficialTheme, useOfficialThemes} from '../../../providers/ServiceProvider';
|
import {OfficialTheme, useOfficialThemes} from '../../../providers/ServiceProvider';
|
||||||
|
@ -36,6 +37,13 @@ const OfficialThemes: React.FC<{
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</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>
|
</ModalPage>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -59,11 +59,14 @@ const ThemePreview: React.FC<{
|
||||||
const left =
|
const left =
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
<Breadcrumbs
|
<Breadcrumbs
|
||||||
|
activeItemClassName='hidden md:!block md:!visible'
|
||||||
|
itemClassName='hidden md:!block md:!visible'
|
||||||
items={[
|
items={[
|
||||||
{label: 'Design', onClick: onClose},
|
{label: 'Design', onClick: onClose},
|
||||||
{label: 'Change theme', onClick: onBack},
|
{label: 'Change theme', onClick: onBack},
|
||||||
{label: selectedTheme.name}
|
{label: selectedTheme.name}
|
||||||
]}
|
]}
|
||||||
|
separatorClassName='hidden md:!block md:!visible'
|
||||||
backIcon
|
backIcon
|
||||||
onBack={onBack}
|
onBack={onBack}
|
||||||
/>
|
/>
|
||||||
|
|
Loading…
Add table
Reference in a new issue