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

Passing callbacks as styles to components #91

Open
efoken opened this issue Sep 5, 2022 · 5 comments
Open

Passing callbacks as styles to components #91

efoken opened this issue Sep 5, 2022 · 5 comments
Labels
enhancement New feature or request

Comments

@efoken
Copy link
Contributor

efoken commented Sep 5, 2022

Is your feature request related to a problem? Please describe.
Now that the Context API is available in Atomico, it would be very nice to be able to pass callbacks to component.styles.

Describe the solution you'd like
I would like to support the following:

const Button: Component = () => {
  // ...
}

Button.styles = [
  ({ theme }) => css`
    :host {
      color: ${theme.colors.brand};
    }
  `,
];

I know there are CSS variables, but as we share our theme with React Native components we cannot use CSS variables. And besides that our theme contains a lot more than just colors, there are even objects containing text styles or shadows.

The question is, how to tell the Button component above to use ThemeContext?!

Maybe:

Button.context = ThemeContext;

or even:

Button.styles = [
  ThemeContext,
  ({ theme }) => css`
    ...
  `,
];
@efoken efoken added the enhancement New feature or request label Sep 5, 2022
@efoken
Copy link
Contributor Author

efoken commented Sep 7, 2022

I thought about this. There several different improvements that have to be made:

  1. As Atomico has it's own styles API (and React does not) that is also slightly similar to styled-components, maybe it would be okay to add a ThemeProvider inside Atomico. The same like styled-components has it:
<ThemeProvider theme={...}>
  // ...
</ThemeProvider>

And then it's possible to use the theme with a hook and in the styles:

const theme = useTheme()

// and

component.styles = (theme) => css`
  :host {
    color: ${theme.colors.brand};
  }
`

And for TypeScript support it should be possible to use declaration merging to extend the theme type:

declare module 'atomico' {
  export interface DefaultTheme {
    colors: {
      brand: string;
    };
  }
}
  1. The css function from Atomico should be extended to allow passing JS objects to the template string or directly, like it can be done with styled-components:
const textStyle = {
  marginBottom: 16,
  color: 'red',
};

component.styles = css`
  :host {
    ${textStyle}
  }
`

Use directly with an object instead of a template string:

const textStyle = {
  marginBottom: 16,
  color: 'red',
};

component.styles = css({
  ':host': textStyle,
});

We already did this in our code by extending the css function from Atomico, I can contribute this. We currently use Stylis to also support Sass-like syntax and nested styles, but I want to refactor this regarding the bundle-size.

Here is how Goober is doing it with only a few lines of code:
https://github.com/cristianbote/goober/blob/master/src/css.js
https://github.com/cristianbote/goober/blob/master/src/core/parse.js

This leads us to the third improvement:

  1. Add support for nested styles in css:
component.styles = css`
  a {
    color: red;
    &:hover {
      color: blue;
    }
  }
`

Goober is also supporting this with the link above, with only a few lines of code.

That's it for now 😃 What do you think about this?

@efoken
Copy link
Contributor Author

efoken commented Sep 7, 2022

In the meantime I created this: https://github.com/efoken/atomico-styled

@UpperCod
Copy link
Member

UpperCod commented Sep 7, 2022

I think the approach of using context to customize appearance is interesting, I personally recommend using customProperties vs the context callack (color:${(theme)=>theme.color.brand}), since then the css does not need to be reactive, example:

import { styled, Theme } from "atomico-styled";

const Div = style("div")`
  :host {
    font-size: 200px;
    color: var(--color-brand);
  }
`;

<Theme value={{ color: { brand: "black" } }}>
    <Div>Black</Div>
</Theme>;

This is just theoretical, but it could work

const Styled: Component = () => {
    const theme = useContext(Theme);

    const props = useAttributes();

    Object.assign(props, { theme });

    useCssLightDom( cssTransformNested( styles ), `:host{ ${ objectToCustomProperties(props) } }` );

    return h("host", null);
};

Atomico core for now only focuses on native CSS support, but we can contribute to its implementation to achieve a more decorated css pre-processing

@efoken
Copy link
Contributor Author

efoken commented Sep 8, 2022

Sure it is possible to use CSS custom properties, but as I mentioned we cannot use them because we share styles with React Native. Besides that there are also some other disadvantages using CSS custom properties:

  1. No type-safety when using TypeScript, no IDE autocompletion, etc.
  2. In your example above, you would have to convert a theme object to CSS variables.
  3. You can't use objects from the theme, for example if we have theme.text.body containing fontSize, fontWeight, lineHeight, we would have to use all single values instead of just using object spreading:
(theme) => css({
  ':host': {
    ...theme.text.body,
    color: '#000',
  }
})

The theme should only change for example when switching from LightMode to DarkMode. The props used for styling also do only change in rare cases and not all the time – so reactivations should be not that often. Even though my feature request only is for using callback as styles where we have access to the theme context, and not to the props of the component – that's another thing 😄

Anyway the first thing that would be amazing, when the css function can support nested styles and also passing styles as objects. That should be easy to do with a small amount of code, if you like I can contribute this.

@UpperCod
Copy link
Member

UpperCod commented Sep 8, 2022

There is an undocumented functionality in Atomico, for confusions with the SSR, the trick is the following:

https://studio.webcomponents.dev/edit/AzdHHrVHCzGQMMbWtkGI/src/theme.jsx?p=stories

from the example above highlighted

index.js

myCounter.styles = css`
  * {
    font-size: ${"200%"};
  }

  span {
    width: 4rem;
    display: inline-block;
    text-align: center;
  }

  button {
    width: 4rem;
    height: 4rem;
    border: none;
    border-radius: 10px;
    background-color: ${(theme) => theme.color.brand};
    color: white;
  }
`;

index.stories.js

export const story1 = () => html`
  <my-theme value=${{ color: { brand: "black" } }}>
    <my-counter></my-counter>
    <my-theme> </my-theme
  ></my-theme>
`;

it's just a prototype, it would be nice to add support for sugar syntax and TS types. \

How is this possible? Atomico allows the component.styles object to be given an Element, internally Atomico will duplicate it at the time of mounted, this helps us internally to support CSSStyleSheet in firefox.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants