0.4.251 • Published 25 days ago

@visma/formula v0.4.251

Weekly downloads
-
License
ISC
Repository
github
Last release
25 days ago

@visma/formula 🏎

React component for configurable forms. Optionally connect to the backend to fetch external config and submit form data to.

Requirements

  1. Material UI v4, MUI v5 and react-intl are required. Install and set up if necessary:
npm i @visma/formula @emotion/styled @emotion/react @mui/x-date-pickers @mui/base @mui/material @material-ui/core @material-ui/styles @material-ui/icons @material-ui/lab react-intl --legacy-peer-deps
  1. Add Vite / Webpack alias for @emotion/core:
// vite.config.js
//...
export default defineConfig({
   resolve: {
     alias: {
       '@emotion/core': '@emotion/react',
     },
   },
});

// webpack.config.js
module.exports = {
  //...
   resolve: {
     alias: {
       '@emotion/core': '@emotion/react',
     },
   },
};
## Examples

### Login form

```js
import Formula from '@visma/formula';

<Formula
  config={{
    title: 'Log In',
    elements: [
      {
        key: 'email',
        type: 'email',
        name: 'Email Address',
        required: true,
      },
      {
        key: 'password',
        type: 'password',
        name: 'Password',
        required: true,
      },
    ],
  }}
  onSubmit={({ values }) => console.log(values)}
/>;

Use external config, prefill some fields

import Formula from '@visma/formula';

<Formula
  axios={(axios) => {
    axios.defaults.baseURL = 'https://example.com/formula/api';
    axios.defaults.headers.common.Authorization = 'Bearer <token>';
  }}
  id="1"
  // Assuming form has at least a formGroup with key `customer`, containing
  // fields with keys `firstName` & `lastName`.
  formData={useMemo(
    () => ({
      customer: {
        firstName: user.firstName,
        lastName: user.lastName,
      },
    }),
    [user]
  )}
/>;

Components

<Formula>

Props

One of config, id or dataId is required. Rest are optional.

NameTypeDescription
configFormForm config
formDataanyOptional, prefilled form data. Ensure the reference does not change undesirably, e.g. using useMemo.
idstringExternal form config id
dataIdstringResume editing
onPreSubmitasync (args: Args, event: SubmitEvent) => void \ | boolean \ | [Args, SubmitEvent]Run code before submit. Falsy return value prevents the submit. Return true or modified args to submit.
onSubmit({ values }) => voidOverride default submit handler
onPostSubmit(dataId, { values }) => voidGet dataId of submitted form data
confirmboolean \ | { title: ReactElement, description: ReactElement }Show confirm dialog or use object for other messages. Default: true
axiosaxios => voidGet access to API client's axios instance e.g. to set defaults
dateFnsLocaleLocale from date-fnsExamples:import useDateFnsLocale from '@visma/react-app-locale-utils/lib/useDateFnsLocale.js';import { fi } from 'date-fns/locale';
childrenReactElementOverride default submit button. Set <></> (empty React Frament) to render nothing.
reviewbooleanShow review after the form has been submitted. Default: true
forceReviewbooleanShow review directly. Default: false
reviewProps{ actions: ReactNode, showSuccessText: boolean, highlightSuccessText: boolean, hideNotAnswered: boolean }actions: Additional action buttonsshowSuccessText: show success text and summary in review, default truehighlightSuccessText: make summary more noticeable, default falsehideNotAnswered: hide not answered fields in review, user can see the full form by unchecking a checkbox, default false
fillProps{ actions: ReactNode, disableSteps: boolean, disableResetFormdata: boolean, disableElementButtons: boolean, showScores: boolean, disablePrint: boolean }actions: Additional action buttonsdisableSteps: disables steps when filling the form, default falsedisableResetFormdata: disable resetting formData to initial formData in disabled fields, default falsedisableElementButtons: disable all button elements, default falseshowScores: show scores if required metadata is available, default falsedisablePrint: disable print button in ConfirmDialog, default false
confirmComponent, previewField, reviewFieldcomponentCustomize
customMessages{ submit: string, reviewSubmitConfirmation: string, confirmDialogTitle: string, confirmDialogConsent: string, confirmDialogPreview: string, confirmDialogSendButton: string, confirmDialogCancelButton: string, error: string }Overrides default texts in submit button, confirmation dialog, confirm message and error.
buttonActionsobjectFunctions for button elements, {functionKey: (buttonActionProps) => boolean}

<FormulaProvider>

Provide options for any <Form> component in children.

Required to use API hooks.

Props

Same as for <Formula>, except:

  • Without config, id, dataId
  • children: ReactElement: App, wrapped forms

<Form>

Props

config, id, dataId and children from <Formula>

Hooks

See src/api.js for all API hooks.

List forms

import { useForms } from '@visma/formula';

function ListForms() {
  const forms = useForms({ status: 'published', visibility: 'public' });
  // ...
}

Form config details

import { useForm } from '@visma/formula';

function FormTitle({ id }) {
  const form = useForm(id);

  return <h1>{form.title}</h1>;
}

Intercept built-in submit function

import { Formula, useMutations } from '@visma/formula';
// ...
const { submit } = useMutations();

<Formula
  onSubmit={async (...args) => {
    try {
      return await submit(...args);
    } catch (error) {
      logger(error);
      throw error;
    }
  }}
  // ...
/>;

Customize

Confirm dialog (confirmComponent)

Example:

import {
  DialogActions,
  DialogContent,
  DialogContentText,
} from '@material-ui/core';
import produce, { original } from 'immer';
import { FormattedMessage, useIntl } from 'react-intl';

export function CustomConfirm({ config, formData, children }) {
  const intl = useIntl();

  // children, the original dialog, is readonly – use produce from immer to make deep immutable changes.
  return produce(children, (children) => {
    const dialogContentElement = children.props.children.find(
      (element) => element && original(element)?.type === DialogContent
    );

    if (config.meta?.showScoreOnPreview && dialogContentElement) {
      dialogContentElement.props.children.splice(
        2,
        0,
        <DialogContentText>
          <FormattedMessage
            defaultMessage="Vastauksesi antavat sinulle {score} pistettä."
            values={{
              score: Math.ceil(Math.random() * config.meta.maxScore),
            }}
          />
        </DialogContentText>
      );
    }

    // Reverse dialog children order 🤪
    children.props.children.reverse();

    // Reverse dialog action button order
    children.props.children
      .find((element) => element && original(element)?.type === DialogActions)
      ?.props.children.reverse();

    // If set, override consent message
    const consentElement = dialogContentElement?.props.children.find(
      (element) => element?.key === 'consent'
    );
    if (consentElement) {
      consentElement.props.label = intl.formatMessage({
        defaultMessage:
          'Kyllä, haluan lähettää tiedot ja osallistua palkinnon arvontaan 🏆',
      });
    }
  });
}

Preview (previewField) & Review Field (reviewField)

Example:

import produce from 'immer';
import { sortBy } from 'lodash';

export function CustomPreviewField({ formData, uiSchema, children }) {
  const dataElement = children[1];

  // children, the original field, is readonly – use produce from immer to make deep immutable changes.
  return produce(children, (children) => {
    if (uiSchema['ui:options'].element.meta.showScoreOnPreview) {
      const highlight = sortBy(
        uiSchema['ui:options'].element.meta.highlightColors,
        ['scoreGreaterThan']
      )
        .reverse()
        .find(({ scoreGreaterThan }) => scoreGreaterThan < formData);

      if (highlight) {
        children[1] = (
          <div style={{ display: 'flex' }}>
            <div style={{ flex: '1 1' }}>{dataElement}</div>
            <div
              style={{
                height: '1.2rem',
                width: '1.2rem',
                color: highlight.color,
              }}
            >
              <svg
                xmlns="http://www.w3.org/2000/svg"
                className="h-6 w-6"
                fill="none"
                viewBox="0 0 24 24"
                stroke="currentColor"
                strokeWidth={2}
              >
                <path
                  strokeLinecap="round"
                  strokeLinejoin="round"
                  d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
                />
              </svg>
            </div>
          </div>
        );
      }
    }
  });
}

Load library dynamically

  1. Call init from @visma/formula/lib/dll before using the API:

    import { init } from '@visma/formula/lib/dll';
    import App from 'components/App';
    import React from 'react';
    import ReactDOM from 'react-dom';
    
    async function main() {
      await init('https://example.com/formula');
    
      ReactDOM.render(
        <React.StrictMode>
          <App />
        </React.StrictMode>,
        document.getElementById('root')
      );
    }
    
    main();
  2. Import the API from @visma/formula/lib/dll. Note that all components and hooks are available only using the default export:

    import DLL from '@visma/formula/lib/dll';
    
    <DLL.Formula
      axios={(axios) => {
        axios.defaults.baseURL = 'https://example.com/formula/api';
        axios.defaults.headers.common.Authorization = 'Bearer <token>';
      }}
      id="1"
    />;
     async function main() {
      await init('https://example.com/formula');
    
    
      ReactDOM.render(
        <DLL.FormulaProvider
        axios={(axios) => {
          axios.defaults.baseURL = 'https://example.com/formula';
        }}
      >
        <App />
      </DLL.FormulaProvider>
        , document.getElementById('root'));
    }
    main();

    After wrapping App with the Provider, Formula component can be used anywhere inside the App

FormulaComponent

   import {IntlProvider} from "react-intl";
   import DLL from "@visma/formula/lib/dll";
   import React from "react";
   
   const FormulaComponent = (props) => {
     return (
       <IntlProvider locale={'fi-FI'}>
         <DLL.Form
           id={props?.formId}
           dataId={props?.formResponseId}
           credentials={props?.credentials}
           ...
         >
         </DLL.Form>
       </IntlProvider>
     );
   }
   
   export default FormulaComponent;
0.4.251

25 days ago

0.4.250

27 days ago

0.4.249

2 months ago

0.4.248

2 months ago

0.4.247

3 months ago

0.4.245

3 months ago

0.4.244

4 months ago

0.4.243

4 months ago

0.4.242

5 months ago

0.4.241

5 months ago

0.4.240

5 months ago

0.4.197

10 months ago

0.4.199

9 months ago

0.4.198

9 months ago

0.4.219

6 months ago

0.4.218

6 months ago

0.4.217

6 months ago

0.4.223

6 months ago

0.4.222

6 months ago

0.4.221

6 months ago

0.4.220

6 months ago

0.4.227

5 months ago

0.4.226

5 months ago

0.4.225

6 months ago

0.4.224

6 months ago

0.4.209

8 months ago

0.4.208

8 months ago

0.4.207

8 months ago

0.4.206

8 months ago

0.4.212

7 months ago

0.4.211

8 months ago

0.4.210

8 months ago

0.4.216

6 months ago

0.4.215

7 months ago

0.4.214

7 months ago

0.4.213

7 months ago

0.4.201

9 months ago

0.4.200

9 months ago

0.4.205

8 months ago

0.4.204

8 months ago

0.4.203

8 months ago

0.4.202

9 months ago

0.4.215-hotfix

6 months ago

0.4.239

5 months ago

0.4.229

5 months ago

0.4.228

5 months ago

0.4.230

5 months ago

0.4.234

5 months ago

0.4.233

5 months ago

0.4.232

5 months ago

0.4.231

5 months ago

0.4.237

5 months ago

0.4.236

5 months ago

0.4.235

5 months ago

0.4.193

12 months ago

0.4.192

12 months ago

0.4.196

11 months ago

0.4.195

11 months ago

0.4.194

12 months ago

0.4.191

12 months ago

0.4.190

12 months ago

0.4.182

1 year ago

0.4.181

1 year ago

0.4.180

1 year ago

0.4.186

1 year ago

0.4.185

1 year ago

0.4.183

1 year ago

0.4.189

12 months ago

0.4.188

1 year ago

0.4.187

1 year ago

0.4.171

1 year ago

0.4.170

1 year ago

0.4.175

1 year ago

0.4.174

1 year ago

0.4.173

1 year ago

0.4.172

1 year ago

0.4.179

1 year ago

0.4.178

1 year ago

0.4.177

1 year ago

0.4.176

1 year ago

0.4.160

1 year ago

0.4.164

1 year ago

0.4.163

1 year ago

0.4.162

1 year ago

0.4.161

1 year ago

0.4.168

1 year ago

0.4.167

1 year ago

0.4.166

1 year ago

0.4.165

1 year ago

0.4.169

1 year ago

0.4.153

1 year ago

0.4.152

1 year ago

0.4.151

1 year ago

0.4.150

1 year ago

0.4.157

1 year ago

0.4.156

1 year ago

0.4.155

1 year ago

0.4.154

1 year ago

0.4.159

1 year ago

0.4.158

1 year ago

0.4.142

1 year ago

0.4.141

1 year ago

0.4.140

1 year ago

0.4.146

1 year ago

0.4.145

1 year ago

0.4.144

1 year ago

0.4.143

1 year ago

0.4.149

1 year ago

0.4.148

1 year ago

0.4.147

1 year ago

0.4.139

1 year ago

0.4.138

1 year ago

0.4.129

1 year ago

0.4.131

1 year ago

0.4.130

1 year ago

0.4.135

1 year ago

0.4.134

1 year ago

0.4.133

1 year ago

0.4.132

1 year ago

0.4.137

1 year ago

0.4.136

1 year ago

0.4.128

1 year ago

0.4.127

2 years ago

0.4.126

2 years ago

0.4.125

2 years ago

0.4.119

2 years ago

0.4.118

2 years ago

0.4.120

2 years ago

0.4.124

2 years ago

0.4.123

2 years ago

0.4.122

2 years ago

0.4.121

2 years ago

0.4.113

2 years ago

0.4.112

2 years ago

0.4.111

2 years ago

0.4.117

2 years ago

0.4.116

2 years ago

0.4.115

2 years ago

0.4.114

2 years ago

0.4.97

2 years ago

0.4.109

2 years ago

0.4.98

2 years ago

0.4.108

2 years ago

0.4.107

2 years ago

0.4.99

2 years ago

0.4.110

2 years ago

0.4.102

2 years ago

0.4.101

2 years ago

0.4.100

2 years ago

0.4.105

2 years ago

0.4.104

2 years ago

0.4.95

2 years ago

0.4.96

2 years ago

0.4.93

2 years ago

0.4.94

2 years ago

0.4.91

2 years ago

0.4.92

2 years ago

0.4.90

2 years ago

0.4.86

2 years ago

0.4.87

2 years ago

0.4.84

2 years ago

0.4.85

2 years ago

0.4.88

2 years ago

0.4.89

2 years ago

0.4.82

2 years ago

0.4.83

2 years ago

0.4.80

2 years ago

0.4.81

2 years ago

0.4.75

2 years ago

0.4.76

2 years ago

0.4.79

2 years ago

0.4.77

2 years ago

0.4.78

2 years ago

0.4.73

2 years ago

0.4.74

2 years ago

0.4.71

2 years ago

0.4.72

2 years ago

0.4.70

2 years ago

0.4.68

2 years ago

0.4.69

2 years ago

0.4.64

2 years ago

0.4.65

2 years ago

0.4.62

2 years ago

0.4.63

2 years ago

0.4.61

2 years ago

0.4.66

2 years ago

0.4.67

2 years ago

0.4.60

2 years ago

0.4.53

2 years ago

0.4.54

2 years ago

0.4.51

2 years ago

0.4.52

2 years ago

0.4.50

2 years ago

0.4.59

2 years ago

0.4.57

2 years ago

0.4.58

2 years ago

0.4.55

2 years ago

0.4.56

2 years ago

0.4.42

3 years ago

0.4.43

3 years ago

0.4.40

3 years ago

0.4.41

3 years ago

0.4.48

3 years ago

0.4.49

3 years ago

0.4.46

3 years ago

0.4.47

3 years ago

0.4.44

3 years ago

0.4.45

3 years ago

0.4.39

3 years ago

0.4.38

3 years ago

0.4.31

3 years ago

0.4.32

3 years ago

0.4.37

3 years ago

0.4.35

3 years ago

0.4.36

3 years ago

0.4.33

3 years ago

0.4.34

3 years ago

0.4.30

3 years ago

0.4.28

3 years ago

0.4.29

3 years ago

0.4.26

3 years ago

0.4.27

3 years ago

0.4.24

3 years ago

0.4.25

3 years ago

0.4.23

3 years ago

0.4.21

3 years ago

0.4.22

3 years ago

0.4.19

3 years ago

0.4.18

3 years ago

0.4.17

3 years ago

0.4.16

3 years ago

0.4.15

3 years ago

0.4.14

3 years ago

0.4.13

3 years ago

0.4.12

3 years ago

0.4.11

3 years ago

0.4.10

3 years ago

0.4.9

3 years ago

0.4.8-next.3

3 years ago

0.4.8

3 years ago

0.4.5

3 years ago

0.4.7

3 years ago

0.4.6

3 years ago

0.4.4

3 years ago

0.4.3

3 years ago

0.4.2

3 years ago

0.4.1

3 years ago

0.4.0

3 years ago

0.3.0

3 years ago

0.2.2

3 years ago

0.2.1

3 years ago

0.2.0

3 years ago

0.1.0

3 years ago