Skip to content

Instantly share code, notes, and snippets.

@KevinBatdorf
Last active February 27, 2023 16:33
Show Gist options
  • Save KevinBatdorf/322c68f532af6a4cc6773f906da0684d to your computer and use it in GitHub Desktop.
Save KevinBatdorf/322c68f532af6a4cc6773f906da0684d to your computer and use it in GitHub Desktop.
This Gist lets you download the Alpine version of TailwindUI components
// ==UserScript==
// @name Download AlpineJs version from Tailwind UI
// @namespace kevinbatdorf
// @version 1.0
// @description When you press copy, it will download an html file containing the script code and alpine HTML
// @author https://twitter.com/kevinbatdorf
// @match https://tailwindui.com/components/*
// @grant none
// ==/UserScript==
// Requires Tampermonkey
// Chrome - https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en
// FF - https://addons.mozilla.org/en-US/firefox/addon/tampermonkey/
// Find me on Twitter: https://twitter.com/kevinbatdorf
// Copy versio: https://gist.github.com/KevinBatdorf/8bd5f808fff6a59e100dfa08a7431822
// If it seems to not be working, check the classes here are matching
const code = Array.from(document.querySelectorAll('button')).filter(
b => b.classList.value === 'group relative ml-2 hidden h-9 w-9 items-center justify-center sm:flex'
);
code.forEach(node => {
node.addEventListener('click', function(event) {
event.preventDefault();
// This is the iFrame the component is in
const iFrame = event.target.closest('[id^=component-]').querySelector('[name][id^=frame]');
const contentArea = iFrame.contentWindow.document.querySelector('body > div');
const markup = contentArea.innerHTML;
// This will attempt to extract the function used in the x-data, like x-data="Component.popover"
const scripts = Array.from(contentArea.querySelectorAll('[x-data]'))
.filter((n) => /\(.*\)/.test(n.getAttribute('x-data').toString()))
.map((n) => {
const fnNameSplit = n
.getAttribute('x-data')
.replace('window.', '')
.replace(/\(.*\)/, '')
.split('.')
// We need to get the string representation of the method
const method = new Function(`return ${iFrame.contentWindow[fnNameSplit[0]][fnNameSplit[1]]};`)
// This adds checks to create the global object then register it.
return `
window.${fnNameSplit[0]} = window.${fnNameSplit[0]} ?? {}
window.${fnNameSplit[0]}['${fnNameSplit[1]}'] = ${method}()
`
})
.join(';')
.toString();
let fileName
try {
fileName = event.target.closest('[id^=component-]').querySelector('h2').textContent.trim().toLowerCase().replace(/[^a-z0-9_]+/gi, '-');
} catch(e) {
fileName = 'tailwind-alpine'
}
const content = scripts ? `<script>${scripts}</script>` + markup : markup;
const link = document.createElement('a');
link.download = `${fileName}.html`;
link.href = window.URL.createObjectURL(new Blob([content], {type: 'text/html'}));
link.click();
});
node.removeAttribute('@click');
});
@KevinBatdorf
Copy link
Author

Copy to clipboard version here: https://gist.github.com/KevinBatdorf/8bd5f808fff6a59e100dfa08a7431822

That version has issues copying large components, so this one is probably preferred. You can use them both and toggle them as needed:

Screen Shot 2022-05-20 at 10 43 05 PM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment