modulz / interop

The interoperable component styling library.
GNU General Public License v3.0
4 stars 0 forks source link

Brain dump #1

Open StephenHaney opened 4 years ago

StephenHaney commented 4 years ago

Allo 👋

This is an initial effort to organize and catalog the meta data we need to create fully visually editable components.

It's super early, and we are more concerned with identifying the needs and less concerned with solutioning the perfect API at this stage.

Here's one pseudo-code approach with fake examples. Please pick it apart for what won't work:

Registering a component

// Creates a new CSS-in-JS component and registers it with interop:
const Slider = interop({ elem: 'div', name: 'Slider' });

// 1. We need to know about default styles
Slider.style({
  color: 'gray',
  '&:hover': {
    color: 'darkgray',
  }
});

// 2. We need to know about variant props
Slider.variants({
  color: {
    red: {
      color: 'red',
    },
    blue: {
      color: 'blue',
    }
  }
});

// 3. We need to know about utility props:
Slider.util([color, backgroundColor, margin]);

// 4. We need to know about possible states, both CSS and manual:
Slider.states(['hover', 'highlighted', 'focused']);

// 5. We need to know which properties are editable in the UI:
Slider.styleProperties({
  'Fill': 'backgroundColor', // individual properties, friendlyName -> CSS Property
  textPanel: true, // predefined groupings of properties in a panel
})

// 6. We need to know about children parts:
const Track = Slider.addChild({ elem: 'div', name: 'Track' });

Track.style({
  backgroundColor: 'black',
});

Track.styleProperties({
  'height': 'height'
});

// 7. We need to know about non-variant props for the top level component...
// there's opportunity here to use something like TypeScript parsing to build
// these automatically, but starting with a brute force method:
Slider.defineProps({
  upsideDown: Types.Boolean,
  twoHandles: Types.Boolean,
})

Using the Parts to build a component

Registering a new component with interop will return a Styled Component (or CSS-in-JS component of your choosing). We'll probably need to re-arrange this example code or use a property to access the CSS-in-JS component like <Slider.Component />

const MySlider = ({ sliderCss, trackCss }) => (
  // Note sliderCss.highlighted is available here to do custom state wire-up
  <Slider css={sliderCss}>
    <Track css={trackCss} />
  </Slider>
);

Creating examples

// We can just use the real component:
Slider.example = MySlider;

// Or a custom new component:
Slider.example = ({ sliderCss, trackCss }) => (
  <Slider css={sliderCss}>
    <Track css={trackCss} someExampleProp={true} />
  </Slider>
);

// Examples get passed a partState object that lets them have custom logic based on forced states from the editor:
type ExampleProps = {
  sliderCss: StyleConfigPart,
  trackCss: StyleConfigPart,
  partState: { [partId: string]: string } // like { slider: 'normal', track: 'normal' }
}

// Each part can have its own example when selected (will default to parent's example):
Track.example = ({ sliderCss, trackCss, partState }) => {
  console.log(partState);
  /* ^^^ outputs:
  {
    slider: 'normal',
    track: 'hover'
  }

  */
  return (
    <div>
      Track is hovered? {String(partState.track === 'hover')}
      <Track trackCss={trackCss} />
    </div>  
  );
}

Collocating docs

Though I think most docs will be built visually in the Style Guide visual editor, they'll write to the same JSON object as the rest of this... so I think there should be function endpoints to write it too. This is the least defined part of all of this, but wanted to keep it on the list of needs.

Slider.addDocBlock({
  name: 'DocBlock 1',
  // MDX? JSX? MD? Plain text? HTML!?!
  code: (
    <div>Only use this in the header</div>
  ),
});

Data IDs to support hover highlights:

// Rendered HTML for the above examples to support hover highlights and potentially click to select:
<div data-part-id="some-unique-slider">
  <div data-part-id="unique-id-track"></div>
</div>

Global helper data

// Globally, there exists a properties to scale mapping to give the correct theme values in the UI:
const scaleKey = {
  'backgroundColor': ThemeScale.Colors,
  'padding': ThemeScale.size,
};

// And a property to group map to keep properties organized for good muscle memory:
const propertiesToGroups = {
  'backgroundColor': PropertyGroups.Colors,
  'letterSpacing': PropertyGroups.Text
}

What am I forgetting?!

StephenHaney commented 4 years ago

What's missing:

Compared to our recent prototypes, this leaves out the ability to create "fake parts" in the UI tree that don't map 1:1 to CSS components.

kof commented 4 years ago

The downside of having an extensive interop API that covers so many aspects of a component is the requirement it puts on the author to use the proprietary API and the commitment to depend on the API and the platform.

What if there would be a way to use props only as a public API and render component on different targets same way react renders them to dom, string or native platforms?

I might be lacking the context here though.

StephenHaney commented 4 years ago

The downside of having an extensive interop API that covers so many aspects of a component is the requirement it puts on the author to use the proprietary API and the commitment to depend on the API and the platform.

Yeah, that's for sure.

It's all very early on this idea, but the elevator pitch on interop is "use this CSS-in-JS library and it will export to design tools, is visually editable in JSX tools, and generates its own docs".

It might even just be a layer on top of existing CSS-in-JS libraries, so we don't reinvent the wheel.

I am definitely keen to use props and TS definitions to reduce the manual registration work and API weight. You do need some additional information to make the above goals possible though – and you see things like Framer's addPropertyControls popping up to provide this additional information.

We've been working on this internally for Modulz primitives. The registration API is verbose at the moment; we're kind of living in the problem space for now until we fully understand the requirements, and then we'll polish it up into something friendly and approachable.

Anyway, that's the context. Would love to hear more of your thoughts about this.

kof commented 4 years ago

@StephenHaney Yeah sounds like a good approach to start with something you can control and then add more things which can be supported out of the box with no registration code like className, style, classes, css prop. The most promising approach seems to be https://theme-ui.com/ though.

StephenHaney commented 4 years ago

Yeah, big fan of their work. I actually talked with John and Jackson specifically on the need for more info than props provide... they're trying to figure out the same thing for Blocks, though they aren't trying to export the components to drawing tools as well. At the time, they forked Framer's addPropertyControls.