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

Replace Bulma with TailwindCSS #20

Open
msaraiva opened this issue Jul 22, 2021 · 11 comments
Open

Replace Bulma with TailwindCSS #20

msaraiva opened this issue Jul 22, 2021 · 11 comments

Comments

@msaraiva
Copy link
Collaborator

I want to redesign the UI using TailwindCSS and turn the Catalogue into a official supported tool (not only a prototype). In the process I want to extract the components we need to create here and move them to surface_tailwind so they can be reused in other projects.

@msaraiva
Copy link
Collaborator Author

Since phoenix v1.6 should be released in the next few days, I believe we can start discussing our next steps. After reading surface-ui/surface_tailwind#1 a few times and visiting the websites/docs of all suggested component suites, it seems to me that building our components on top of DaisyUI would be the safest choice.

So here are all (for now) the things I want to explore in this project:

  1. A minimal suite of components based on DaisyUI to be used in the Catalogue and then extracted to its own project later.
  2. Reusable application layouts - It would be great if we had a few generic app layouts that would allow users to quickly get started and although we'll not need more than one for the Catalogue, we could at least make the one we create available. Something similar to the "Application shells" in https://tailwindui.com/#product-application-ui.
  3. Theming - just light/dark mode would be enough for the catalogue but since DaisyUI already provide a bunch of other themes, I don't see any reason to not allow developers to use them.
  4. Use alpine.js to handle unavoidable client JS stuff - this is a tough call as it would make some of the components to have an external dependency, however, it seem like a good tradeoff given the benefits.
  5. Explore the idea of having component templates that can hold all the component logic but don't depend on a specific CSS lib. Something similar to https://headlessui.dev/

I have already discussed some of those items with @davydog187 and I'm glad he's just as excited as I am about this project and will be helping me in this journey.

@haubie, @rmayhue, @Menkir and @gdub01 based on the previous discussion, it seems to me you've already played with the Tailwind + Surface combo and experienced some of the challenges we're about to face here. I liked the discussion and ideas that were brought up regarding the ideal API and I think we'll certainly explore them here.

So moving on, I'll start with a couple of questions:

  1. How do you feel about using DaisyUI as a starting point? Any downsides or concerns you have?
  2. Should we even try to create a generic surface_tailwind with more components or just embrace DaisyUI, stick with it and create a surface_daisyui instead?

@haubie
Copy link

haubie commented Aug 13, 2021

Hi @msaraiva. Very excited to see parts of your pioneering work and persistence with Surface being imported into the next Phoenix release!

I've been playing around with the ideas in the previous discussion including creating some components using them.

But I agree that if the goal is to get a rich and slick set of components using Tailwind CSS in a short period of time, wrapping DaisyUI maybe the path. (Plus adding some additional ones like you mentioned for layouts.) And using the name surface_daisyui.

It looks like they had similar to goals to what I was exploring in how to have some sensible defaults, but still allow component users to customise through applying a theme definition or overriding classes on a specific component. The way they've done it looks pretty nice :-)

I also really really like the idea of number 5 in terms of future proofing any components built and not have to rebuilt components from scratch if that new amazing CSS framework comes along!

In terms of an API, for the styling part of that, if the goal is targeting people who use Tailwind on a regular basis, I'm thinking that Tailwind naming conventions could be used for prop names.

e.g. if there was a prop for background, use bg as the prop name, which mirror the background Tailwind classes like:

  • bg-none
  • bg-gradient-to-t
  • bg-center
  • bg-clip-padding
  • etc.
    e.g.

I guess one issue with this is that is it could create many props on a component, rather than a single class prop. But the good thing with this way is that component user only has to override the props they want. And if DaisyUI is being used, those additional props are probably only needed for reaching into some of those deeper classes - e.g. with the DaisyUI avatar component, the top-level div would have the class prop is appended to as a general catch-all, and w, h and rounded for setting things deeper in:

prop class, :css_class
prop w, :css_class, default: "w-24"
prop h, :css_class, default: "h-24"
prop rounded, :css_class, default: "rounded-full"
prop src, :string

In the render function:

<div class={"avatar", @class}>
  <div class={@rounded, @w, @h}>
    <img src={@src} class={@w, @h} />
  </div>
</div> 

So the component user might just do:

<Avatar src="my_img.png" />

And if they only wanted to change the shape, it would be:

<Avatar src="my_img.png" rounded="rounded-md" />

Or add some padding:

<Avatar src="my_img.png" rounded="rounded-md" class="p-4" />

Another thought on API, for components that link off, like a menu, instead of a singular to prop, I'm wondering if the user can choose from href, patch or redirect, the last two being the Live View patch or redirect. E.g.:

<MenuItem title="Href link" href="https://google.com.au" opts={[target: "_blank"]} />
<MenuItem title="Patch to /"  patch="/" />
<MenuItem title="Redirect to new-liveview" redirect="/new-liveview" />

This is just opinion based my my experimentation and I'm sure others will have views as well!

@Menkir
Copy link

Menkir commented Aug 13, 2021

I am glad that there is more movement in this project.

Grouping Tailwind classes into DaisyUi components is a very good start for an out of the box solution. There is no license constraint now and you can provide a library with strong defaults.

The question if the project should be called surface_tailwind or surface_daisyui implies for me on the question if also a standard API should be provided, so that one can write own components against this API. If not then surface_daisyui, otherwise surface_tailwind.

However, a standard API is not exactly easy to develop, since components can become indefinitely complex. The way to configure a component as @haubie suggested works only for flat components. So rather for absolute basic components. For bigger things like tables it needs more configuration.
The problem here is that the application of different values of the same classes on different elements is possible e.g.:

<div class={@width}>
    <div class={@bg-color}>
        <div class={... different bg-color ...} 
    </div>
</div>

The tight coupling of design and form (html-divs etc.) makes it hard to define the properties.

What works better is the separation of design and form like DaisyUi does. You introduce your own classes that are configurable to a certain degree.
Example:

<MyComp type="danger">
defmodule MyComp do
    use Surface.Component
    prop type, :string, default: "default", values: ~w(default danger info success warning)     

    def render(assigns) do
        <div class="foo" data-type={@type}>
            <div class="inner-foo" data-type={@type}>
                <div class="baz" data-type={@type}>
                    ... 
                </div>
            </div>
        </div>

    end
end
.foo data[type="danger"]{
    @apply text-red-200 border-2 border-red-500;
} 

.inner-foo data[type="danger"]{
    @apply bg-red-50
} 

.baz {
    @apply text-sm;
}

.baz data[type="danger"]{
    @apply text-red-900
} 

Another advantage of this method is that the HTML is not bloated. Especially in the Inspector the html trees become much leaner.

@msaraiva on point 4: What exactly do you mean by unavoidable Js stuff? I had used Alpine for exactly one reason: Animations. LiveView/Surface exists for the very reason that you need little to no JS anymore. Unless you want to link your application to a JS application. But there are hooks for that.

@msaraiva
Copy link
Collaborator Author

@haubie and @Menkir, thanks for your inputs.

if there was a prop for background, use bg as the prop name

I have mixed feelings about this. I believe one of the main reasons that drove people to create all those bg, m, p, h, w, etc. is simply because when you're not using components, as soon as you need dozens of those things (and you usually do), it just take too much space and the whole thing becomes unreadable. However, when using components, we have a change to abstract all that mess and instead of using all those utility classes directly, we can expose higher level properties with more meaningful names. We should still provide a nice way to allow users to override/add some of those utilities when necessary but it seems to me that if we need to do that every time we use a component, we probably haven't done a good job when abstracting those components in the first place.

The tight coupling of design and form (html-divs etc.) makes it hard to define the properties.

I totally agree. It's a hard problem to solve. I'm not sure we'll be able to find a solution for that but we can certainly try :)

What exactly do you mean by unavoidable Js stuff?

I guess "unavoidable" was a bad choice of word :)

What I mean it that besides the obvious cases where we need JS (animations, drag and drop, etc.), there are cases when we don't need server events to perform some basic user interactions.

Take this Tabs component, for example. Currently, whenever you click on a tab, an event is sent to the server. That would make sense if we needed to load the tab's content dynamically on each click but in many cases, we don't. We could just bring everything and use JS to show/hide the tabs. This may not be a problem for most components but depending on the amount of data that might be exchanged, it just adds unnecessary server round trips. We can still use vanilla JS to handle that too but with alpine it's just much easier and clean.

Basically, whenever a component requires some state on the client, Alpine really shines. I still haven't decided if we should use alpine or not but I think it might be worth to explore the possibility.

@haubie
Copy link

haubie commented Aug 14, 2021

Interesting discussion. Comfortable with the thoughts above including the use of AlpineJS.

We should still provide a nice way to allow users to override/add some of those utilities when necessary but it seems to me that if we need to do that every time we use a component, we probably haven't done a good job when abstracting those components in the first place.

I guess we could take this a bit more iteratively and aim for a less 'customisable' release initially, with a minimal API, until we settle on a nice way to handle it?

Although there is probably a 'common sense' test that could be applied to each component as to what parts we'd like to expose in its own prop? (e.g. just to use the basic avatar example above I'm thinking size, shape, image and any action would be the minimum and all optional?)

If Surface Catalogue is the fist project using them, then I think you may end up identifying the pain points and API needs @msaraiva as you're using it!

Let me know if I can contribute in some way. I'm happy to start wrapping some DaisyUI with Surface as an initial parse through. That may help with design decisions around the API as well.

@gdub01
Copy link

gdub01 commented Aug 16, 2021

With regards to DaisyUI.. the upside is it's faster to develop if we use it directly.
The downside is we won't be able to progress faster or differently from daisy ui if we call it daisy_ui.

Perhaps we could use a lot of their component-based tailwind classes and html structure to get a jump on things instead of using daisyui directly? They'll be pinned to a tailwind version so they'll be unlikely to change much I don't think.

I also like the idea of alpine for things like tabs and dropdowns.

@haubie
Copy link

haubie commented Aug 16, 2021

I hadn't really taken a deeper look at DaisyUI until the last few days. It looks like it's a Tailwind plugin with JavaScript code running behind the scenes during build. I guess there is a concern by using DaisyUI, that we might be coupling ourselves to their specific build tooling, how they have organised their project, etc?

So your idea @gdub01 of using their classes and html but not the rest of it (with full acknowledgement) could be a good way to kick off surface_tailwind rather than surface_daisyui?

@gdub01
Copy link

gdub01 commented Aug 16, 2021 via email

@gdub01
Copy link

gdub01 commented Aug 16, 2021

Ah you know what I may have over simplified. https://github.com/saadeghi/daisyui/blob/master/src/components/styled/button.css is their styled button component.
https://github.com/saadeghi/daisyui/blob/master/src/components/unstyled/button.css is unstyled.
https://github.com/saadeghi/daisyui/blob/master/src/utilities/unstyled/button.css is a utility modifier for buttons.

So there's a little more complexity than just copying a list of tailwind classes I'm learning.

@msaraiva
Copy link
Collaborator Author

@gdub01 as far as I could see, they work independent from each other and they are chosen according to the styled and utils config described here https://daisyui.com/docs/config.

So we could just pick the one we want, which I believe would be the styled one, with or without the additional utilities. Right?

@gdub01
Copy link

gdub01 commented Aug 17, 2021

Oh yes I think you're right @msaraiva

I think the utilities, in the case of the button, have aspects you could argue are better relegated to a theme. ie. if you want rounded buttons, you probably always want rounded buttons.. not sometimes round sometimes square. So the utility classes for buttons may not be as useful.

However in the case of the avatars https://github.com/saadeghi/daisyui/blob/master/src/utilities/styled/avatar.css having an online and offline version would be pretty useful. So perhaps we can pick and choose the utility modifiers we'd want to support.

The other interesting thing is their use of css variables like so:

border-width: var(--border-btn, 1px);

I think we'd have to decide how we'd want to set that up as it plays into theming. Perhaps we could have a module that just sets all the variables by passing it a theme object and it sets the variables inside

<style>
:root {
  --my-color: #fff;
}
[data-theme='dark'] {
  --my-color: #000;
}
[data-theme='pink'] {
  --my-color: #ffabc8;
}
</style> 

tags that are rendered on the page inline?

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

4 participants