0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-20 22:42:53 -05:00

Added page header component in AdminX

refs. https://github.com/TryGhost/Team/issues/3432
This commit is contained in:
Peter Zimon 2023-06-14 09:12:26 +02:00
parent 52af10fef5
commit 15ec9f0d14
5 changed files with 218 additions and 72 deletions

View file

@ -15,7 +15,7 @@ const preview: Preview = {
options: { options: {
storySort: { storySort: {
mathod: 'alphabetical', mathod: 'alphabetical',
order: ['Global', ['Chrome', 'Form', 'Modal', 'List', '*'], 'Settings', ['Setting Section', 'Setting Group', '*'], 'Experimental'], order: ['Global', ['Chrome', 'Form', 'Modal', 'Layout', 'List', '*'], 'Settings', ['Setting Section', 'Setting Group', '*'], 'Experimental'],
}, },
}, },
}, },

View file

@ -0,0 +1,67 @@
import type {Meta, StoryObj} from '@storybook/react';
import PageHeader from './PageHeader';
const meta = {
title: 'Global / Layout / Page Header',
component: PageHeader,
tags: ['autodocs']
} satisfies Meta<typeof PageHeader>;
export default meta;
type Story = StoryObj<typeof PageHeader>;
export const Default: Story = {
args: {
left: 'Left content',
center: 'Center content',
right: 'Right content'
}
};
export const CustomContainer: Story = {
args: {
left: 'Left content',
center: 'Center content',
right: 'Right content',
containerClassName: 'bg-grey-50'
}
};
export const LeftAndRight: Story = {
args: {
left: 'Left content',
right: 'Right content'
}
};
export const LeftOnly: Story = {
args: {
left: 'Left content'
}
};
export const CenterOnly: Story = {
args: {
center: 'Center content'
}
};
export const RightOnly: Story = {
args: {
right: 'Right content'
}
};
export const CustomContent: Story = {
args: {
children: (
<div className='flex justify-between'>
<div className='basis-1/4'>This</div>
<div className='basis-1/4'>is</div>
<div className='basis-1/4'>custom</div>
<div className='basis-1/4'>content!</div>
</div>
)
}
};

View file

@ -0,0 +1,77 @@
import React from 'react';
import clsx from 'clsx';
interface PageHeaderProps {
/**
* Use these to specifically place elements on the left | center | right of the header.
*/
left?: React.ReactNode;
center?: React.ReactNode;
right?: React.ReactNode;
sticky?: boolean;
containerClassName?: string;
/**
* Or you can simply use the whole container to make sure header spacing is consistent. `children` takes precedence over `left`, `center` and `right`.
*/
children?: React.ReactNode;
}
const PageHeader: React.FC<PageHeaderProps> = ({
left,
center,
right,
sticky = true,
containerClassName,
children
}) => {
const containerClasses = clsx(
'h-[74px] p-5 px-7',
!children && 'flex items-center justify-between gap-3',
sticky && 'sticky top-0',
containerClassName
);
if (!children) {
if (left) {
const leftClasses = clsx(
'flex flex-auto items-center',
(right && center) && 'basis-1/3',
((right && !center) || (!right && center)) && 'basis-1/2'
);
left = <div className={leftClasses}>{left}</div>;
}
if (center) {
const centerClasses = clsx(
'flex flex-auto items-center justify-center',
(left && right) && 'basis-1/3',
((left && !right) || (!left && right)) && 'basis-1/2'
);
center = <div className={centerClasses}>{center}</div>;
}
if (right) {
const rightClasses = clsx(
'flex flex-auto items-center justify-end',
(left && center) && 'basis-1/3',
((left && !center) || (!left && center)) && 'basis-1/2'
);
right = <div className={rightClasses}>{right}</div>;
}
}
return (
<div className={containerClasses}>
{children ? children :
<>
{left}
{center}
{right}
</>
}
</div>
);
};
export default PageHeader;

View file

@ -6,6 +6,7 @@ import Modal from '../../../admin-x-ds/global/modal/Modal';
import NewThemePreview from './theme/ThemePreview'; import NewThemePreview from './theme/ThemePreview';
import NiceModal, {NiceModalHandler, useModal} from '@ebay/nice-modal-react'; import NiceModal, {NiceModalHandler, useModal} from '@ebay/nice-modal-react';
import OfficialThemes from './theme/OfficialThemes'; import OfficialThemes from './theme/OfficialThemes';
import PageHeader from '../../../admin-x-ds/global/layout/PageHeader';
import React, {useState} from 'react'; import React, {useState} from 'react';
import TabView from '../../../admin-x-ds/global/TabView'; import TabView from '../../../admin-x-ds/global/TabView';
import {OfficialTheme} from '../../../models/themes'; import {OfficialTheme} from '../../../models/themes';
@ -40,85 +41,86 @@ const ThemeToolbar: React.FC<ThemeToolbarProps> = ({
setThemes setThemes
}) => { }) => {
const api = useApi(); const api = useApi();
let left, right;
if (selectedTheme) { if (selectedTheme) {
const installedTheme = themes.find(theme => theme.name.toLowerCase() === selectedTheme.name.toLowerCase()); const installedTheme = themes.find(theme => theme.name.toLowerCase() === selectedTheme.name.toLowerCase());
return ( left =
<div className='sticky top-0 flex justify-between gap-3 bg-grey-50 p-5 px-7'> <div className='flex w-[33%] items-center gap-2'>
<div className='flex w-[33%] items-center gap-2'> <button
<button className={`text-sm`}
className={`text-sm`} type="button"
type="button" onClick={() => {
onClick={() => { setCurrentTab('official');
setCurrentTab('official'); setSelectedTheme(null);
setSelectedTheme(null); }}>
}}> Official themes
Official themes </button>
</button> &rarr;
&rarr; <span className='text-sm font-bold'>{selectedTheme?.name}</span>
<span className='text-sm font-bold'>{selectedTheme?.name}</span> </div>;
</div>
<div className='flex w-[33%] justify-end gap-8'> right =
<ButtonGroup <div className='flex w-[33%] justify-end gap-8'>
buttons={[ <ButtonGroup
{icon: 'laptop', link: true, size: 'sm'}, buttons={[
{icon: 'mobile', iconColorClass: 'text-grey-500', link: true, size: 'sm'} {icon: 'laptop', link: true, size: 'sm'},
]} {icon: 'mobile', iconColorClass: 'text-grey-500', link: true, size: 'sm'}
/>
<Button
color='green'
disabled={Boolean(installedTheme)}
label={installedTheme?.active ? 'Activated' : (installedTheme ? 'Installed' : `Install ${selectedTheme?.name}`)}
onClick={async () => {
const data = await api.themes.install(selectedTheme.ref);
const newlyInstalledTheme = data.themes[0];
setThemes([
...themes.map(theme => ({...theme, active: false})),
newlyInstalledTheme
]);
showToast({
message: `Theme installed - ${newlyInstalledTheme.name}`
});
setCurrentTab('installed');
}}
/>
</div>
</div>
);
} else {
return (
<div className='sticky top-0 flex justify-between gap-3 bg-white p-5 px-7'>
<TabView
border={false}
tabs={[
{id: 'official', title: 'Official themes'},
{id: 'installed', title: 'Installed'}
]} ]}
onTabChange={(id: string) => { />
setCurrentTab(id); <Button
color='green'
disabled={Boolean(installedTheme)}
label={installedTheme?.active ? 'Activated' : (installedTheme ? 'Installed' : `Install ${selectedTheme?.name}`)}
onClick={async () => {
const data = await api.themes.install(selectedTheme.ref);
const newlyInstalledTheme = data.themes[0];
setThemes([
...themes.map(theme => ({...theme, active: false})),
newlyInstalledTheme
]);
showToast({
message: `Theme installed - ${newlyInstalledTheme.name}`
});
setCurrentTab('installed');
}} }}
/> />
</div>;
} else {
left =
<TabView
border={false}
tabs={[
{id: 'official', title: 'Official themes'},
{id: 'installed', title: 'Installed'}
]}
onTabChange={(id: string) => {
setCurrentTab(id);
}} />;
<div className='flex items-center gap-3'> right =
<FileUpload id='theme-uplaod' onUpload={async (file: File) => { <div className='flex items-center gap-3'>
const data = await api.themes.upload({file}); <FileUpload id='theme-uplaod' onUpload={async (file: File) => {
const uploadedTheme = data.themes[0]; const data = await api.themes.upload({file});
setThemes([...themes, uploadedTheme]); const uploadedTheme = data.themes[0];
showToast({ setThemes([...themes, uploadedTheme]);
message: `Theme uploaded - ${uploadedTheme.name}` showToast({
}); message: `Theme uploaded - ${uploadedTheme.name}`
}}>Upload theme</FileUpload> });
<Button }}>Upload theme</FileUpload>
className='min-w-[75px]' <Button
color='black' className='min-w-[75px]'
label='OK' color='black'
onClick = {() => { label='OK'
modal.remove(); onClick = {() => {
}} /> modal.remove();
</div> }} />
</div> </div>;
);
} }
return <PageHeader containerClassName={selectedTheme! && 'bg-grey-50'} left={left} right={right} />;
}; };
const ThemeModalContent: React.FC<ThemeModalContentProps> = ({ const ThemeModalContent: React.FC<ThemeModalContentProps> = ({