Added Accordion and AccordionItem Components
This commit is contained in:
@@ -2,6 +2,9 @@ import React from 'react';
|
||||
import Markdoc from '@markdoc/markdoc';
|
||||
import type { Node } from '@markdoc/markdoc';
|
||||
|
||||
import ContentComponents from '@/components/Content';
|
||||
import SidenoteContainer from '@/components/Content/Sidenote/Container';
|
||||
|
||||
import styles from './MarkdocRenderer.module.css';
|
||||
|
||||
import { nodes } from '@/lib/markdoc/nodes';
|
||||
@@ -9,13 +12,6 @@ import { tags } from '@/lib/markdoc/tags';
|
||||
|
||||
import { collectSideNotes, hasComponents } from '@/lib/markdoc/utils';
|
||||
|
||||
import { Column } from '@/components/Content/Grid/Column';
|
||||
import { Row } from '@/components/Content/Grid/Row';
|
||||
import Sidenote from '@/components/Content/Sidenote/Item';
|
||||
import SidenoteContainer from '@/components/Content/Sidenote/Container';
|
||||
import DefinitionList from '@/components/Content/DefinitionList';
|
||||
import DefinitionItem from '@/components/Content/DefinitionList/DefinitionItem';
|
||||
|
||||
interface MarkdocRendererProps {
|
||||
content: () => Promise<{ node: Node }>;
|
||||
className?: string;
|
||||
@@ -27,14 +23,6 @@ export default async function MarkdocRenderer({
|
||||
content,
|
||||
className,
|
||||
}: MarkdocRendererProps) {
|
||||
const components = {
|
||||
Column,
|
||||
Row,
|
||||
Sidenote,
|
||||
DefinitionList,
|
||||
DefinitionItem,
|
||||
};
|
||||
|
||||
const { node } = await content();
|
||||
const errors = Markdoc.validate(node, { tags, nodes });
|
||||
|
||||
@@ -52,7 +40,9 @@ export default async function MarkdocRenderer({
|
||||
className={`${styles.wrapper} ${className} ${showMargin ? styles.hasMargin : ''}`}
|
||||
>
|
||||
<div className={`${styles.content} content`}>
|
||||
{Markdoc.renderers.react(renderable, React, { components })}
|
||||
{Markdoc.renderers.react(renderable, React, {
|
||||
components: ContentComponents,
|
||||
})}
|
||||
</div>
|
||||
<SidenoteContainer items={sidenotes} />
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
.item {
|
||||
& .summary {
|
||||
--el-summary-marker-symbol-closed: "▾";
|
||||
--el-summary-marker-symbol-open: "▾";
|
||||
--el-summary-marker-symbol-transform-open: rotate(180deg);
|
||||
|
||||
position: relative;
|
||||
|
||||
padding: var(--spacing-snug);
|
||||
|
||||
font-family: var(--el-summary-font-family);
|
||||
font-size: var(--el-summary-font-size);
|
||||
font-weight: var(--el-summary-font-weight);
|
||||
line-height: var(--el-summary-line-height);
|
||||
color: var(--el-summary-fg);
|
||||
text-transform: var(--el-summary-text-transform);
|
||||
list-style: none;
|
||||
|
||||
background: var(--el-summary-bg);
|
||||
|
||||
&::before {
|
||||
content: var(--el-summary-marker-symbol-closed);
|
||||
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
font-size: 1em;
|
||||
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
}
|
||||
|
||||
& .content {
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
transition: grid-template-rows 2s ease;
|
||||
|
||||
& .inner {
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&[open] {
|
||||
& .summary {
|
||||
&::before {
|
||||
content: var(--el-summary-marker-symbol-open);
|
||||
transform: var(--el-summary-marker-symbol-transform-open);
|
||||
}
|
||||
}
|
||||
|
||||
& .content {
|
||||
grid-template-rows: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/components/Content/Accordion/AccordionItem/index.tsx
Normal file
39
src/components/Content/Accordion/AccordionItem/index.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import styles from './AccordionItem.module.css';
|
||||
|
||||
interface AccordionItemProps {
|
||||
title: string;
|
||||
defaultOpen: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function AccordionItem({
|
||||
title,
|
||||
defaultOpen,
|
||||
children,
|
||||
}: AccordionItemProps) {
|
||||
const [isOpen, setIsOpen] = React.useState(defaultOpen);
|
||||
return (
|
||||
<details
|
||||
className={styles.item}
|
||||
open={isOpen}
|
||||
onToggle={(e) => setIsOpen(e.currentTarget.open)}
|
||||
>
|
||||
<summary
|
||||
className={styles.summary}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setIsOpen(!isOpen);
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
</summary>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.inner}>{children}</div>
|
||||
</div>
|
||||
</details>
|
||||
);
|
||||
}
|
||||
11
src/components/Content/Accordion/index.tsx
Normal file
11
src/components/Content/Accordion/index.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './Accordion.module.css';
|
||||
|
||||
interface AccordionProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function Accordion({ children }: AccordionProps) {
|
||||
return <div className={styles.accordion}>{children}</div>;
|
||||
}
|
||||
@@ -9,7 +9,7 @@ interface ColumnProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Column({ style, children }: ColumnProps) {
|
||||
export default function Column({ style, children }: ColumnProps) {
|
||||
return (
|
||||
<div
|
||||
className={styles.column}
|
||||
|
||||
@@ -12,7 +12,7 @@ interface RowProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Row({ style, children }: RowProps) {
|
||||
export default function Row({ style, children }: RowProps) {
|
||||
return (
|
||||
<div
|
||||
className={`${styles.row}`}
|
||||
|
||||
19
src/components/Content/index.ts
Normal file
19
src/components/Content/index.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import Row from '@/components/Content/Grid/Row';
|
||||
import Column from '@/components/Content/Grid/Column';
|
||||
import Sidenote from '@/components/Content/Sidenote/Item';
|
||||
import DefinitionList from '@/components/Content/DefinitionList';
|
||||
import DefinitionItem from '@/components/Content/DefinitionList/DefinitionItem';
|
||||
import Accordion from '@/components/Content/Accordion';
|
||||
import AccordionItem from '@/components/Content/Accordion/AccordionItem';
|
||||
|
||||
const ContentComponents = {
|
||||
Column,
|
||||
Row,
|
||||
Sidenote,
|
||||
DefinitionList,
|
||||
DefinitionItem,
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
};
|
||||
|
||||
export default ContentComponents;
|
||||
@@ -7,6 +7,7 @@ export default collection({
|
||||
slugField: 'title',
|
||||
path: 'content/meta/*',
|
||||
format: { contentField: 'content' },
|
||||
entryLayout: 'content',
|
||||
schema: {
|
||||
...createArticleField('meta'),
|
||||
},
|
||||
|
||||
32
src/keystatic/components/general/accordion.ts
Normal file
32
src/keystatic/components/general/accordion.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { listCollapseIcon } from '@keystar/ui/icon/icons/listCollapseIcon';
|
||||
import { repeating, wrapper } from '@keystatic/core/content-components';
|
||||
import { fields } from '@keystatic/core';
|
||||
|
||||
const accordionComponents = {
|
||||
Accordion: repeating({
|
||||
label: 'Accordion',
|
||||
icon: listCollapseIcon,
|
||||
children: ['AccordionItem'],
|
||||
schema: {},
|
||||
}),
|
||||
AccordionItem: wrapper({
|
||||
label: 'Accordion Item',
|
||||
forSpecificLocations: true,
|
||||
schema: {
|
||||
title: fields.text({
|
||||
label: 'Title',
|
||||
validation: {
|
||||
length: {
|
||||
min: 1,
|
||||
},
|
||||
},
|
||||
}),
|
||||
defaultOpen: fields.checkbox({
|
||||
label: 'Open by default',
|
||||
defaultValue: false,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default accordionComponents;
|
||||
@@ -2,7 +2,7 @@ import { listTreeIcon } from '@keystar/ui/icon/icons/listTreeIcon';
|
||||
import { fields } from '@keystatic/core';
|
||||
import { repeating, block } from '@keystatic/core/content-components';
|
||||
|
||||
export const definitionlistComponents = {
|
||||
const definitionlistComponents = {
|
||||
DefinitionList: repeating({
|
||||
label: 'Definition List',
|
||||
icon: listTreeIcon,
|
||||
@@ -11,6 +11,7 @@ export const definitionlistComponents = {
|
||||
}),
|
||||
DefinitionItem: block({
|
||||
label: 'Definition Item',
|
||||
forSpecificLocations: true,
|
||||
schema: {
|
||||
term: fields.text({ label: 'Term' }),
|
||||
definitions: fields.array(
|
||||
@@ -23,3 +24,5 @@ export const definitionlistComponents = {
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default definitionlistComponents;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { grid2X2Icon } from '@keystar/ui/icon/icons/grid2X2Icon';
|
||||
import { fields } from '@keystatic/core';
|
||||
import { repeating, wrapper } from '@keystatic/core/content-components';
|
||||
|
||||
export const gridComponents = {
|
||||
const gridComponents = {
|
||||
Row: repeating({
|
||||
label: 'Row',
|
||||
icon: grid2X2Icon,
|
||||
@@ -59,3 +59,4 @@ export const gridComponents = {
|
||||
},
|
||||
}),
|
||||
};
|
||||
export default gridComponents;
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { gridComponents } from '@/keystatic/components/general/grid';
|
||||
import { sidenoteComponents } from '@/keystatic/components/general/sidenote';
|
||||
import { definitionlistComponents } from '@/keystatic/components/general/definitionlist';
|
||||
import gridComponents from '@/keystatic/components/general/grid';
|
||||
import sidenoteComponents from '@/keystatic/components/general/sidenote';
|
||||
import definitionlistComponents from '@/keystatic/components/general/definitionlist';
|
||||
import accordionComponents from '@/keystatic/components/general/accordion';
|
||||
|
||||
export const generalComponents = {
|
||||
...gridComponents,
|
||||
...sidenoteComponents,
|
||||
...definitionlistComponents,
|
||||
...accordionComponents,
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ import { panelRightDashedIcon } from '@keystar/ui/icon/icons/panelRightDashedIco
|
||||
import { fields } from '@keystatic/core';
|
||||
import { inline } from '@keystatic/core/content-components';
|
||||
|
||||
export const sidenoteComponents = {
|
||||
const sidenoteComponents = {
|
||||
Sidenote: inline({
|
||||
label: 'Sidenote',
|
||||
icon: panelRightDashedIcon,
|
||||
@@ -57,3 +57,5 @@ export const sidenoteComponents = {
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
export default sidenoteComponents;
|
||||
|
||||
@@ -58,4 +58,21 @@ export const tags: Config['tags'] = {
|
||||
},
|
||||
},
|
||||
},
|
||||
Accordion: {
|
||||
render: 'Accordion',
|
||||
children: ['tag'],
|
||||
},
|
||||
AccordionItem: {
|
||||
render: 'AccordionItem',
|
||||
attributes: {
|
||||
title: {
|
||||
type: 'String',
|
||||
required: true,
|
||||
},
|
||||
defaultOpen: {
|
||||
type: 'Boolean',
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
--el-small-font-family: var(--font-body);
|
||||
--el-small-font-size: var(--typo-size-xs);
|
||||
|
||||
/* HR */
|
||||
/* === HR === */
|
||||
--hr-height: var(--size-3);
|
||||
--hr-margin: var(--spacing-relaxed) 0;
|
||||
--hr-color: var(--color-text-tertiary);
|
||||
@@ -254,7 +254,27 @@
|
||||
--hr-symbol-color: var(--color-text-tertiary);
|
||||
--hr-symbol-background: var(--color-surface-base);
|
||||
|
||||
/* Header */
|
||||
/* === SUMMARY === */
|
||||
--el-summary-fg: var(--color-text-inverse);
|
||||
--el-summary-bg: var(--color-surface-inverse);
|
||||
--el-summary-font-family: var(--font-mono);
|
||||
--el-summary-font-size: var(--typo-size-lg);
|
||||
--el-summary-font-weight: var(--typo-weight-black);
|
||||
--el-summary-text-transform: uppercase;
|
||||
--el-summary-line-height: var(--typo-leading-normal);
|
||||
--el-summary-marker-symbol-closed: "▾";
|
||||
--el-summary-marker-symbol-open: "▴";
|
||||
--el-summary-marker-symbol-transition-open: none;
|
||||
|
||||
/* === DETAILS === */
|
||||
--el-details-color: var(--color-text-primary);
|
||||
--el-details-font-family: var(--font-body);
|
||||
--el-details-font-size: var(--typo-size-md);
|
||||
--el-details-line-height: var(--typo-leading-normal);
|
||||
--el-details-padding: var(--spacing-snug);
|
||||
--el-details-border: var(--size-1) solid var(--color-text-secondary);
|
||||
|
||||
/* === Header === */;
|
||||
--el-header-font-size: var(--typo-size-responsive);
|
||||
--el-header-line-height: var(--typo-leading-snug);
|
||||
--el-header-paddingY: var(--spacing-snug);
|
||||
|
||||
Reference in New Issue
Block a user