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

Code snippets should not be tangled with any framework #7

Closed
mgibas opened this issue Mar 1, 2020 · 29 comments
Closed

Code snippets should not be tangled with any framework #7

mgibas opened this issue Mar 1, 2020 · 29 comments
Labels
bug Something isn't working

Comments

@mgibas
Copy link

mgibas commented Mar 1, 2020

Describe the bug
Code snippets have alpine embedded in - instead of just copy paste you have to go and remove/replace them with framework of your choice.

Expected behavior
Provide "pristine" snippet first and "framework versions" (vue, angular, react ?) as and extra (stretch goal?).

@mgibas mgibas added the bug Something isn't working label Mar 1, 2020
@danharrin
Copy link

This could be done with a framework switcher like the Ionic Framework docs uses:

Ionic Framework docs example

Framework preference is persisted whilst browsing the docs.

@adamwathan
Copy link
Member

adamwathan commented Mar 1, 2020

@mgibas Hey thanks for opening this discussion, I definitely think it's something we should figure out as a community.

I tried for quite a while to come up with a succinct way to make the snippets plain HTML and just document that classes that needed to be added/removed in different states using HTML comments, but no matter what I tried, the result was always harder to understand than what I had put together with the simple Alpine examples.

Here's an example of trying to document the off-canvas navigation in sidebar layouts for example:

<div class="h-screen flex overflow-hidden bg-gray-100">
  <!-- Off-canvas menu for mobile -->
  <div class="md:hidden">
    <!-- 
      Semi-transparent overlay

      Close the sidebar when this is clicked
      When sidebar is open, add classes "opacity-75 pointer-events-auto"
      When sidebar is closed, add classes "opacity-0 pointer-events-none" (already included here by default)
    -->
    <div class="fixed inset-0 z-30 bg-gray-600 transition-opacity ease-linear duration-300 opacity-0 pointer-events-none"></div>

    <!--
      Sidebar drawer

      When sidebar is open, add class "translate-x-0" 
      When sidebar is closed, add class "-translate-x-full" (already included here by default)
    -->
    <div class="fixed inset-y-0 left-0 flex flex-col z-40 max-w-xs w-full bg-gray-800 transform ease-in-out duration-300 -translate-x-full">
      <div class="absolute top-0 right-0 -mr-14 p-1">
        <!--
          "Close sidebar" button

          Close the sidebar when this button is clicked, and hide it immediately after clicking
        -->
        <button class="flex items-center justify-center h-12 w-12 rounded-full focus:outline-none focus:bg-gray-600">
          <svg class="h-6 w-6 text-white" stroke="currentColor" fill="none" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
          </svg>
        </button>
  
  <!-- Snipped -->

...or a dropdown menu with a bunch of transitions to document:

<div class="relative inline-block text-left">
  <div>
    <span class="rounded-md shadow-sm">
      <button type="button" class="inline-flex justify-center w-full rounded-md border border-gray-300 px-4 py-2 bg-white text-sm leading-5 font-medium text-gray-700 hover:text-gray-500 focus:outline-none focus:border-blue-300 focus:shadow-outline-blue active:bg-gray-50 active:text-gray-800 transition ease-in-out duration-150">
        Options
        <icon name="sm-chevron-down" class="-mr-1 ml-2 h-5 w-5" />
      </button>
    </span>
  </div>

  <!-- 
    Hide or show this element based on whether the dropdown should be open or closed.

    Transition classes:
      While opening: "transition ease-out duration-100"
      Starting point when opening: "transform opacity-0 scale-95"
      Ending point when opening: "transform opacity-100 scale-100"
      While closing: "transition ease-in duration-75"
      Starting point when closing: "transform opacity-100 scale-100"
      Ending point when closing: "transform opacity-0 scale-95"
  -->
  <div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
    <div class="rounded-md bg-white shadow-xs">
      <div class="py-1">
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">Account settings</a>
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">Support</a>
        <a href="#" class="block px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">License</a>
        <form method="POST" action="#">
          <button type="submit" class="block w-full text-left px-4 py-2 text-sm leading-5 text-gray-700 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900">
            Sign out
          </button>
        </form>
      </div>
    </div>
  </div>
</div>

To me, this felt like a much worse experience than parsing the Alpine.js directives, so I decided to stick with that when we launched.

If everyone overwhelmingly agrees the HTML comments are better, I'm happy to switch everything over. Personally I would prefer to not have any JS in the examples at all — I want it to be very clear that what we're proving is very "bring-your-own-JavaScript" because there is all sorts of complex behavior we haven't included (like keyboard navigation, focus-trapping, etc.) since it is all framework specific and out of scope for what we're offering.

@danharrin Love that example and maybe something for us to strive for if/when we explore official support for popular frameworks. Right now though we are focused on the "HTML-only, bring your own JS" product we actually have for sale and need to dedicate our resources to designing and building the big backlog of examples we have lined up 👍

@adamwathan
Copy link
Member

Question for the community:

Can you suggest an HTML-only way to document what classes need to be toggled for interactive components in different states that you think is an improvement over what we currently have?

Would love any concrete examples to consider 👍

@mgibas
Copy link
Author

mgibas commented Mar 1, 2020

Oh lord, no for comments option! :)

Personally I think that at some point there is no escape from providing separate snippets for each framework (at least 3 most popular) - I would imagine I can select component, copy/paste, put some customisations and be done. Now I have to do some parsing, translate Alpine to Vue (at least it's syntax seems to be Vue inspired) and then go back to original flow. I may be wrong and maybe some other ppl will chip some ideas in :)

To be honest though I would rather have more components than that for now 👍

@pdevito3
Copy link

pdevito3 commented Mar 1, 2020

@adamwathan I like having the alpine example in the raw components as it shows how they are meant to operate and saves even more time for us.

With that said, the conversions can be tedious so far. I would guess the implementation with the lowest LOE for you would be to provide "conversion protocols" that can give an SOP for converting tailwind ui components to each framework.

For example, Vue is relatively straight forward. I've found the biggest updates I need are changing x-show to v-show and variable names or props for various operations. Have also needed to create keydown code. Clicks have generally worked as is. I haven't taken a crack at al the x-transitions yet.

React is a bit more involved from the minimal I've done so far. First step is to convert class to className. Clicks won't work as is. I think ryanb had a transition snippet in the react discord. I'm sure there are many others. Also found this in the discord as some react items

Same kind of a thing for angular, ember, etc.

That way, we can at least go through each component and check a bunch of updates off the SOP before calling out any problems. Will also facilitate speed until we can find a way to provide examples in multiple languages. SOP could help standardize this too if you had crowd sourced bases for you to start from before you push them to tailwindui

also agreed that i'd rather have new components for now

@dillingham
Copy link

dillingham commented Mar 1, 2020

I would love code comments, particularly useful when figuring out which dropdown is mobile.
I think the web app could have settings, code_comments: true and strip them before copy.

If you wanted to use them for interchangeable sub components, like the navbar's profile dropdown

<!-- DROPDOWN-START -->
<!-- DROPDOWN-END -->
<!-- MOBILE-DROPDOWN-START -->
<!-- MOBILE-DROPDOWN-END -->

Also allows future possibly selecting the sub-component to replace the comment section with
Maybe adding a prefix of <!-- TUI-* --> to for a webpack stripping

@dillingham
Copy link

dillingham commented Mar 1, 2020

As for documenting interactive state, TODO: could be useful.
Mixed with an IDE TODO viewer, or search, you make it easy to identify.

<!-- TODO: TUI-DROPDOWN-TOGGLE -->
<div>
    <button @click="open = !open" class="...">
        <img class="h-8 w-8 rounded-full" src="..." alt="" />
    </button>
</div>

And default the menu to open so the developer attends to it upon visually inspecting the outcome

Web app state viewer / dropdown

I don't think comments are better for the different states though. Really hard to digest

Maybe that can be a web app feature: state selector: Closed, Opening, Open, Closing

  • shows the component in the state
  • shows the class diff

@adamwathan
Copy link
Member

@mgibas Cool it's helpful to know you hate the comment approach — so the question is still open though which is if we only show one syntax (which is the plan for the foreseeable future), what could we show that is the most helpful to everyone?

My biggest concern with trying to show minimal examples in multiple formats is that I worry it over-promises on what we are delivering — people are going to think "wow this is a ready-to-go React component!" when in reality it still won't be, because it can be dozens and dozens of lines of JS to handle stuff like keyboard navigation, focus trapping, positioning dropdowns with Popper.js, whatever, and all that stuff is part of the necessary work when trying to take our pre-designed templates and turn them into properly keyboard-accessible, fully-functioning UI components.

That's a problem I'd love to tackle one day but it's a huge problem way outside the scope of what we've been working on so far and I don't want it to take our attention away from what we really should be focusing on right now.

@mgibas
Copy link
Author

mgibas commented Mar 1, 2020

Agreed @adamwathan 👍
This also seems to be an issue only for complex components (organisms and up?).

@danharrin
Copy link

Hey @adamwathan, thanks for taking the time to review this.

Personally, I am an Alpine lover so I have no issues with using the component library as it is. The examples are straight forward to me, but I have seen people struggling (especially on the Discord group) to partner their own JS framework with the components.

I would definitely steer away from using HTML comments. If you would like me to brainstorm a few other ideas and get back to you, let me know. I can build up an example of how I'd make the feature look, if you'd find that helpful.

Thanks again for an awesome product!

@tchak
Copy link

tchak commented Mar 1, 2020

I played a bit with tailwind UI and Ember. I have no issue with the current alpine example implementation. It is easy to read and convert to whatever you like. I think the strength of tailwind UI is that it not supposed to provide you with a finite component, but gives you a baseline to start from.

@Christophvh
Copy link

I think it's great how it is. +1 for keeping it like it is. Extra comments for more info is a good 'solution'

@vvo
Copy link

vvo commented Mar 1, 2020

Can you suggest an HTML-only way to document what classes need to be toggled for interactive components in different states that you think is an improvement over what we currently have?

What about having a dropdown/links of the various states for a template/example and highlighting the diff in the code snippet? So that you can stick to pure html and css.

For the "complex parts" like transitions, have generic (not per component) documentation per framework explaining how to handle them.

Something like that:
image

Now that I think about it, it could be overwhelming/not really doable. Or just worse than what we have right now. But if this idea resonates with you let me know!


Another way to solve this issue could maybe to provide ONE documentation example/tutorial per framework of turning a "complex" alpine layout (with states, transitions, keyboard) to your favorite framework.

This way people would maybe feel more secure, knowing that yes, it's alpine but there's documentation on how to turn alpine into React. I know it may be out of your scope but at the same time some alpine examples do require some digging to understand how to turn them into React.


On a side note...

I must say I was surprised to see the examples were with alpinejs (I know now, it's on the pricing FAQ :D). It was the first time I encounter this framework. I was so excited by Tailwind UI and the checkout pages were so well done that I completely missed (= lazy yes!) all the examples were with alpinejs. I appreciate the tremendous work you have accomplished for sure and I will use it (I am sure once I will really dig it further it will be fine).

But! Given alpine seems an exotic1 framework I believe it could hurt Tailwind UI on the long term because no matter how you'll explain it, people will always think "Oh wow the examples are in a framework I never worked with". Even if the symtax seems easy, it's still one you need to learn and maybe never planned to.

I understand I am criticizing without providing the real solution here but just how I felt when going through the first code samples: surprise! And again, I will use Tailwind UI no matter what!

1About exotic, I meant: Way way less used than react, angular, vue, vanilla.
image

@mgibas mgibas closed this as completed Mar 2, 2020
@erik06
Copy link

erik06 commented Mar 11, 2020

@ adamwathan I think using alpine in the docs as you have it now, works really well for me, I clearly see that I need to add Alpine to my Vue / Nuxt app and then I can copy and paste from Tailwind UI right into my app and I'm good to go (very fast turn around). Or if I'm building a static site without any JS framework, I know I can just plop Alpine in, and be on my way.

The BIG issue is that you have used Alpine's "short" bindings that are the same as Vue / Nuxt, so when I place the component from Tailwind UI, and include Alpine in my header, I get major collisions because Vue tries to bind the Alpine "short" bindings.

I was able to solve the collision issue by converting ALL of the Alpine bindings to their full, full-length bindings. Here is an example:

Alpine click binding from the Tailwind Navbar:
<button @click="open = !open" class="inline-flex ...
Which collides with Vue JS's @click binding.

I fixed this by changing all the bindings like this:
<button x-on:click="open = !open" class="inline-flex ...

If you could change all the Alpine code to use their full-length biding names, it may cause less confusion and would save me a world of time.

(I'm aware I could re-format the Alpine bindings to use Vue, but for simplicity and speed, I just included Alpine and changed all the bindings to use the x-[binding-here] etc.

I think editing all the components to have their full Alpine binding would be very helpful and would prevent collisions out of the box.

Love the product/technology, thanks for your work! #teamKeepAlpine

@prsn-uk
Copy link

prsn-uk commented Mar 15, 2020

Suggestion:

  • Elements that need JS attached have a specific id or class set.
  • JS is attached from a separate file or script tag using getElementById etc.

This way the examples would be JS framework agnostic.

Bulma docs have a good setup: e.g. https://bulma.io/documentation/components/navbar/#navbarJsExample.

@sandervanhooft
Copy link

@adamwathan Not sure why this issue is closed. Can this be reopened?

@sandervanhooft
Copy link

I'm also dealing with the Vue-Alpine clash mentioned by @erik06. Currently I'm stripping out all Alpine stuff, which is more tedious than I thought. I now need to learn Alpine - just to strip it out. Next I will have to reintroduce Vue.

I'm a big fan of the teachings in Adam's advanced Vue course - separation of concerns, reusable components. I'd favour any framework/approach to the Tailwind UI component examples that does not mix markup and framework code. @prsn-uk 's suggestion on this may also be interesting.

For me, the current Alpine setup is significantly negating the efficiency benefits that Tailwind UI is bringing to the table. (Still, it's so shiny I just have to use it 😉)

@mgibas
Copy link
Author

mgibas commented Mar 19, 2020

@sandervanhooft I think that it was made clear that it’s not a scope of this project:

That's a problem I'd love to tackle one day but it's a huge problem way outside the scope of what we've been working on so far and I don't want it to take our attention away from what we really should be focusing on right now.

@sandervanhooft
Copy link

sandervanhooft commented Mar 19, 2020

Ok, could be that I don't agree with the project scope / priorities then. 🤷‍♂

See, this currently isn't true for me with the current setup (from the top of TailwindUI.com):

image

If I drop these component examples in, my UI breaks completely, because there's js scattered throughout the components.

@adamwathan
Copy link
Member

@sandervanhooft Would you prefer the HTML comment based approach outlined in my earlier comment?

#7 (comment)

That's the only real alternative on the table right now but in my attempts to make it clear, it always ended up more convoluted and harder to understand than just reading the Alpine declarations (as mentioned in the documentation).

If you have any suggestions on how to document what classes need to change under what states I would love to see them — I really don't want things to be tangled with any specific JS tool either but so far I haven't been able to come up with anything better.

@sandervanhooft
Copy link

@adamwathan I understand that there's no one size fits all approach here. Yes, I'd prefer the suggested HTML comment based approach over having only the Alpine one.

Best would be to have these two side by side, but I understand if that's too much to ask, especially in terms of maintainability.

Personally, if you'd be using js in the examples, I'd go with a framework that can be wrapped around the html examples as much as possible and is not sprinkled throughout.

I really dislike Bootstrap and jQuery, but in the docs they used to solved this issue using separate js/selectors/jQuery, afak completely outside the html. Not a big fan (understatement) of this approach for real use cases, but it may be an interesting direction here.

- If only there was an easy fix...

@dillingham
Copy link

Made a mockup of what a "state selector" might entail

https://github.com/tailwindui/issues/issues/106

@pdme
Copy link

pdme commented Apr 5, 2020

I just purchased early access and was taken aback by this embedded JS as well. I had expected plain HTML.

But I understand your predicament: How to document the code changes for different states of a component?

Here's a thought: Why not try a GitHub-like approach to show code changes?

Let's say you show different tabs for different states. You'd have an initial state, and then on the "isOpen" tab, for example, you'd see what you see when you look at a commit on GitHub. That way you can easily see which classes to add/remove for which state.

@schaeferalex
Copy link

schaeferalex commented Apr 8, 2020

I have to agree with most commentators here. I was surprised to see JS deeply embedded in the examples. The messaging on the website certainly made me believe there wouldn't be any issues like this. I had to go line by line to remove any occurrences, especially as the syntax clashed with VueJS.

In order to be as universal as possible, Tailwind UI is an HTML-only component library and does not include any JavaScript.

I believe this could be solved by providing two versions of every component (where applicable):

  1. Plain HTML without any JS (just for the basic look and feel)
  2. Interactive version including JS (AlpineJS as an example of how interactivity could be achieved)

@adamwathan
Copy link
Member

@schaeferalex Thanks for your input! The real issue as mentioned earlier is that static HTML doesn't capture the classes needed for the different states an element can be in, like open vs. closed or transitioning in vs. transitioning out.

Do you prefer this approach where everything is in comments?

<div class="h-screen flex overflow-hidden bg-gray-100">
  <!-- Off-canvas menu for mobile -->
  <div class="md:hidden">
    <!-- 
      Semi-transparent overlay

      Close the sidebar when this is clicked
      When sidebar is open, add classes "opacity-75 pointer-events-auto"
      When sidebar is closed, add classes "opacity-0 pointer-events-none" (already included here by default)
    -->
    <div class="fixed inset-0 z-30 bg-gray-600 transition-opacity ease-linear duration-300 opacity-0 pointer-events-none"></div>

    <!--
      Sidebar drawer

      When sidebar is open, add class "translate-x-0" 
      When sidebar is closed, add class "-translate-x-full" (already included here by default)
    -->
    <div class="fixed inset-y-0 left-0 flex flex-col z-40 max-w-xs w-full bg-gray-800 transform ease-in-out duration-300 -translate-x-full">
      <div class="absolute top-0 right-0 -mr-14 p-1">
        <!--
          "Close sidebar" button

          Close the sidebar when this button is clicked, and hide it immediately after clicking
        -->
        <button class="flex items-center justify-center h-12 w-12 rounded-full focus:outline-none focus:bg-gray-600">
          <svg class="h-6 w-6 text-white" stroke="currentColor" fill="none" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
          </svg>
        </button>
  
  <!-- Snipped -->

No matter what you have to translate the instructions to Vue in your case, either from the comments or from the inline Alpine directives.

Personally I'd rather come up with a good way to just use comments and rip out all of the Alpine stuff too, because it sends the wrong message no matter how much I try to document it, and I've had support requests from people trying to make Alpine work inside of a React app because of it (which is absolutely NOT what you should do, you should just use React and use the Alpine directives as instructions).

I think coming up with some innovative way to display the different states has potential but is also high risk, as I wrote about in another thread:

It would be a huge investment to build though, likely a month of effort that we couldn't spend on something else. Also trying to show transitions with this wouldn't be straightforward so still some unknowns. Transitions have a lot of states (starting enter, finished enter, starting leave, finished leave) so it would get messy.

It also means you can't really just copy and paste the code anymore, you'd have to copy all the states separately or use the mouse to select just the green additions when building each state.

My fear is that even if we did invest a month into building this, once you could actually try it and interact with it it could still feel slow or inconvenient to use, so it's a high risk thing to focus on right now :/

To me the constraints of the ideal solution are:

  1. Has to be one-click copy and pastable, so has to all be contained in a single snippet.
  2. Has to include zero JS so nobody tries to integrate Alpine into their React/Vue/Angular apps, I want it to be crystal clear that you should provide the JS yourself (even our Alpine snippets are not production ready, they are purely instructional)

The problem like I've mentioned before is that every solution I've tried to come up with that meets these constraints is still a worse solution than just adding some dynamic class bindings and click handlers with Alpine, because it's harder to read and harder to translate. It doesn't mean the Alpine solution is good but every other solution so far has been worse. Tough problem and would love concrete suggestions that meet the criteria mentioned above.

@schaeferalex
Copy link

Whenever an element has different states, the HTML version should just contain the state in which it would be loaded initially when accessing a page. Any other state should be included in the interactive version allowing for one-click copy.

For example, a dropdown in the HTML version would just be displayed as a button. If you want to use the full functionality you can switch to the interactive version and copy/paste or copy/modify the required code.

I am not in favour of comments in the code as this is just another element to strip from the final code. In my opinion the right place for all comments is in the documentation.

Another consideration should be that a lot of functionality that is commonly achieved by adding JS can very often actually be solved by CSS alone.

@KevinBatdorf
Copy link

KevinBatdorf commented Apr 8, 2020

You could hijack the copy event and strip out the AlpineJS code. Then just refer to the code as needed.

const code = document.querySelectorAll('[x-ref=clipboardCode]')
Array.from(code).forEach(node => {
    node.addEventListener('copy', event => {
        let selection = window.getSelection()
          .toString().split(/\r?\n/)
	  .map(function(string, index) {
              const alpine = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>| [:@x-][^"]*="[^"]*"|(\r\n)/g
              return string.replace(alpine, '')
	  })
          .filter(string => string.length)
          .join("\r\n")
	
	event.clipboardData.setData('text/plain', selection)
	event.preventDefault()
    })
})

If you're using Tampermonkey you can install this and use it now in Chrome.

https://gist.github.com/KevinBatdorf/136b648cedccc2a127ff5cde0d354e1a

Seems to work fine but if it's missing something just comment on the gist 🤙

@lww
Copy link

lww commented Apr 9, 2020

Made a little helper site that cleans up the code based on @KevinBatdorf 's regex (hope thats ok, Kevin?). I also strip the comments.

https://tailwinduicleaner.now.sh/

@vvo
Copy link

vvo commented Apr 9, 2020

For react users: Based on a gist, I did a webpage where you can drag a bookmark to help you copy JSX from the Tailwind UI website directly, see here: https://qlp8g.csb.app/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests