mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-24 23:48:13 -05:00
Added simple search bar to AdminX settings
refs https://github.com/TryGhost/Team/issues/3349
This commit is contained in:
parent
543f3750a7
commit
dd1d2f8d4d
21 changed files with 58 additions and 8 deletions
|
@ -1,6 +1,8 @@
|
|||
import ButtonGroup from '../global/ButtonGroup';
|
||||
import React from 'react';
|
||||
import SettingGroupHeader from './SettingGroupHeader';
|
||||
import clsx from 'clsx';
|
||||
import useSearchable from '../../hooks/useSearchable';
|
||||
import {ButtonProps} from '../global/Button';
|
||||
import {SaveState} from '../../hooks/useForm';
|
||||
|
||||
|
@ -9,6 +11,7 @@ interface SettingGroupProps {
|
|||
testId?: string;
|
||||
title?: string;
|
||||
description?: React.ReactNode;
|
||||
searchKeywords?: string[];
|
||||
isEditing?: boolean;
|
||||
saveState?: SaveState;
|
||||
customHeader?: React.ReactNode;
|
||||
|
@ -36,6 +39,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
testId,
|
||||
title,
|
||||
description,
|
||||
searchKeywords,
|
||||
isEditing,
|
||||
saveState,
|
||||
customHeader,
|
||||
|
@ -49,6 +53,8 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
onSave,
|
||||
onCancel
|
||||
}) => {
|
||||
const {isVisible} = useSearchable({keywords: searchKeywords && [title || '', ...searchKeywords]});
|
||||
|
||||
const handleEdit = () => {
|
||||
onEditingChange?.(true);
|
||||
};
|
||||
|
@ -120,7 +126,7 @@ const SettingGroup: React.FC<SettingGroupProps> = ({
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={`relative flex flex-col gap-6 rounded ${border && 'border p-5 md:p-7'} ${styles}`} data-testid={testId}>
|
||||
<div className={clsx('relative flex flex-col gap-6 rounded', !isVisible && 'hidden', border && 'border p-5 md:p-7', styles)} data-testid={testId}>
|
||||
<div className='absolute top-[-60px]' id={navid && navid}></div>
|
||||
{customHeader ? customHeader :
|
||||
<SettingGroupHeader description={description} title={title!}>
|
||||
|
|
|
@ -8,14 +8,14 @@ interface Props {
|
|||
|
||||
const SettingSection: React.FC<Props> = ({title, children}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="!visible hidden [&:has(>div>:not(.hidden))]:!block">
|
||||
{title && <SettingSectionHeader sticky={true} title={title} />}
|
||||
{children &&
|
||||
<div className="mb-[100px] flex flex-col gap-9">
|
||||
{children}
|
||||
</div>
|
||||
}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import React from 'react';
|
||||
import SettingNavItem from '../admin-x-ds/settings/SettingNavItem';
|
||||
import SettingNavSection from '../admin-x-ds/settings/SettingNavSection';
|
||||
import TextField from '../admin-x-ds/global/form/TextField';
|
||||
import {useSearch} from './providers/ServiceProvider';
|
||||
|
||||
const Sidebar: React.FC = () => {
|
||||
const {filter, setFilter} = useSearch();
|
||||
|
||||
const handleSectionClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const element = document.getElementById(e.currentTarget.name);
|
||||
if (element) {
|
||||
|
@ -12,6 +16,8 @@ const Sidebar: React.FC = () => {
|
|||
|
||||
return (
|
||||
<div className="hidden md:!visible md:!block md:h-[calc(100vh-5vmin-84px)] md:w-[300px] md:overflow-y-scroll md:pt-[32px]">
|
||||
<TextField containerClassName="mb-10" placeholder="Search" value={filter} onChange={e => setFilter(e.target.value)} />
|
||||
|
||||
<SettingNavSection title="General">
|
||||
<SettingNavItem navid='title-and-description' title="Title and description" onClick={handleSectionClick} />
|
||||
<SettingNavItem navid='timezone' title="Timezone" onClick={handleSectionClick} />
|
||||
|
|
|
@ -9,6 +9,7 @@ interface ServicesContextProps {
|
|||
api: ReturnType<typeof setupGhostApi>;
|
||||
fileService: FileService|null;
|
||||
officialThemes: OfficialTheme[];
|
||||
search: {filter: string, setFilter: (value: string) => void}
|
||||
}
|
||||
|
||||
interface ServicesProviderProps {
|
||||
|
@ -20,7 +21,8 @@ interface ServicesProviderProps {
|
|||
const ServicesContext = createContext<ServicesContextProps>({
|
||||
api: setupGhostApi({ghostVersion: ''}),
|
||||
fileService: null,
|
||||
officialThemes: []
|
||||
officialThemes: [],
|
||||
search: {filter: '', setFilter: () => {}}
|
||||
});
|
||||
|
||||
const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersion, officialThemes}) => {
|
||||
|
@ -32,11 +34,14 @@ const ServicesProvider: React.FC<ServicesProviderProps> = ({children, ghostVersi
|
|||
}
|
||||
}), [apiService]);
|
||||
|
||||
const [filter, setFilter] = React.useState('');
|
||||
|
||||
return (
|
||||
<ServicesContext.Provider value={{
|
||||
api: apiService,
|
||||
fileService,
|
||||
officialThemes
|
||||
officialThemes,
|
||||
search: {filter, setFilter}
|
||||
}}>
|
||||
{children}
|
||||
</ServicesContext.Provider>
|
||||
|
@ -50,3 +55,5 @@ export const useServices = () => useContext(ServicesContext);
|
|||
export const useApi = () => useServices().api;
|
||||
|
||||
export const useOfficialThemes = () => useServices().officialThemes;
|
||||
|
||||
export const useSearch = () => useServices().search;
|
||||
|
|
|
@ -195,6 +195,7 @@ const DefaultRecipients: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='default-recipients'
|
||||
saveState={saveState}
|
||||
searchKeywords={['newsletter', 'recipients', 'email']}
|
||||
testId='default-recipients'
|
||||
title='Default recipients'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -101,6 +101,7 @@ const MailGun: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='mailgun'
|
||||
saveState={saveState}
|
||||
searchKeywords={['mailgun', 'email']}
|
||||
testId='mailgun'
|
||||
title='Mailgun'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -102,6 +102,7 @@ const Facebook: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='facebook'
|
||||
saveState={saveState}
|
||||
searchKeywords={['facebook card', 'structured data', 'rich cards']}
|
||||
testId='facebook'
|
||||
title='Facebook card'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -83,6 +83,7 @@ const LockSite: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='locksite'
|
||||
saveState={saveState}
|
||||
searchKeywords={['private', 'password', 'lock']}
|
||||
testId='locksite'
|
||||
title='Make site private'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -104,6 +104,7 @@ const Metadata: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='metadata'
|
||||
saveState={saveState}
|
||||
searchKeywords={['meta', 'title', 'description', 'search', 'engine', 'google']}
|
||||
testId='metadata'
|
||||
title='Metadata'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -59,6 +59,7 @@ const PublicationLanguage: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='publication-language'
|
||||
saveState={saveState}
|
||||
searchKeywords={['language', 'locale']}
|
||||
testId='publication-language'
|
||||
title="Publication Language"
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -160,6 +160,7 @@ const SocialAccounts: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='social-accounts'
|
||||
saveState={saveState}
|
||||
searchKeywords={['social', 'accounts', 'facebook', 'twitter', 'structured data', 'rich cards']}
|
||||
testId='social-accounts'
|
||||
title='Social accounts'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -87,6 +87,7 @@ const TimeZone: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='timezone'
|
||||
saveState={saveState}
|
||||
searchKeywords={['time', 'date', 'timezone', 'time zone']}
|
||||
testId='timezone'
|
||||
title='Site timezone'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -71,6 +71,7 @@ const TitleAndDescription: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='title-and-description'
|
||||
saveState={saveState}
|
||||
searchKeywords={['site title', 'site description']}
|
||||
testId='title-and-description'
|
||||
title='Title & description'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -104,6 +104,7 @@ const Twitter: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='twitter'
|
||||
saveState={saveState}
|
||||
searchKeywords={['twitter card', 'structured data', 'rich cards']}
|
||||
testId='twitter'
|
||||
title='Twitter card'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -231,6 +231,7 @@ const Users: React.FC = () => {
|
|||
<SettingGroup
|
||||
customButtons={buttons}
|
||||
navid='users'
|
||||
searchKeywords={['users', 'permissions', 'roles', 'staff']}
|
||||
title='Users and permissions'
|
||||
>
|
||||
<Owner updateUser={updateUser} user={ownerUser} />
|
||||
|
|
|
@ -145,6 +145,7 @@ const Access: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='access'
|
||||
saveState={saveState}
|
||||
searchKeywords={['access', 'subscription', 'post']}
|
||||
testId='access'
|
||||
title='Access'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -78,6 +78,7 @@ const Analytics: React.FC = () => {
|
|||
isEditing={isEditing}
|
||||
navid='analytics'
|
||||
saveState={saveState}
|
||||
searchKeywords={['analytics', 'tracking', 'privacy']}
|
||||
testId='analytics'
|
||||
title='Analytics'
|
||||
onCancel={handleCancel}
|
||||
|
|
|
@ -14,6 +14,7 @@ const DesignSetting: React.FC = () => {
|
|||
customButtons={<Button color='green' label='Customize' link onClick={openPreviewModal}/>}
|
||||
description="Customize the look and feel of your site"
|
||||
navid='branding-and-design'
|
||||
searchKeywords={['design', 'branding', 'logo', 'cover', 'colors', 'fonts', 'background']}
|
||||
testId='design'
|
||||
title="Branding and design"
|
||||
/>
|
||||
|
|
|
@ -14,6 +14,7 @@ const Navigation: React.FC = () => {
|
|||
customButtons={<Button color='green' label='Customize' link onClick={openPreviewModal}/>}
|
||||
description="Set up primary and secondary menus"
|
||||
navid='navigation'
|
||||
searchKeywords={['navigation', 'menus', 'primary', 'secondary', 'links']}
|
||||
testId='navigation'
|
||||
title="Navigation"
|
||||
/>
|
||||
|
|
|
@ -12,6 +12,7 @@ const Theme: React.FC = () => {
|
|||
}}/>}
|
||||
description="Change or upload themes"
|
||||
navid='theme'
|
||||
searchKeywords={['themes', 'design', 'appearance', 'style']}
|
||||
testId='theme'
|
||||
title="Theme"
|
||||
/>
|
||||
|
|
15
ghost/admin-x-settings/src/hooks/useSearchable.tsx
Normal file
15
ghost/admin-x-settings/src/hooks/useSearchable.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import {useSearch} from '../components/providers/ServiceProvider';
|
||||
|
||||
const useSearchable = ({keywords}: { keywords: string[] | undefined }) => {
|
||||
const {filter} = useSearch();
|
||||
|
||||
if (!filter || !keywords) {
|
||||
return {isVisible: true};
|
||||
}
|
||||
|
||||
const isVisible = keywords.some(keyword => keyword.toLowerCase().includes(filter.toLowerCase()));
|
||||
|
||||
return {isVisible};
|
||||
};
|
||||
|
||||
export default useSearchable;
|
Loading…
Add table
Reference in a new issue