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

Proposal: a class map for easy class manipulation. #66

Closed
justin-schroeder opened this issue Apr 16, 2020 · 35 comments
Closed

Proposal: a class map for easy class manipulation. #66

justin-schroeder opened this issue Apr 16, 2020 · 35 comments
Labels
future Accepted and tracking for an upcoming release suggestions welcome Looking for suggestions on a proposal

Comments

@justin-schroeder
Copy link
Member

justin-schroeder commented Apr 16, 2020

Describe the new feature you'd like
Proposed solution for allowing classes to be overriden on any sub-element of vue-formulate:

The default would be something like:

Vue.use(VueFormulate, {
  classmap: context => ({
    "outer": `formulate-input`,
    "outer-wrapper": `formulate-input-wrapper`,
    "element-wrapper": `formulate-input-element formulate-input-element--${context.type}`,
    "element": '',
    "help": "formulate-input-help",
    "errors": "formulate-input-errors",
    "error": "formulate-input-error"
  })
}

The idea is that classMap function would be defined globally for all inputs, and then each sub-type could override it, and then as granular as individual input fields could also override each:

<FormulateInput
  type="text"
  classmap-element="form-input block w-full sm:text-sm sm:leading-5"
/>

The idea is that the above solution would allow a developer to setup their own fields using a class-based ui framework and handle 90% of a project's form styles globally for all FormulateInput elements, and then on a case by case basis easily override the styles on a per-element basis, and lastly scoped slots as a nuclear option.

I'll reiterate the goal of Vue Formulate is to make high quality form building fast and easy, so I we want to provide that 90% solution as painlessly as possible. 90% of inputs shouldn't require any additional configuration, scoped slot usage, etc.

What percentage of vue-formulate users would benefit?
Probably about 20%


I'm looking for feedback from users of frameworks like Tailwind, Tachyons, or Bootstrap.

@justin-schroeder justin-schroeder added feature request New feature or request suggestions welcome Looking for suggestions on a proposal labels Apr 16, 2020
@justin-schroeder justin-schroeder changed the title Proposal: a class map for class manipulation. Proposal: a class map for easy class manipulation. Apr 16, 2020
@gilesbutler
Copy link

I think the classMap function sounds like an interesting idea. Just to clarify your example though would it be something like this...

<FormulateInput
  type="text"
  classmap-element-wrapper="mt-1 relative rounded-md shadow-sm"
  classmap-element="form-input block w-full sm:text-sm sm:leading-5"
/>

classmap- just name spaces the input you want to apply classes to?

I think you're right, that should help for 90% of cases and then people can use scoped slots if they want to go deeper.

@justin-schroeder
Copy link
Member Author

Correct, the classmap- would just be the prop namespace for consistency. Think it's too verbose? We could drop the map class-element-wrapper="form-input" if that seems better.

@gilesbutler
Copy link

Dropping the map may help beginners a bit more. It's a bit more succinct I guess.

@Garito
Copy link

Garito commented May 20, 2020

There is no need for this way to work
I work with OpenApi schemas so let me explain how I'm doing it that way:
In the schema you could add field's classes by using x-yourlabel-class and the rest of the layout you could define it by putting the layout as a slot and move the fields to their positions with vue-portal (that vue v3.0 will have as a build in component)
So from the vue-formulate perspective, I will include a prop (use-portals) to render the forminput with a portal and then the default slot for the form will be the layout you what to use

@justin-schroeder justin-schroeder added future Accepted and tracking for an upcoming release and removed feature request New feature or request labels May 22, 2020
@bbugh

This comment has been minimized.

@Garito
Copy link

Garito commented May 30, 2020

Giving that portals will be a built in feature for vue, the approach could be:
This is the bootstrap input widget:

<div class="form-group">
  <label for="exampleInputEmail1">Email address</label>
  <input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp">
  <small id="emailHelp" class="form-text text-muted">We'll never share your email with anyone else.</small>
</div>

Using portals, the label, input and messages/errors should be wrapped by a portal and the widget should be written like:

<div class="form-group">
  <label for="exampleInputEmail1"><PortalTarget name="label"></PortalTarget></label>
  <PortalTarget name="input"></PortalTarget>
  <small id="emailHelp" class="form-text text-muted"><PortalTarget name="msg"></PortalTarget></small>
</div>

The only part that will need some attributes will be the input

Would be more simple and better for this library if we could focus on the specifics of managing forms and not how they should look (seems that is the whole point of vue-portal: separation between the layout and the functionality)

@bbugh

This comment has been minimized.

@Garito
Copy link

Garito commented May 30, 2020

Perhaps your are the confused one but, ok, I will mind my business

@Garito
Copy link

Garito commented May 30, 2020

According with Linus Borg's portal-vue:
"A feature-rich Portal Plugin for Vuejs, for rendering DOM outside of a component, anywhere our app or the entire document."

Bye

@bbugh

This comment has been minimized.

@dosstx
Copy link

dosstx commented Jun 2, 2020

Thanks for making this plugin, Very nice. Unfortunately, I can't easily add Bootstrap classes to it.

@n10000k
Copy link

n10000k commented Jun 10, 2020

Simple solution, use tailwind ;)

@n10000k
Copy link

n10000k commented Jun 10, 2020

I'm waiting on this been talking about vue-formulate for a long time, just want to throw my own classes in it and dev quicker and not have any bs.

@andrew-boyd
Copy link
Member

@narwy Vue Formulate does not want to take strong stances on layout (https://vueformulate.com/guide/#what-it-isn-t). The base theme is currently provided mainly for the sake of having good-looking examples.

Whether your CSS approach of choice is to roll your own styles, use a full UI framework such as Bootstrap, or leverage a utility-based class system such as Tailwind we’re seeking to make the library work for you.

Rather than “just use Tailwind” we’d prefer to solve the hard problem of creating an API that allows Tailwind users to get just as much value out of Vue Formulate as any other CSS aficionado.

We’re not there yet though, so as a Tailwind user your input would be invaluable to us (we are not users of Tailwind ourselves). We need feedback on any proposed class-map system to ensure it’ll be robust enough to handle whatever CSS system is currently in vogue.

@n10000k
Copy link

n10000k commented Jun 10, 2020

@andrew-boyd I'd just create the correct props/api "config" as such to handle for passing classes. This day and age people make classes regardless, seen as tailwindcss is a utility driven css framework which is classes only this is a good thing. Other frameworks like BS4, Bulma etc as long as you can pass a class to the style a component you want i think that would cover it all.

Something to take into account in tailwind we have pseudo class variants an example would be a group so covering top level is a must also - ref: https://tailwindcss.com/docs/pseudo-class-variants/#app

I think covering each element is a must. I'll be honest when It comes to forms I'd rather have a package like formulate that just is generic where I have any input in my app.. I'd have some form of config that I can change in one place instead of on each element, and maybe if i pass a custom style on that component, it will override the config? I don't know spit-balling ideas here where I could see it benefiting.

This is the only thing that's been putting me off using formulate more is the styling, i want to have ease when it comes to throwing something together vs manually doing everything, especially when an app maybe form input heavy.

Don't know if this helps or if I'm rambling on, but please add a way to style items in a easier way/format vs each element manually styling.

@andrew-boyd
Copy link
Member

andrew-boyd commented Jun 10, 2020

Thanks for the input. The proposed system (and the one that’s also in the works) allows for setting custom classes on each level of hierarchy that Vue Formulate outputs. You’d be able to set both global “these classes should be on every Vue Formulate element” defaults as well as per-input overrides when you need exceptions.

If you have a form-heavy project then even with class maps it may be worth it to create your own abstraction components that output specific input lock-ups that are common in your project’s UI — but that is something we would leave up to the individual project author to complete.

Keep an eye on this issue and we’d love to get your reaction to class maps when they ship. Current target is for release in version 2.4.0.

@hmaesta
Copy link
Contributor

hmaesta commented Jun 11, 2020

Super excited about this 🙌

I up vote for class- instead of classmap- on FormulateInput.

First time using Tailwind (coming from Bulma) and Vue Formulate was my first "can't do" so far, as detailed here.

Simple solution, use tailwind

@narwy Are you able to use Tailwind on Vue Formulate with inline classes? What is your workaround? (withou @apply etc)

@n10000k
Copy link

n10000k commented Jun 11, 2020

@hmaesta No work around for me, have to use @apply and define utility classes under that. I actually can't wait for this though, will reduce my apply definitions which is a good thing <3

@justin-schroeder
Copy link
Member Author

Hey @narwy @hmaesta and @gilesbutler — this feature is almost finished now. I've got an early preview of the docs here:

https://vueformulatecom-git-feature-classmaps.braid.now.sh/guide/theming/#customizing-classes

I think this should handle almost every use case, and like @narwy mentioned you can just configure your tailwind classes once at the global level and be done — then when you need a little more customization on a particular input extend those globals. Would love feedback though!

@justin-schroeder
Copy link
Member Author

also @bbugh somehow I missed your helpful conversation on here.

I've considered including renderless too. It would be pretty easy to implement, but I'm slightly concerned about it undermining the main the goals of this project: to make form authoring really easy, fast, and high quality (best practices, accessibility out of the box, etc) so markup is actually a pretty important part of what we're trying to do here — and getting people to conform to a system that allows those features to be improved over time is really valuable for the community at large. For example, if we get some good accessibility feedback an update could help hundreds of sites improve accessibility at the same time.

That said, I understand that occasionally people need to deviate from that or their use case really doesn't need those features. I was hoping slots, and slot components would suffice.

@hmaesta
Copy link
Contributor

hmaesta commented Jun 11, 2020

That's really nice, @justin-schroeder 🤩

I read the doc and everything looks great. But since you are already working on this... shouldn't Vue Formulate support pseudo-classes on input elements?

For example:

  • input-hover-class
  • input-active-class
  • input-focus-class
  • input-checked-class

Thinking about Tailwind specific, if @tailwind/ui plugin is present it shouldn't be any problem, since .form-input class handles these pseudo-classes –– but with pure Tailwind user won't be able to add styling to :hover, :active :focus and :checked.

Since this is a project that handles forms, at least :focus and :checked are mandatory for accessibility proposes (for example, how would I know if a checkbox is selected without :checked?)

I have some fears about performance, but I think it should be supported.

Tailwind UI is a paid plugin for Tailwind CSS. Right now everyone is using (including me) because it's early stage (and free), but it's not so clear if this plugin will continue free after the public release. What I understand so far is that they will charge for the "example codes", but since the plugin doesn't have a public repository I feel that it maybe won't be free.

Anyway, Tailwind is just an argument. I really think pseudo-class support would be great for advanced CSS styling.


Just to illustrate, this is my Tailwind code so far – without using Tailwind UI:

ScreenFlow

.formulate-input {
    .formulate-input-label {
        @apply block text-sm leading-5 text-gray-700;
    }

    .formulate-input-element {
        @apply mt-1 relative rounded-md shadow-sm;

        &.formulate-input-element--text {
            input {
                @apply block w-full px-3 py-2 border-gray-100 border-solid border rounded-md transition duration-100 ease-in-out;

                &:hover {
                    @apply border-gray-200;
                }

                &:focus {
                    @apply border-blue-400 outline-none shadow-outline;
                }
            }
        }
    }
}

Ps:
On the sentence "Simply target the class key you’d like..." the "class key" link is broken.
You pointed to #class-map and it should be #class-keys 🙂

@hmaesta
Copy link
Contributor

hmaesta commented Jun 11, 2020

Also thinking about status classes... I am not happy about adding more complexity for this, but I believe it's best to bring all cases to discussion. 🤐

  • input-error-class - ie.border-red-500
  • input-valid-class - ie. border-green-500
  • label-error-class - ie. text-red-500
  • label-valid-class - ie. text-green-500

@justin-schroeder
Copy link
Member Author

@hmaesta So if you want to conditionally add classes you could always use functions and then change things based on the context, but are you sure that you need separate class keys for each of these? I'm not much of a tailwind user, only dabbled, but since psuedo's are just classes in tailwind would it work something like this?

Standard HTML:

<input class="bg-gray-200 hover:bg-white hover:border-gray-300 focus:outline-none focus:bg-white focus:shadow-outline focus:border-gray-300">

Vue Formulate:

<FormulateInput
  input-class="bg-gray-200 hover:bg-white hover:border-gray-300 focus:outline-none focus:bg-white focus:shadow-outline focus:border-gray-300"
/>

Am I way off on that @hmaesta

@hmaesta
Copy link
Contributor

hmaesta commented Jun 11, 2020

Oh god 🙉 Today I was so immersed with the @apply function that I totally forgot about Tailwind inline support for pseudo-classes.

For that scenario you are totally right. No need for specific "pseudo-keys" on Vue Formulate.
But I don't know if competitors (Bootstrap?) have support for this.

@bbugh
Copy link

bbugh commented Jun 11, 2020

@hmaesta you're correct, bootstrap doesn't have hover psuedo-classes like Tailwind. Generally, the class map idea seems like it will work great with Tailwind and not so well for non-utility frameworks. To get a global styling for Bootstrap, we'd likely have to shadow/extend the built-in formulate classes with custom CSS and/or create a custom wrapper component for FormulateInput.

@justin-schroeder
Copy link
Member Author

justin-schroeder commented Jun 11, 2020

Yeah, with non-utilities we'll never have enough options. There are so many combinations of things out there. The current approach gives people an escape hatch to implement their own functions to apply as needed, for example to add input-error-class on the <input> only when there are errors:

Vue.use(VueFormulate, {
  classes: {
    input: (context, baseClasses) => {
      if (context.visibleValidationErrors.length) {
        baseClasses.push('input-error-class')
      }
      return baseClasses
    }
  }
})

@justin-schroeder
Copy link
Member Author

@hmaesta and @bbugh do you guys think we need to add more class keys even with functional class assignment? Trying to figure out what the right balance is (appreciate all the feedback 🙏)

@hmaesta
Copy link
Contributor

hmaesta commented Jun 11, 2020

In my mind I can imagine 4 levels for this:

  • Level 1: classes for every element (as you already did)
  • Level 2: 1 + error/valid classes for input
  • Level 3: 1 + 2 + error/valid classes for label
  • Level 4: 1 + 2 + 3 + hover/active/focus classes for input

To be honest I think we should go at least with Level 2 – simply because we should be responsible for UX. Here's an example:

Artboard

We can't deny what input is way easier to recognise that has wrong data.

Looking both inputs side by side it's clear that the second one is better. For the average user, on a form full of filled inputs, this makes a lot of difference. It's 100% don't make me think.

I know we can offer a nice sample code of "how to do it yourself", but we have to be honest with ourselves: on a cheap project with a near deadline, no developer will care about this kind of detail. So, again, we have to be responsible for UX and the "average user". It's not a huge change for us, but can benefit a lot of people.

I don't feel like I am in a position of telling what should be done – since I can't even help with coding and I am not paying for this great work – but every one of these states significant improves user experience. So, if you guys have the time and it's possible to go with Level 4 without perfomance issues, I would go for it – but if you are looking for the best balance, I think it is the Level 2.

Why don't we start with Level 2 and them, based on community feedback (aka issues "how to do this?"), we add support for other levels? 🙂

@justin-schroeder
Copy link
Member Author

@hmaesta strong argument, and I cant disagree that we should give a better UX to more end users by embracing sensible defaults. Great feedback. I'll try and implement 1-3. I'm concerned that Level 4 is a “bridge too far” since we'd need to tracking cursors/touch start etc. However your Level 4 would make for a compelling little plugin if someone wants to write one.

@justin-schroeder
Copy link
Member Author

Phew — ok everyone. This is finally merged and live in 2.4. You can read more in the docs here:

https://vueformulate.com/guide/theming/customizing-classes/

@hmaesta
Copy link
Contributor

hmaesta commented Jun 18, 2020

That's the fastest open source project team on GitHub 😆
Already using with Nuxt without problems 🙌 🚀

For the error classes, that's how I am doing:

let error_class = '';

Vue.use(VueFormulate, {
  classes: {
    input: (context) => {

      if (context.hasErrors) {
        error_class = 'border-red-400 hover:border-red-400';
      } else {
        error_class = '';
      }

      switch (context.classification) {
        case "select":
          return 'form-select ' + error_class;
        default:
          return 'form-input ' + error_class;
      }
    },

I am a designer... Not sure if that's the best approach – if you have suggestions, let me know.

Thank you for the fast implementation 👍

@mwojtul
Copy link

mwojtul commented Jun 18, 2020

Props on the 2.4 release! We're in the process of adding VueFormulate to our codebase and the newly introduced classes functionality has saved us from needing to create custom inputs that override the defaults, which had been necessary due to our Boostrap css setup.

@justin-schroeder
Copy link
Member Author

justin-schroeder commented Jun 19, 2020

That’s awesome @mwojtul! So glad that saved some pain!

@hmaesta Thanks for the kind words! I think the way you're doing it looks great. Also, we did implement your suggestion for states like hasErrors, isValid etc so if you want you can do:

Vue.use(VueFormulate, {
  classes: {
    input: (context) => {
      switch (context.classification) {
        case "select":
          return 'form-select ' + error_class;
        default:
          return 'form-input ' + error_class;
      }
    },
    inputHasErrors: 'border-red-400 hover:border-red-400'
  }

These are "state keys" and are always additive (they wont override). They're your idea :) and totally a helpful shortcut!

https://vueformulate.com/guide/theming/customizing-classes/#state-keys

@gilesbutler
Copy link

Congrats on the 2.4 release @justin-schroeder and the rest of the Braid team. Awesome work and amazing results!

@hmaesta
Copy link
Contributor

hmaesta commented Jun 19, 2020

we did implement your suggestion for states

Oh, I don't why I thought that just isValid was implemented. This is perfect! 🤩

Thanks, guys.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
future Accepted and tracking for an upcoming release suggestions welcome Looking for suggestions on a proposal
Projects
None yet
Development

No branches or pull requests

9 participants