>;
+export type WidgetFor = (name: K) => ReactNode;
+
export type WidgetsFor = (
name: K,
) => P[K] extends Array
@@ -312,7 +314,7 @@ export interface TemplatePreviewProps>;
- widgetFor: (name: T extends EntryData ? string : keyof T) => ReactNode;
+ widgetFor: WidgetFor;
widgetsFor: WidgetsFor;
}
@@ -321,6 +323,20 @@ export type TemplatePreviewComponent<
EF extends BaseField = UnknownField,
> = ComponentType>;
+export interface TemplatePreviewCardProps {
+ collection: Collection;
+ fields: Field[];
+ entry: Entry;
+ viewStyle: 'list' | 'grid';
+ widgetFor: WidgetFor;
+ widgetsFor: WidgetsFor;
+}
+
+export type TemplatePreviewCardComponent<
+ T = EntryData,
+ EF extends BaseField = UnknownField,
+> = ComponentType>;
+
export interface WidgetOptions {
validator?: Widget['validator'];
getValidValue?: Widget['getValidValue'];
@@ -912,3 +928,12 @@ export type DeepPartial = T extends object
[P in keyof T]?: DeepPartial;
}
: T;
+
+export interface InferredField {
+ type: string;
+ secondaryTypes: string[];
+ synonyms: string[];
+ defaultPreview: (value: string | boolean | number) => JSX.Element | ReactNode;
+ fallbackToFirstField: boolean;
+ showError: boolean;
+}
diff --git a/packages/core/src/lib/registry.ts b/packages/core/src/lib/registry.ts
index 6ea19037..1fdc4959 100644
--- a/packages/core/src/lib/registry.ts
+++ b/packages/core/src/lib/registry.ts
@@ -19,6 +19,7 @@ import type {
PreviewStyle,
PreviewStyleOptions,
ShortcodeConfig,
+ TemplatePreviewCardComponent,
TemplatePreviewComponent,
UnknownField,
Widget,
@@ -38,6 +39,7 @@ const eventHandlers = allowedEvents.reduce((acc, e) => {
interface Registry {
backends: Record;
templates: Record>;
+ cards: Record>;
widgets: Record;
icons: Record;
additionalLinks: Record;
@@ -57,6 +59,7 @@ interface Registry {
const registry: Registry = {
backends: {},
templates: {},
+ cards: {},
widgets: {},
icons: {},
additionalLinks: {},
@@ -71,6 +74,8 @@ const registry: Registry = {
export default {
registerPreviewTemplate,
getPreviewTemplate,
+ registerPreviewCard,
+ getPreviewCard,
registerWidget,
getWidget,
getWidgets,
@@ -123,6 +128,17 @@ export function getPreviewTemplate(name: string): TemplatePreviewComponent(name: string, component: TemplatePreviewCardComponent) {
+ registry.cards[name] = component as TemplatePreviewCardComponent;
+}
+
+export function getPreviewCard(name: string): TemplatePreviewCardComponent {
+ return registry.cards[name];
+}
+
/**
* Editor Widgets
*/
diff --git a/packages/core/src/lib/util/collection.util.ts b/packages/core/src/lib/util/collection.util.ts
index a761929e..ff4b0b48 100644
--- a/packages/core/src/lib/util/collection.util.ts
+++ b/packages/core/src/lib/util/collection.util.ts
@@ -15,13 +15,13 @@ import { selectField } from './field.util';
import { selectMediaFolder } from './media.util';
import type { Backend } from '@staticcms/core/backend';
-import type { InferredField } from '@staticcms/core/constants/fieldInference';
import type {
Collection,
Config,
Entry,
Field,
FilesCollection,
+ InferredField,
ObjectField,
SortableField,
} from '@staticcms/core/interface';
diff --git a/packages/core/src/reducers/selectors/config.ts b/packages/core/src/reducers/selectors/config.ts
index ac9519e1..2776e11b 100644
--- a/packages/core/src/reducers/selectors/config.ts
+++ b/packages/core/src/reducers/selectors/config.ts
@@ -1,7 +1,12 @@
/* eslint-disable import/prefer-default-export */
import type { Config } from '@staticcms/core/interface';
+import type { RootState } from '@staticcms/core/store';
export function selectLocale(config?: Config) {
return config?.locale || 'en';
}
+
+export function selectConfig(state: RootState) {
+ return state.config.config;
+}
diff --git a/packages/demo/public/index.html b/packages/demo/public/index.html
index 5bb2b928..813dea5f 100644
--- a/packages/demo/public/index.html
+++ b/packages/demo/public/index.html
@@ -20,7 +20,7 @@
},
'2015-02-16-this-is-a-toml-frontmatter-post.md': {
content:
- '+++\ntitle = "This is a TOML front matter post"\nimage = "/assets/uploads/moby-dick.jpg"\n"date" = "2015-02-16T00:00:00.000Z"\n+++\n\n# I Am a Title in Markdown\n\nHello, world\n\n* One Thing\n* Another Thing\n* A Third Thing\n',
+ '+++\ntitle = This is a TOML front matter post\nimage = "/assets/uploads/moby-dick.jpg"\n"date" = "2015-02-16T00:00:00.000Z"\n+++\n\n# I Am a Title in Markdown\n\nHello, world\n\n* One Thing\n* Another Thing\n* A Third Thing\n',
},
'2015-02-14-this-is-a-post-with-a-different-extension.other': {
content:
diff --git a/packages/demo/src/cms.jsx b/packages/demo/src/cms.jsx
index aa879200..ccfaa37a 100644
--- a/packages/demo/src/cms.jsx
+++ b/packages/demo/src/cms.jsx
@@ -18,6 +18,51 @@ const PostPreview = ({ entry, widgetFor }) => {
);
};
+const PostPreviewCard = ({ entry, widgetFor, viewStyle }) => {
+ return (
+
+ {viewStyle === "grid" ? widgetFor("image") : null}
+
+
+
+ {entry.data.title}
+ {entry.data.date}
+
+
+ {entry.data.draft === true ? "Draft" : "Published"}
+
+
+
+
+ );
+};
+
const GeneralPreview = ({ widgetsFor, entry }) => {
const title = entry.data.site_title;
const posts = entry.data.posts;
@@ -88,6 +133,7 @@ const CustomPage = () => {
cms.registerPreviewStyle(".toastui-editor-contents h1 { color: blue }", { raw: true });
cms.registerPreviewTemplate("posts", PostPreview);
+CMS.registerPreviewCard("posts", PostPreviewCard);
cms.registerPreviewTemplate("general", GeneralPreview);
cms.registerPreviewTemplate("authors", AuthorsPreview);
// Pass the name of a registered control to reuse with a new widget preview.
diff --git a/packages/docs/content/docs/custom-previews.mdx b/packages/docs/content/docs/custom-previews.mdx
index f307589b..6e8cb5ce 100644
--- a/packages/docs/content/docs/custom-previews.mdx
+++ b/packages/docs/content/docs/custom-previews.mdx
@@ -4,17 +4,21 @@ title: Creating Custom Previews
weight: 50
---
-The Static CMS exposes a `window.CMS` global object that you can use to register custom previews for an entire collection (or file within a file collection) via `registerPreviewTemplate`.
+The Static CMS exposes a `window.CMS` global object that you can use to register custom previews for an entire collection (or file within a file collection) via `registerPreviewTemplate` (editor view) and `registerPreviewCard` (collection view).
### React Components Inline
-The `registerPreviewTemplate` requires you to provide a React component. If you have a build process in place for your project, it is possible to integrate with this build process.
+The `registerPreviewTemplate` and `registerPreviewCard` require you to provide a React component. If you have a build process in place for your project, it is possible to integrate with this build process.
However, although possible, it may be cumbersome or even impractical to add a React build phase. For this reason, Static CMS exposes some constructs globally to allow you to create components inline: `h` (alias for React.createElement) as well some basic hooks (`useState`, `useMemo`, `useEffect`, `useCallback`).
**NOTE**: `createClass` is still provided, allowing for the creation of react class components. However it has now been deprecated and will be removed in `v2.0.0`.
-## Params
+## Editor Preview
+
+`registerPreviewTemplate` allows you to create a template that overrides the entire editor preview for a given collection.
+
+### `registerPreviewTemplate` Params
| Param | Type | Description |
| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
@@ -26,13 +30,15 @@ The following parameters will be passed to your `react_component` during render:
| Param | Type | Description |
| ---------- | -------------- | ---------------------------------------------------------------------------------------------------- |
| entry | object | Object with a `data` field that contains the current value of all widgets in the editor |
+| collection | object | Collection configuration |
+| fields | object | The fields for the given collection |
| document | Document | The document object the preview is within. If rendered with a frame, it will be the frame's document |
| window | Window | The window object the preview is within. If rendered with a frame, it will be the frame's window |
| getAsset | Async function | Function that given a url returns (as a promise) a loaded asset |
| widgetFor | Function | Given a field name, returns the rendered preview of that field's widget and value |
| widgetsFor | Function | Given a field name, returns the rendered previews of that field's nested child widgets and values |
-### Example
+#### `registerPreviewTemplate` Example
@@ -62,7 +68,7 @@ const PostPreview = ({ widgetFor, getAsset, entry, collection, field }) => {
{entry.data.title}
-
{widgetFor('body')}
+
{widgetFor('body')}
);
};
@@ -84,14 +90,20 @@ interface Post {
body: string;
}
-const PostPreview = ({ widgetFor, getAsset, entry, collection, field }: TemplatePreviewProps) => {
+const PostPreview = ({
+ widgetFor,
+ getAsset,
+ entry,
+ collection,
+ field,
+}: TemplatePreviewProps) => {
const imageUrl = useMediaAsset(entry.data.image, collection, field, entry);
return (
{entry.data.title}
-
{widgetFor('body')}
+
{widgetFor('body')}
);
};
@@ -101,11 +113,11 @@ CMS.registerPreviewTemplate('posts', PostPreview);
-### Lists and Objects
+#### Lists and Objects
The API for accessing the individual fields of list- and object-type entries is similar to the API for accessing fields in standard entries, but there are a few key differences. Access to these nested fields is facilitated through the `widgetsFor` function, which is passed to the preview template component during render.
-#### List Template Example
+##### List Template Example
For list fields, the widgetFor function returns an array of objects that you can map over in your template. If your field is a list of authors containing two entries, with fields `name` and `description`, the return value of `widgetsFor` would look like this:
@@ -210,7 +222,7 @@ CMS.registerPreviewTemplate('authors', AuthorsPreview);
-#### Object Example
+##### Object Example
Object fields are simpler than lists - instead of `widgetsFor` returning an array of objects, it returns a single object. Accessing the shape of that object is the same as the shape of objects returned for list fields:
@@ -309,3 +321,205 @@ CMS.registerPreviewTemplate('general', GeneralPreview);
```
+
+## Collection Card Preview
+
+`registerPreviewCard` allows you to create a card template that overrides the cards displayed in the collection view.
+
+### `registerPreviewCard` Params
+
+| Param | Type | Description |
+| --------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
+| name | string | The name of the collection (or file for file collections) which this preview component will be used for
- Folder collections: Use the name of the collection
- File collections: Use the name of the file
|
+| react_component | [React Function Component](https://reactjs.org/docs/components-and-props.html) | A React functional component that renders a preview card for a given entry in your collection |
+
+The following parameters will be passed to your `react_component` during render:
+
+| Param | Type | Description |
+| ---------- | --------------------- | ------------------------------------------------------------------------------------------------- |
+| viewStyle | 'list'
\| 'grid' | The current view style being displayed |
+| entry | object | Object with a `data` field that contains the current value of all widgets in the editor |
+| widgetFor | Function | Given a field name, returns the rendered preview of that field's widget and value |
+| widgetsFor | Function | Given a field name, returns the rendered previews of that field's nested child widgets and values |
+
+#### `registerPreviewTemplate` Example
+
+
+
+```js
+const PostPreviewCard = ({ entry, widgetFor, viewStyle }) => {
+ return h(
+ 'div',
+ { style: { width: '100%' } },
+ viewStyle === 'grid' ? widgetFor('image') : null,
+ h(
+ 'div',
+ { style: { padding: '16px', width: '100%' } },
+ h(
+ 'div',
+ {
+ style: {
+ display: 'flex',
+ width: '100%',
+ justifyContent: 'space-between',
+ alignItems: 'start',
+ },
+ },
+ h(
+ 'div',
+ {
+ style: {
+ display: 'flex',
+ flexDirection: viewStyle === 'grid' ? 'column' : 'row',
+ alignItems: 'baseline',
+ gap: '8px',
+ },
+ },
+ h('strong', { style: { fontSize: '24px' } }, entry.data.title),
+ h('span', { style: { fontSize: '16px' } }, entry.data.date),
+ ),
+ h(
+ 'div',
+ {
+ style: {
+ backgroundColor: entry.data.draft === true ? 'blue' : 'green',
+ color: 'white',
+ border: 'none',
+ padding: '4px 8px',
+ textAlign: 'center',
+ textDecoration: 'none',
+ display: 'inline-block',
+ cursor: 'pointer',
+ borderRadius: '4px',
+ },
+ },
+ entry.data.draft === true ? 'Draft' : 'Published',
+ ),
+ ),
+ ),
+ );
+};
+
+CMS.registerPreviewCard('posts', PostPreviewCard);
+```
+
+```jsx
+import CMS from '@staticcms/core';
+
+const PostPreviewCard = ({ entry, widgetFor, viewStyle }) => {
+ return (
+
+ {viewStyle === 'grid' ? widgetFor('image') : null}
+
+
+
+ {entry.data.title}
+ {entry.data.date}
+
+
+ {entry.data.draft === true ? 'Draft' : 'Published'}
+
+
+
+
+ );
+};
+```
+
+```tsx
+import CMS from '@staticcms/core';
+
+import type { TemplatePreviewCardProps } from '@staticcms/core';
+
+/**
+ * The type for 'entry.data'
+ */
+interface Post {
+ image: string;
+ title: string;
+ body: string;
+}
+
+const PostPreviewCard = ({ entry, widgetFor, viewStyle }: TemplatePreviewCardProps) => {
+ return (
+
+ {viewStyle === 'grid' ? widgetFor('image') : null}
+
+
+
+ {entry.data.title}
+ {entry.data.date}
+
+
+ {entry.data.draft === true ? 'Draft' : 'Published'}
+
+
+
+
+ );
+};
+
+CMS.registerPreviewTemplate('posts', PostPreview);
+```
+
+
+
+##### List View
+
+![Post Preview Card List View](/img/preview_card_list.png)
+
+##### Grid View
+
+![Post Preview Card List View](/img/preview_card_grid.png)
diff --git a/packages/docs/public/img/preview_card_grid.png b/packages/docs/public/img/preview_card_grid.png
new file mode 100644
index 00000000..f7729470
Binary files /dev/null and b/packages/docs/public/img/preview_card_grid.png differ
diff --git a/packages/docs/public/img/preview_card_list.png b/packages/docs/public/img/preview_card_list.png
new file mode 100644
index 00000000..5dedd07a
Binary files /dev/null and b/packages/docs/public/img/preview_card_list.png differ