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

A question about theming / styling / etc.. #861

Open
JovanVeljanoski opened this issue Nov 14, 2024 · 6 comments
Open

A question about theming / styling / etc.. #861

JovanVeljanoski opened this issue Nov 14, 2024 · 6 comments

Comments

@JovanVeljanoski
Copy link
Collaborator

Hi,

A question about theming in solara.
I have a assets/theme.js in the root of my project, a file with which I hope to control the theme(s) of the application. It seems to be picked up correctly by solara, and works well.

My question is, how can I modify the.. (I do not know what i the correct terminology here) the "true background" of the application, the "canvas" that goes black for example when one is using the pre-defined dark-mode (also the text letters go white in that case). Is that something that can be modified per app?

A small pycafe example that hopefully shows what I am talking about

(in my app the theme is applied, here it is applied to the pycafe component - that was not my intention but funny side-effect).

In general, is there a prefered way to set the true background to a user defined colour/picture/gradient etc..

In addition, regarding the solara.Style(..) component.
Say we defined a CSS file that has bunch of classes.

  1. Where in our component / page structure should we load the file (i.e. where should the solara.Style(myccs.css) so that the relevant components can have access to the defined classes in the .css file

  2. Sometimes..(through a lot of trial and error) i found out that I need to do something like a .. "hard overwrite" of the vue classes to make the styling work, i.e. my css file has something like

.logo-button.v-btn {
    background: none !important;
    box-shadow: none !important;
    /*other definitions */
}

instead of

.logo-button {
    background: none !important;
    box-shadow: none !important;
    /*other definitions */
}

which was what I expected. (not, this example is in a custom Layout component..)

Then I apply the css like this in python

solara.Style('path/to/mycss.css')
solara.Button(children=list_of_kids, on_click=my_callable, flat=True, classes=['logo-button'])

Is there some kind of best practice for this, or an expectation of how styling should be approached?

Many many thanks!

@JovanVeljanoski
Copy link
Collaborator Author

After some word, LLMs, and trial and error i think i have answered my own question, and I will write it here in case anyone else is interested in the same thing (but also so I can look it up in the future :P ).

Warning: Most of what I am writing are assumptions and observations.. might be wrong or wrong way of thinking / approach. Follow the maintainers advice / suggestion if available .

If one wants a "total" control of everything styling, one should start with defining a custom layout. There, in the layout component, you can add CSS styling to define how the application background, etc.. will behave.
Then, since solara is useing vue2/vuetifyjs under the hood, depending on how "deep" one wants to modify the components, sometimes it is necessary (or just easier) to modify the vue components directly, as they propagate throught the vue template, i.e.

.v-application {
    background: #e8f0fe !important;
}

The above is an aggresive approach.

One can also choose to a more fine-grained control, and style each individual component

import solara
import reacton.ipyvuetify as v


v.App(class_="my_class")
v.AppBar(class_="my_class_app_bar")

but in this case, the style does not.. cascade so you really need to style everything, depending on what you need. Probably a combination of both is needed.

I know nothing about CSS or best practices there, so doing stuff/discovering as I go along.

On the topic of solara.Style(..) i found that only works if the argument is of type Path(), and just string does not work for me (although the type hint suggests that it should?).
I still do not know where this should be added.. but I just add it as the first thing in a component, and that seems to work generally ok.

At the end of the day, it is nice to know that you can do whatever you want, and you can impose your style to solara/reacton components (if you know what you are doing). There are some steps to be done, but if one is familiar with CSS + vue, everything should be possible.

To mods: Feel free to close this issue

@iisakkirotko
Copy link
Collaborator

Hi @JovanVeljanoski! Great to see you found at least some answers to your questions already. A couple things I wanted to clarify:

Sometimes..(through a lot of trial and error) i found out that I need to do something like a .. "hard overwrite" of the vue classes to make the styling work, i.e. my css file has something like...

yeah, this is an annoyance of working together with (but not within) the Vuetify design system. Ultimately it all comes down to CSS selector specificity (the most specific style always wins, so .my-class.another-class styles override .my-class ones (see mdn web docs on the topic). Also note that any styles applied directly to an element always have the highest specificity. Vuetify's liberal use of !important also contributes to making overriding styles quite involved. Maybe it would be an idea to provide @component_blank or a similar alternative decorator that wraps everything in a CSS reset, removing any global Vuetify and ipywidgets styles?

but in this case, the style does not.. cascade so you really need to style everything

You could also use a combination, as you say. Something like v.App(class_=...), and override styles for some children of that element, i.e.

.my-class .v-btn.disabled {
    color: salmon;
}

or all children (your mileage may vary... specificity isn't working in your favour)

.my-class * {
    color: fuchsia;
}

On the topic of solara.Style(..) i found that only works if the argument is of type Path(), and just string does not work for me (although the type hint suggests that it should?).

solara.Style has, in a way, two functionalities:

  1. Inject full stylesheets (.css files) into your app. This can be done by giving the component a Path as an argument. solara.Style then loads the CSS file content and adds it to your app.
  2. Inject short CSS snippets into the app. For example in the case where you have a component that you give a class my_class and want to quickly apply some style to it, e.g. background-color: limegreen;. This can be done by giving solara.Style a string. When using solara.Stylethis way, the string itself is injected into the app
    I think the documentation on this should be improved, so I'll leave the issue open until that is done.

@JovanVeljanoski
Copy link
Collaborator Author

Hi @iisakkirotko

Thanks for the comprehensive answer - makes things way more clear!
Also yeah, I think it would be quite handy at times to have a decorator as @component_blank. Anything to ease the friction between solara and vue(tify) would be much appreciated!

Thank you!

@JovanVeljanoski
Copy link
Collaborator Author

Just a quick update, in case anyone else is stuck on doing styling etc..
(and I am very much a noob on any of these topics..)

I realized that if your .css file imports other files for example

@import './colors.css';
@import './fonts.css';

.my-classy-button{
    /* Colors only */
    background-color: var(--Primary-primary-100);
    color: var(--Text-text-main);
}

Then in solara one needs to apply all of the styles for the importing to work, i.e.

@solara.component
def Page():
    with solara.Column(align='center'):
        solara.Style(value=Path(__file__).parent.parent / 'assets/styles.css')
        solara.Style(value=Path(__file__).parent.parent / 'assets/colors.css')
        solara.Style(value=Path(__file__).parent.parent / 'assets/fonts.css')
        solara.Button('Click me', classes=['my-classy-button'])
        solara.Button('Click me but unstyled')

Maybe this is obvious to the front-end experts, but to me it was not!

@maartenbreddels
Copy link
Contributor

@JovanVeljanoski could you confirm that you see a 404 http request in the dev console for http://localhost:8765/colors.css ?

If so, we might have to not embed if we find a @import in

embed = len(content) < 1024 * 10 and b"url" not in content
(cc @iisakkirotko )

@JovanVeljanoski
Copy link
Collaborator Author

Hi,

@maartenbreddels i am not sure what you mean.. http://localhost:8765/colors.css just says "Page not found by Solara router", which is expected.. There is nothing in the console log about it.

On another note.. which might or might not be related.. is that when using @solara.component_vue, the styles "leak" from once component to another, which is kind of annoying, and probably a bug.

Say you do something like

<template>
  <v-col>
    <div class="my-styling">My fancy text</div>
  </v-col>
</template>

<style scoped>
  .my-styling {
    font-weight: bold;
  }
</style>

And then say we have another component

<template>
  <v-col>
    <div class="my-styling">Another fancy text</div>
  </v-col>
</template>

<style scoped>
  .my-styling {
    font-weight: bold;
    color: red
  }
</style>

Then in the solara app, even if these two components are used in separate pages (same app tho), the css styling seems to be "global", i.e. the last one seems to overwrite all previous ones, even though they should be "scoped", i.e. only applied to the component in question (as far as my limited understanding goes).

So in summary, you can't use css classes with the same name, even if they are "scoped" and part of different components that have nothing to do with each other but are being used in the same solara app.

I hope this makes sense!

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

No branches or pull requests

3 participants