Allow custom icons and additional links in left nav

This commit is contained in:
Daniel Lautzenheiser 2022-09-20 10:14:40 -04:00
parent a82f27100e
commit 04098bf96d
7 changed files with 127 additions and 3 deletions

View File

@ -62,6 +62,7 @@ collections: # A list of collections the CMS should be able to edit
- name: 'settings'
label: 'Settings'
icon: 'settings'
delete: false # Prevent users from deleting documents in this collection
editor:
preview: false

View File

@ -237,6 +237,7 @@
);
}
});
CMS.registerAdditionalLink('Example.com', 'https://example.com/', 'new-tab');
</script>
</body>
</html>

View File

@ -1,3 +1,4 @@
import React from 'react';
// Core
import { NetlifyCmsCore as CMS } from 'netlify-cms-core';
// Backends
@ -29,6 +30,7 @@ import NetlifyCmsWidgetColorString from 'netlify-cms-widget-colorstring';
import image from 'netlify-cms-editor-component-image';
// Locales
import * as locales from 'netlify-cms-locales';
import { images, Icon } from 'netlify-cms-ui-default';
// Register all the things
CMS.registerBackend('git-gateway', GitGatewayBackend);
@ -66,3 +68,7 @@ CMS.registerEditorComponent({
Object.keys(locales).forEach(locale => {
CMS.registerLocale(locale, locales[locale]);
});
Object.keys(images).forEach(iconName => {
CMS.registerIcon(iconName, <Icon type={iconName} />);
});

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
declare module 'netlify-cms-core' {
import type { ComponentType } from 'react';
import type { ComponentType, ReactNode } from 'react';
import type { List, Map } from 'immutable';
import type { Pluggable } from 'unified';
@ -578,7 +578,10 @@ declare module 'netlify-cms-core' {
widgetName: string,
serializer: CmsWidgetValueSerializer,
) => void;
resolveWidget: (name: string) => CmsWidget | undefined;
registerIcon: (iconName: string, icon: ReactNode) => void;
getIcon: (iconName: string) => ReactNode;
registerAdditionalLink: (title: string, link: string, iconName?: string) => void;
getAdditionalLinks: () => { title: string, link: string, iconName?: string }[];
}
export const NetlifyCmsCore: CMS;

View File

@ -10,6 +10,7 @@ import { Icon, components, colors } from 'netlify-cms-ui-default';
import { searchCollections } from '../../actions/collections';
import CollectionSearch from './CollectionSearch';
import NestedCollection from './NestedCollection';
import { getAdditionalLinks, getIcon } from '../../lib/registry';
const styles = {
sidebarNavLinkActive: css`
@ -67,6 +68,26 @@ const SidebarNavLink = styled(NavLink)`
`};
`;
const AdditionalLink = styled.a`
display: flex;
font-size: 14px;
font-weight: 500;
align-items: center;
padding: 8px 12px;
border-left: 2px solid #fff;
z-index: -1;
${Icon} {
margin-right: 8px;
flex-shrink: 0;
}
&:hover,
&:active {
${styles.sidebarNavLinkActive};
}
`;
export class Sidebar extends React.Component {
static propTypes = {
collections: ImmutablePropTypes.map.isRequired,
@ -79,6 +100,14 @@ export class Sidebar extends React.Component {
renderLink = (collection, filterTerm) => {
const collectionName = collection.get('name');
const iconName = collection.get('icon');
let icon = <Icon type="write" />;
if (iconName) {
const storedIcon = getIcon(iconName);
if (storedIcon) {
icon = storedIcon;
}
}
if (collection.has('nested')) {
return (
<li key={collectionName}>
@ -97,7 +126,60 @@ export class Sidebar extends React.Component {
activeClassName="sidebar-active"
data-testid={collectionName}
>
<Icon type="write" />
{icon}
{collection.get('label')}
</SidebarNavLink>
</li>
);
};
renderAdditionalLink = ({ title, url, iconName }) => {
let icon = <Icon type="write" />;
if (iconName) {
const storedIcon = getIcon(iconName);
if (storedIcon) {
icon = storedIcon;
}
}
return (
<li key={title}>
<AdditionalLink href={url} target="_blank" rel="noopener">
{icon}
{title}
</AdditionalLink>
</li>
);
};
renderLink = (collection, filterTerm) => {
const collectionName = collection.get('name');
const iconName = collection.get('icon');
let icon = <Icon type="write" />;
if (iconName) {
const storedIcon = getIcon(iconName);
if (storedIcon) {
icon = storedIcon;
}
}
if (collection.has('nested')) {
return (
<li key={collectionName}>
<NestedCollection
collection={collection}
filterTerm={filterTerm}
data-testid={collectionName}
/>
</li>
);
}
return (
<li key={collectionName}>
<SidebarNavLink
to={`/collections/${collectionName}`}
activeClassName="sidebar-active"
data-testid={collectionName}
>
{icon}
{collection.get('label')}
</SidebarNavLink>
</li>
@ -106,6 +188,7 @@ export class Sidebar extends React.Component {
render() {
const { collections, collection, isSearchEnabled, searchTerm, t, filterTerm } = this.props;
const additionalLinks = getAdditionalLinks();
return (
<SidebarContainer>
<SidebarHeading>{t('collection.sidebar.collections')}</SidebarHeading>
@ -122,6 +205,7 @@ export class Sidebar extends React.Component {
.toList()
.filter(collection => collection.get('hide') !== true)
.map(collection => this.renderLink(collection, filterTerm))}
{additionalLinks.map(this.renderAdditionalLink)}
</SidebarNavList>
</SidebarContainer>
);

View File

@ -25,6 +25,8 @@ const registry = {
templates: {},
previewStyles: [],
widgets: {},
icons: {},
additionalLinks: [],
editorComponents: Map(),
remarkPlugins: [],
widgetValueSerializers: {},
@ -58,6 +60,10 @@ export default {
removeEventListener,
getEventListeners,
invokeEvent,
registerIcon,
getIcon,
registerAdditionalLink,
getAdditionalLinks
};
/**
@ -284,3 +290,23 @@ export function registerLocale(locale, phrases) {
export function getLocale(locale) {
return registry.locales[locale];
}
/**
* Icons
*/
export function registerIcon(name, icon) {
registry.icons[name] = icon;
}
export function getIcon(name) {
return registry.icons[name];
}
/**
* Icons
*/
export function registerAdditionalLink(title, url, iconName) {
registry.additionalLinks.push({ title, url, iconName });
}
export function getAdditionalLinks() {
return registry.additionalLinks;
}

View File

@ -14,6 +14,7 @@ import AuthenticationPage from './AuthenticationPage';
import WidgetPreviewContainer from './WidgetPreviewContainer';
import ObjectWidgetTopBar from './ObjectWidgetTopBar';
import GoBackButton from './GoBackButton';
import images from './Icon/images/_index';
import {
fonts,
colorsRaw,
@ -63,6 +64,7 @@ export const NetlifyCmsUiDefault = {
zIndex,
reactSelectStyles,
GlobalStyles,
images,
};
export {
Dropdown,
@ -97,4 +99,5 @@ export {
reactSelectStyles,
GlobalStyles,
GoBackButton,
images,
};