Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Documentation: Theme Compose #1139

Open
tomdye opened this issue Mar 17, 2020 · 3 comments
Open

Documentation: Theme Compose #1139

tomdye opened this issue Mar 17, 2020 · 3 comments
Assignees
Labels
documentation medium Medium size issue

Comments

@tomdye
Copy link
Member

tomdye commented Mar 17, 2020

Document the use of theme compose

@pottedmeat
Copy link
Contributor

pottedmeat commented Mar 23, 2020

My notes reading through the code and tests.

The function is used when a widget is created within a parent widget's source.

  • baseCss: The embedded widget's CSS
  • css: The CSS of the widget which is returning the vdom
  • prefix: A string to use as a prefix

The return value should be passed to the embedded widget's theme property.

Benefit:

For the widget author, prefixed classes can be written that will completely replace that class in the embedded widget.

For the widget user, instead of writing a classes object that targets a descendent widget by type, rules are added to the parent widget type by writing the class name as it appears in the targeted widget using the assigned prefix. Should these prefixes be documented somewhere to make it clear what classes can be used?

How it works

TLDR

  • If a prefix is passed, we check to se if there is a prefixed version of the embedded widget's keys in either the theme or the primary widget's CSS. If there isn't, we use the original styles (and passed classes) for the embedded widget. If there is, we use the theme's prefixed versions, followed by the prefixed version in the primary widget's CSS. In both cases, we tack on the prefixed key from the classes for the primary widget.

Details

  • If a prefix is passed, the original keys from the theme and baseCss will be replaced:
    • If a prefixed key does not appear in the theme or css for the primary widget and one of the following does exist:
      • the original key from baseCss for the embedded widget
      • followed by the original key from classes for the embedded widget
      • followed by the prefixed key from the classes for the primary widget
    • If a prefixed key appears in the theme or css for the primary widget:
      • the prefixed key from the theme for the primary widget
      • if not in the theme, the prefixed key from css for the primary widget
      • followed by the prefixed key from classes for the primary widget
  • With no prefix, keys from the theme and baseCss will be replaced with:
    • If a key exists in the theme or css for the primary widget:
      • the key from css for the primary widget
      • followed by the key from classes
    • Otherwise, if the key exists in baseCss for the primary widget with one of the following:
      • the key from baseCss for the embedded widget
      • followed by the key from classes for the embedded widget
      • followed by the key from classes for the primary widget

@tomdye
Copy link
Member Author

tomdye commented Mar 24, 2020

We have different use cases to consider here

Widget Authors

  • As a widget author, you initially write the default theme and use child widgets to create your widget.
  • if you need to provide structural styles to a child widget or wish to provide a means to theme the child via a prefix class you use theme.compose rather than just passing theme.
  • the styles in the default theme will be additive to the child widgets styling

Theme Authors

  • theme modules using the prefixed theme.compose classes will override the base widgets theme.
  • authors wishing to use a prefixed class name whilst maintaining base styles can use composes.
/* loginForm.m.css with a submit button using `submit` prefix */
.submitRoot {
   composes: root from './button.m.css';
}

.root .submitRoot {
   /* overrides go here, nest under root selector for local specifity */
   margin: 10px;
}

Widget users

  • a widget user wanting to override the above submit button root can use classes to target the submit button without needing to know what type of widget the login form is using.
    ie.
// with theme.compose use
classes: {
   '@dojo/login-form': { submitRoot: [ myAdditionalClass ]}
}

// without theme.compose use
classes: {
   '@dojo/unknown/button/type': { root: [myAdditionalClass ] }
}

@pottedmeat
Copy link
Contributor

Widget Authoring

Many widgets embed imported widgets in their returned vdom. Often, changes must be made to the styling of these embedded widgets both to support its placement within the parent widget as well as supporting any stylistic changes users and theme authors may want to make.

Using theme.compose allows all rules for embedded widgets to be written in your widget's CSS file or in a theme with a prefix rather than having to make alterations in code using classes. It also allows, in cases where you are embedding two widgets of the same type, for a specific instance to be easily targeted.

When to Use classes

If a class should be added situationally, they should appear situationally in the embedded widget's classes. Feel free to use both theme.compose and classes.

<Button
	classes={{
			'@dojo/widgets/button': {
				root: [isActive ? themeCss.activeButton : null]
			}
	}}
	theme={theme.compose(buttonCss, css, 'button')}
>
</Button>

Overriding or Composing

When a prefixed class appears in the imported CSS or theme, it will override that class in the embedded widget—that node no longer will have the resolved class applied to it which will also remove any nested styles depending on it.

Often, this is intentional, but if your goal as a user or theme author is to augment the existing styles, you will want to use the CSS Modules composes functionality.

By using composes, both the new resolved class and the original resolved class will be applied to that node in the embedded widget.

From the CSS Modules documentation:

Note that when composing multiple classes from different files the order of appliance is undefined.

What this means is that your rules and the original rules will be of equal specificity and dependent on the order the style sheets are imported. For this reason, composed rules should have the following format:

.buttonDisabled {
	composes: disabled from './button.m.css';
}

.root .buttonDisabled {
	opacity: 0.5;
}

Example

widget.tsx

<div classes={root}>
	<Button theme={theme.compose(buttonCss, css, 'cancelButton')>Cancel</Button>
	<Button theme={theme.compose(buttonCss, css, 'saveButton')>Save</Button>
</div>

widget.m.css

/* modifies the .disabled class in the Button with prefix 'cancelButton' */
.cancelButtonDisabled {
	opacity: 0.5;
}

/* modifies the .disabled class in the Button with prefix 'saveButton' */
.saveButtonDisabled {
	text-decoration: bold;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation medium Medium size issue
Projects
None yet
Development

No branches or pull requests

7 participants