Skip to content

Commit

Permalink
Merge pull request #177 from open-formulieren/refactor/mode-toggle
Browse files Browse the repository at this point in the history
Refactor the mode toggle into own component
  • Loading branch information
sergei-maertens authored Aug 16, 2024
2 parents e7d0a19 + 48cb938 commit 4a175fa
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 49 deletions.
47 changes: 3 additions & 44 deletions src/components/ComponentPreview.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -27,7 +27,7 @@ const ComponentPreviewWrapper: React.FC<ComponentPreviewWrapperProps> = ({
onComponentChange,
children,
}) => {
const [previewMode, setpreviewMode] = useState<PreviewState>('rich');
const [previewMode, setPreviewMode] = useState<PreviewState>('rich');
const builderContext = useContext(BuilderContext);

return (
Expand All @@ -36,10 +36,7 @@ const ComponentPreviewWrapper: React.FC<ComponentPreviewWrapperProps> = ({
<h4 className="card-title mb-0">
<FormattedMessage description="Component preview card title" defaultMessage="Preview" />
</h4>
<PreviewModeToggle
mode={previewMode}
onChange={event => setpreviewMode(event.target.value as PreviewState)}
/>
<PreviewModeToggle previewMode={previewMode} setPreviewMode={setPreviewMode} />
</div>

{previewMode === 'JSON' ? (
Expand Down Expand Up @@ -114,42 +111,4 @@ const GenericComponentPreview: React.FC<GenericComponentPreviewProps> = ({
);
};

export type PreviewState = 'rich' | 'JSON';

interface PreviewModeToggleProps {
mode: PreviewState;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

export const PreviewModeToggle: React.FC<PreviewModeToggleProps> = ({mode, onChange}) => {
const isRichPreview = mode === 'rich';
const isJSON = mode === 'JSON';
return (
<div className="btn-group btn-group-toggle">
<label className={clsx('btn', 'btn-sm', 'btn-secondary', {active: isRichPreview})}>
<input
type="radio"
name="previewMode"
value="rich"
autoComplete="off"
checked={isRichPreview}
onChange={onChange}
/>
<FormattedMessage description="Component 'Rich' preview mode" defaultMessage="Form" />
</label>
<label className={clsx('btn', 'btn-sm', 'btn-secondary', {active: isJSON})}>
<input
type="radio"
name="previewMode"
value="JSON"
autoComplete="off"
checked={isJSON}
onChange={onChange}
/>
<FormattedMessage description="Component 'JSON' preview mode" defaultMessage="JSON" />
</label>
</div>
);
};

export default GenericComponentPreview;
15 changes: 15 additions & 0 deletions src/components/ModeToggle.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {ArgTypes, Canvas, Meta} from '@storybook/blocks';

import * as ModeToggleStories from './ModeToggle.stories';

<Meta of={ModeToggleStories} />

# Mode toggle

The mode toggle is a button group control to select a particular (presentation) mode.

<Canvas of={ModeToggleStories.Default} />

## Props

<ArgTypes />
25 changes: 25 additions & 0 deletions src/components/ModeToggle.stories.ts
Original file line number Diff line number Diff line change
@@ -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<typeof ModeToggle>;

type Story = StoryObj<typeof ModeToggle>;

export const Default: Story = {};
68 changes: 68 additions & 0 deletions src/components/ModeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import clsx from 'clsx';

export interface Mode<T extends string> {
value: T;
label: React.ReactNode;
}

export interface ModeToggleProps<T extends string> {
/**
* Name attribute for the HTML radio inputs.
*/
name: string;
/**
* Array of possible modes - a list of objects with keys `value` and `label`.
*/
modes: Mode<T>[];
/**
* 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: `<ModeToggle<'mode1' | 'mode2'>>`
*/
function ModeToggle<T extends string>({
name,
modes,
currentMode,
onToggle,
className,
btnClassName = 'btn-secondary',
}: ModeToggleProps<T>) {
return (
<div className={clsx('btn-group', 'btn-group-toggle', className)}>
{modes.map(({value, label}) => (
<label className={clsx('btn', 'btn-sm', btnClassName, {active: value === currentMode})}>
<input
type="radio"
name={name}
value={value}
autoComplete="off"
checked={value === currentMode}
onChange={event => onToggle(event.target.value as T)}
/>
{label}
</label>
))}
</div>
);
}

export default ModeToggle;
34 changes: 34 additions & 0 deletions src/components/PreviewModeToggle.tsx
Original file line number Diff line number Diff line change
@@ -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<PreviewModeToggleProps> = ({previewMode, setPreviewMode}) => (
<ModeToggle<PreviewState>
name="previewMode"
currentMode={previewMode}
onToggle={mode => setPreviewMode(mode)}
modes={[
{
value: 'rich',
label: (
<FormattedMessage description="Component 'Rich' preview mode" defaultMessage="Form" />
),
},
{
value: 'JSON',
label: (
<FormattedMessage description="Component 'JSON' preview mode" defaultMessage="JSON" />
),
},
]}
/>
);

export default PreviewModeToggle;
7 changes: 2 additions & 5 deletions src/registry/content/edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -57,10 +57,7 @@ const EditForm: EditFormDefinition<ContentComponentSchema> = () => {
<>
<div className="card panel preview-panel">
<div className="card-header d-flex justify-content-end">
<PreviewModeToggle
mode={previewMode}
onChange={event => setPreviewMode(event.target.value as PreviewState)}
/>
<PreviewModeToggle previewMode={previewMode} setPreviewMode={setPreviewMode} />
</div>
<div className="card-body">
{previewMode === 'JSON' ? (
Expand Down

0 comments on commit 4a175fa

Please sign in to comment.