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
Comments
@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 👍 |
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 👍 |
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 👍 |
@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 |
I would love code comments, particularly useful when figuring out which dropdown is mobile. 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 |
As for documenting interactive state, <!-- 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 / dropdownI don't think comments are better for the different states though. Really hard to digest Maybe that can be a web app feature:
|
@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. |
Agreed @adamwathan 👍 |
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! |
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. |
I think it's great how it is. +1 for keeping it like it is. Extra comments for more info is a good 'solution' |
@ 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: I fixed this by changing all the bindings like this: 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 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 |
Suggestion:
This way the examples would be JS framework agnostic. Bulma docs have a good setup: e.g. https://bulma.io/documentation/components/navbar/#navbarJsExample. |
@adamwathan Not sure why this issue is closed. Can this be reopened? |
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 😉) |
@sandervanhooft I think that it was made clear that it’s not a scope of this project:
|
@sandervanhooft Would you prefer the HTML comment based approach outlined in my earlier 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. |
@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... |
Made a mockup of what a "state selector" might entail |
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. |
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.
I believe this could be solved by providing two versions of every component (where applicable):
|
@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:
To me the constraints of the ideal solution are:
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. |
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. |
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 🤙 |
Made a little helper site that cleans up the code based on @KevinBatdorf 's regex (hope thats ok, Kevin?). I also strip the comments. |
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/ |
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?).
The text was updated successfully, but these errors were encountered: