measuredco / puck

The visual editor for React
https://puckeditor.com
MIT License
5.28k stars 322 forks source link

Colocate component params to the field level #632

Open mkilpatrick opened 1 month ago

mkilpatrick commented 1 month ago

Components have different params that operate at the full component level: fields, defaultProps, resolveData, etc. It would be great if these params could also work at the field level. For instance, if I create a 'custom' field that is consumable by others but that field also needs special handling in resolveData, I would need to expose multiple functions that the user needs to call. Additionally, if the user calls my custom resolveData function, they can no longer do their own resolveData things if they needed to without reimplementing what my function does.

Below, exposedMyThingFunc returns a 'custom' RenderProps that might show two fields: one for id and one for value (for the myThing field level).

This is the current way:

foo: {
  myThing: {
    id: string;
    value: string;
  },
  userThing: string
}

fields: {
  foo: {
    myThing: exposedMyThingFieldFunc()
    userThing: {
      type: "text"
      label: "User Thing"
    }
  }
},
defaultProps: {
  foo: {
    myThing: exposedMyThingDefaultPropsFunc(),
    userThing: "userVal"
  }
},
resolveData: exposedMyThingResolveDataFunc()

It would be awesome if I could write something I can expose to user that encompasses all of the things you can do for a component at the individual field level. The above could look something like this instead:

foo: {
  myThing: {
    id: string;
    value: string;
  },
  userThing: string
}

customFields: { // unclear if this should be a different top level or what the name should be
  foo: {
    myThing: {
      fields: exposedMyThingFieldFunc(),
      defaultProps: exposedMyThingDefaultPropsFunc,
      resolveData: exposedMyThingResolveDataFunc()
    }
  }
}
fields: {
  foo: {
    userThing: {
      type: "text"
      label: "User Thing"
    }
  }
},
defaultProps: {
  foo: {
    userThing: "userVal"
  }
},

The idea here is that functionality is grouped at the field level rather than the component level. You can imagine going further where instead of this:

customFields: {
  foo: {
    myThing: {
      fields: exposedMyThingFieldFunc(),
      defaultProps: exposedMyThingDefaultPropsFunc,
      resolveData: exposedMyThingResolveDataFunc()
    }
  }
}

I expose something to my user that does everything:

customFields: {
  foo: {
    myThing: exposedCompleteMyThingFunc()
  }
}