diff --git a/src/components/ComponentPreview.tsx b/src/components/ComponentPreview.tsx index 75941988..5c9e2813 100644 --- a/src/components/ComponentPreview.tsx +++ b/src/components/ComponentPreview.tsx @@ -1,9 +1,9 @@ import {JSONEditor} from '@open-formulieren/monaco-json-editor'; -import clsx from 'clsx'; import {Formik} from 'formik'; import React, {useContext, useState} from 'react'; import {FormattedMessage} from 'react-intl'; +import PreviewModeToggle, {PreviewState} from '@/components/PreviewModeToggle'; import {BuilderContext} from '@/context'; import {getRegistryEntry} from '@/registry'; import {AnyComponentSchema, hasOwnProperty} from '@/types'; @@ -27,7 +27,7 @@ const ComponentPreviewWrapper: React.FC = ({ onComponentChange, children, }) => { - const [previewMode, setpreviewMode] = useState('rich'); + const [previewMode, setPreviewMode] = useState('rich'); const builderContext = useContext(BuilderContext); return ( @@ -36,10 +36,7 @@ const ComponentPreviewWrapper: React.FC = ({

- setpreviewMode(event.target.value as PreviewState)} - /> + {previewMode === 'JSON' ? ( @@ -114,42 +111,4 @@ const GenericComponentPreview: React.FC = ({ ); }; -export type PreviewState = 'rich' | 'JSON'; - -interface PreviewModeToggleProps { - mode: PreviewState; - onChange: (e: React.ChangeEvent) => void; -} - -export const PreviewModeToggle: React.FC = ({mode, onChange}) => { - const isRichPreview = mode === 'rich'; - const isJSON = mode === 'JSON'; - return ( -
- - -
- ); -}; - export default GenericComponentPreview; diff --git a/src/components/ModeToggle.mdx b/src/components/ModeToggle.mdx new file mode 100644 index 00000000..81ddd6b5 --- /dev/null +++ b/src/components/ModeToggle.mdx @@ -0,0 +1,15 @@ +import {ArgTypes, Canvas, Meta} from '@storybook/blocks'; + +import * as ModeToggleStories from './ModeToggle.stories'; + + + +# Mode toggle + +The mode toggle is a button group control to select a particular (presentation) mode. + + + +## Props + + diff --git a/src/components/ModeToggle.stories.ts b/src/components/ModeToggle.stories.ts new file mode 100644 index 00000000..dec9191f --- /dev/null +++ b/src/components/ModeToggle.stories.ts @@ -0,0 +1,25 @@ +import {Meta, StoryObj} from '@storybook/react'; +import {fn} from '@storybook/test'; + +import ModeToggle from './ModeToggle'; + +export default { + title: 'Generic/ModeToggle', + component: ModeToggle, + parameters: { + modal: {noModal: true}, + }, + args: { + name: 'story', + modes: [ + {value: 'mode1', label: 'Mode 1'}, + {value: 'mode2', label: 'Mode 2'}, + ], + currentMode: 'mode2', + onToggle: fn(), + }, +} satisfies Meta; + +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/components/ModeToggle.tsx b/src/components/ModeToggle.tsx new file mode 100644 index 00000000..0090d059 --- /dev/null +++ b/src/components/ModeToggle.tsx @@ -0,0 +1,68 @@ +import clsx from 'clsx'; + +export interface Mode { + value: T; + label: React.ReactNode; +} + +export interface ModeToggleProps { + /** + * Name attribute for the HTML radio inputs. + */ + name: string; + /** + * Array of possible modes - a list of objects with keys `value` and `label`. + */ + modes: Mode[]; + /** + * Value of the currently active mode. + */ + currentMode: T; + /** + * Callback invoked when a particular mode is selected. + */ + onToggle: (mode: T) => void; + /** + * Any additional classnames to apply to the container element. + */ + className?: string; + /** + * Toggle button classname(s). + */ + btnClassName?: string; +} + +/** + * Render a button group with the available and active modes. + * + * This component is generic - pass a union of the possible mode values for strict type + * checking: `>` + */ +function ModeToggle({ + name, + modes, + currentMode, + onToggle, + className, + btnClassName = 'btn-secondary', +}: ModeToggleProps) { + return ( +
+ {modes.map(({value, label}) => ( + + ))} +
+ ); +} + +export default ModeToggle; diff --git a/src/components/PreviewModeToggle.tsx b/src/components/PreviewModeToggle.tsx new file mode 100644 index 00000000..9ecb6289 --- /dev/null +++ b/src/components/PreviewModeToggle.tsx @@ -0,0 +1,34 @@ +import {FormattedMessage} from 'react-intl'; + +import ModeToggle from '@/components/ModeToggle'; + +export type PreviewState = 'rich' | 'JSON'; + +export interface PreviewModeToggleProps { + previewMode: PreviewState; + setPreviewMode: (mode: PreviewState) => void; +} + +const PreviewModeToggle: React.FC = ({previewMode, setPreviewMode}) => ( + + name="previewMode" + currentMode={previewMode} + onToggle={mode => setPreviewMode(mode)} + modes={[ + { + value: 'rich', + label: ( + + ), + }, + { + value: 'JSON', + label: ( + + ), + }, + ]} + /> +); + +export default PreviewModeToggle; diff --git a/src/registry/content/edit.tsx b/src/registry/content/edit.tsx index 3033105e..4a16ed29 100644 --- a/src/registry/content/edit.tsx +++ b/src/registry/content/edit.tsx @@ -4,7 +4,7 @@ import {useFormikContext} from 'formik'; import React, {useContext, useEffect, useRef, useState} from 'react'; import {FormattedMessage, defineMessage, useIntl} from 'react-intl'; -import {PreviewModeToggle, PreviewState} from '@/components/ComponentPreview'; +import PreviewModeToggle, {PreviewState} from '@/components/PreviewModeToggle'; import { BuilderTabs, Hidden, @@ -57,10 +57,7 @@ const EditForm: EditFormDefinition = () => { <>
- setPreviewMode(event.target.value as PreviewState)} - /> +
{previewMode === 'JSON' ? (