Skip to content

NEXT: Review options for sharing and maintaining common component data between frameworks #2811

Open
@endigo9740

Description

@endigo9740

Following our recent integration of Zag.js, we're now unifying logic and state handling for most components and features using Zag itself. However, there's still a lot of repetition in how we scaffold components per each framework, including:

  1. Common prop names
  2. Common default prop values
  3. Common types and interfaces
  4. Common JSDoc comments for type definitions

We should investigate potential solutions to provide a single source of truth for this information.

Potential Issues

Hugo and I have discussed this on several occasions, but the biggest barrier of entry is that not all components are constructed symmetrically per framework. For simple components such as Avatars the implementation is quite close. The same list of props, values, types, etc:

However, this is not always the case. Select components, such as Accordions and Tabs, can differ wildly:

The most common reason for this being React (or JSX-based component systems) lacking an 1:1 equivalent for named slots. For example:

Svelte:

<Tabs bind:value={group}>
	{#snippet list()}
		<Tabs.Control value="plane">Plane</Tabs.Control>
		<Tabs.Control value="boat">Boat</Tabs.Control>
		<Tabs.Control value="car">Car</Tabs.Control>
	{/snippet}
	{#snippet content()}
		<Tabs.Panel value="plane">Plane Panel - {lorem}</Tabs.Panel>
		<Tabs.Panel value="boat">Boat Panel - {lorem}</Tabs.Panel>
		<Tabs.Panel value="car">Car Panel - {lorem}</Tabs.Panel>
	{/snippet}
</Tabs>

React:

<Tabs value={group} onValueChange={setGroup}>
	<Tabs.List>
		<Tabs.Control value="plane">Plane</Tabs.Control>
		<Tabs.Control value="boat">Boat</Tabs.Control>
		<Tabs.Control value="car">Car</Tabs.Control>
	</Tabs.List>
	<Tabs.Content>
		<Tabs.Panel value="plane">Plane Panel - {lorem}</Tabs.Panel>
		<Tabs.Panel value="boat">Boat Panel - {lorem}</Tabs.Panel>
		<Tabs.Panel value="car">Car Panel - {lorem}</Tabs.Panel>
	</Tabs.Content>
</Tabs>

While fairly similar from the end user's perspective, there's some notable differences:

  • Svelte: uses snippets for List/Control; requires only 3 components (Root, List, Panel)
  • React: requires dedicated components for each part of the component tree structure

Note that React does supply React.Node props, allowing you to pass template data through props. But the user-facing API for doing this is not widely used in any framework we've reviewed. And personally speaking is quite unfriendly:

<Tabs
	value={group}
	onValueChange={setGroup}
	list=(
		<Tabs.Control value="plane">Plane</Tabs.Control>
		<Tabs.Control value="boat">Boat</Tabs.Control>
		<Tabs.Control value="car">Car</Tabs.Control>
	)
	content=(
		<Tabs.Panel value="plane">Plane Panel - {lorem}</Tabs.Panel>
		<Tabs.Panel value="boat">Boat Panel - {lorem}</Tabs.Panel>
		<Tabs.Panel value="car">Car Panel - {lorem}</Tabs.Panel>
	)
/>

This is great for small additions, such as icons. But would quickly become unmanageable once the size of the child content reached a certain length.

Potential Solutions

So far we do not have a solid solution for this scenario. However, the following ideas have been discussed:

  1. Standardize all components around the composed tree of components (ala React), unfortunately this hurts the DX for Svelte, Vue and other frameworks on both the end user and maintainer/contributor side of things. We don't get to use the handy tools they provide (Snippets, slots, etc)
  2. Keep separate structures, but find an alternative way to define the non-uniform props/types per these components. Unfortunately this returns us to the original issue we're trying to solve.

We welcome additional ideas and proposals around this in the comments section below!

Metadata

Metadata

Labels

administrationItems related to the project but outside the code.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions