Skip to content

DRAFT FOR COMMENT: Design system modularization#2559

Closed
bheston wants to merge 2 commits intomasterfrom
users/bheston/wip-recipe-provider-experiment
Closed

DRAFT FOR COMMENT: Design system modularization#2559
bheston wants to merge 2 commits intomasterfrom
users/bheston/wip-recipe-provider-experiment

Conversation

@bheston
Copy link
Copy Markdown
Collaborator

@bheston bheston commented Jan 7, 2020

Initial idea for converting recipe parameters into a style provider module.

Description

Goals

  • Allow different design system designs to apply style as modular elements, especially with different algorithms like for elevation, corners, or colors.
    The current recipes can't represent a wide variety of intended looks through properties alone.
  • Allow instances of components to accept style overrides by swapping modules instead of rewriting css.
    For instance, a one-off special color button.
  • Make styles more composable. For instance, the only difference between the different button appearances is the combination or fill, outline, and foreground recipes.

Transition groups of design system properties (for example, color ramp deltas) and associated functions (ex: color recipes) into a single resource (object) that can be configured, referenced, and replaced in a modular fashion. Allows for easily adding new recipe types or changing design without needing to restyle components.

For instance, the design system provides a resource that knows how to provide outline color swatches. The Outline Button references that resource, which by default might use the neutral ramp and apply states by offsets on the same ramp. Another design could provide an alternate implementation, like outlines are neutral colored by default, and go to accent on hover or active states. Another example would be a default shadow algorithm being replaced by another that does something outside of the parameters available in the default algorithm, like x-offset or more shadow levels.

This is the initial part of creating modules, where a remaining portion would provide for an easy way to swap a particular modular resource at the instance level (goal #2).

Motivation & context

Starting to look into some of the challenges with applying the design system, particularly color, and ways to extend the current systems approach into more customized styles that don't work with the base recipes. The current design system setup requires everything to be conveyable in the given set of properties, and larger adjustments require restyling components, which is complex and error-prone.

Issue type checklist

  • Chore: A change that does not impact distributed packages.
  • Bug fix: A change that fixes an issue, link to the issue above.
  • New feature: A change that adds functionality.

Is this a breaking change?

  • This change causes current functionality to break.

Process & policy checklist

  • I have added tests for my changes.
  • I have tested my changes.
  • I have updated the project documentation to reflect my changes.
  • I have read the CONTRIBUTING documentation and followed the standards for this project.

neutralOutlineHover,
neutralOutlineRest,
} from "../utilities/color";
import { neutralFocus, neutralForegroundRest, neutralOutline } from "../utilities/color";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the outline button example given is a good one - I'd like to see how the swapping gets implemented here. Would it be possible to create a stories.tsx file with one example similar to what you detailed in the write-up?

an alternate implementation, like outlines are neutral colored by default, and go to accent on hover or active states.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried this, and I found that this code is not working quite as expected. I'm having a problem that seems to be something related to webpack, where the function value I'm putting into the design system is getting swapped out with some 'reviver' function that doesn't seem to be resolving correctly.

I suspect we can do something like this with some sort of design system registry, where we could potentially even look up or pass in named resources from the standard or custom design system. I don't understand enough about webpack to resolve this though.

restDelta: number | DesignSystemResolver<number>,
hoverDelta: number | DesignSystemResolver<number>,
activeDelta: number | DesignSystemResolver<number>,
focusDelta: number | DesignSystemResolver<number>
Copy link
Copy Markdown
Member

@chrisdholt chrisdholt Jan 7, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought, and I know this is in the idea stage - but perhaps this algo can be defined by more of a config driven aspect? That may help those using it and reading it. It seems like the algorithm needs two things: 1) Palette, 2) interactive state config for the deltas

export interface InteractiveOffsetConfig {
    restDelta: number | DesignSystemResolver<number>;
    hoverDelta: number | DesignSystemResolver<number>;
    activeDelta: number | DesignSystemResolver<number>;
    focusDelta: number | DesignSystemResolver<number>;
}

export function offsetsAlgorithm(palette: Palette | DesignSystemResolver<Palette>, config: InteractiveOffsetConfig): SwatchFamilyResolver { };

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the benefit here cleaner syntax, reuse of config, or both? It does seem like it would protect from potential future changes easier as properties could be added and defaulted or deprecated without changing the signature. I would probably put everything in though, including the palette. I could see an implementation of this that wants both an accent and neutral, or even success or error, etc.

Perhaps in the design system service model there would be a way for style modules like this to retrieve properties from the registry of sorts. For instance, a custom design system might add a fourth color, and through composition the style could find it. This feels like a fairly standard composition problem that possibly has a framework or structure already in place. I wonder if we could pull something in.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I see it for cleaner syntax - from a fn() standpoint, once you hit more than 3 inputs it's going to become difficult to manage. Considering the deltas are all "related" and inform the palette I think the syntax makes more sense than having them all at the same level.

@nicholasrice
Copy link
Copy Markdown
Contributor

I'm going to take a stab at translating your stated goals into more specific requirements. Note that I'm saying "style recipe" here because I think the system should support arbitrary key/value pairs unspecific to color. Also note that the following code is pseudo-code and is not a recommendation for implementation:

  1. A component's StyleSheet must be able to assign, by name, an arbitrary style recipe to a single CSS property. (eg, border-color: neutralOutlineRest )
    a. Is there a recruitment to expand this, where groups of css property/value pairs are provided by a modular recipe? If so, this would likely have large impacts on implementation.
  2. Style recipe implementations need to be modular, where an app developer can customize(?) and override the functional implementation of a style recipe (what the above neutralOutlineRest reference does)
    a. Overrides must be available for any sub-tree of an app, meaning both root and nested overrides are possible, where implementations are projected down the tree.
    b. Overriding the implementation would cause all components downstream of that override to use the overriding recipe during CSS generation

Currently the "design system" is simply a set of serializeable data informing functions. The above will require some mechanism for providing and overriding function implementations. With "design system as a service" conversations, it is unclear to me in this scenario what exactly the "design system" is and what information will be provided from said service. The two primary approaches I see would be to either limit the data provided by a service or define a protocol that creates runtime functions on the client.

Related but tangential is how color recipes are created. Color recipes generally follow one or more patterns - can we refactor color algorithm creation tools to map more closely to those patterns where each specific recipe implementation can be a product of pattern implementations, and in general be more composable and extensible. I think this is a separate issue though, so perhaps we can limit this discussion to how we can map overrideable function implementations to CSS properties.

Are there any requirements you see that I missed?

@bheston bheston self-assigned this Jan 8, 2020
@bheston
Copy link
Copy Markdown
Collaborator Author

bheston commented Jan 8, 2020

I'm fine with "style recipe". Yes, these should definitely work for any potential styling value including color, measurement, shadow, etc.

1a. Not explicitly, at least initially, but that thought has definitely come up. For instance, we have a lot of repetition between states, and I've even added state functions for retrieving values from the swatch family in this PR, so that is at least a minor pain point. The other usage of this that comes to mind is RTL application of padding on "left" and "right" attributes.

  1. Yes, both customize and override. Customization would be the updated model of changing the current design system property values, like a recipe delta.
    2a + 2b. Yes. I think this is the same thing as we have now, which is what I think you're saying here as well.

Agreed on the "service" aspects, but I don't think that's any different than with what we have now. Let's say the service provided values for the currently serializable design system properties. If an app had needed the customizations we're talking about supporting, they would have had to write custom code and restyle components anyway. At the very least this improves the situation as we abstract some of the style providers and potentially reduce the need for custom code. I think either implementation would require code being able to be run within the app, or potentially the service can do the actual calculations instead of only returning values. That has different tradeoffs though. Any shared sort of service would only ever be able to communicate the standard aspects of the design, I believe.

Also agreed regarding the color recipes, on functionality as well as separating it out. I think it will be a good example when discussing this update, so I'm sure they will come up again anyway.

One aspect that isn't yet described here, and I only mentioned it in the last sentence of the description, is an idea around formalizing the interface of style recipes a particular component uses or has available. The basic idea is that a Button says it needs fill, border, foreground, focus treatment, corner treatment, and elevation. I was thinking about being able to cleanly mark that up something like:

<Button fill={accentFillRecipe}>Click me</Button>

I still like the idea of something like this, but I'm not sure props or something like this is the best approach, because it doesn't allow for a style or override to decide that it also needed another type of recipe later. This gets me back to the idea that the design system is a registry and potential mapping of style recipes / modules.

Right now the solution for the above would require wrapping in a DesignSystemProvider, which seems a bit heavy and is what I was looking for a way to avoid.

@stale
Copy link
Copy Markdown

stale Bot commented Jan 30, 2020

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale Bot added the warning:stale No recent activity within a reasonable amount of time label Jan 30, 2020
@stale stale Bot closed this Feb 6, 2020
@bheston bheston deleted the users/bheston/wip-recipe-provider-experiment branch October 18, 2022 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

warning:stale No recent activity within a reasonable amount of time

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants