Functional css make DX a pleasant, but I always wonder how do we convey some thing like
.item + item {
margin-top: 20px;
}
into our template using tailwindcss ?
You may write your own plugin for this
// tailwind.config.js
const plugin = require('tailwindcss/plugin')
module.exports = {
plugins: [
plugin( ({ addVariant, e }) => {
addVariant('item', ({ modifySelectors, separator }) => {
modifySelectors(
({ className }) => {
const eClassName = e(`item${separator}${className}`); // escaped class. This is like `item:bg-red-500` basically
return `[class^="item"] + .${eClassName}`; // this is your CSS selector
}
)
})
})
],
}
I chose this [class^="item"] selector (class starts with item) as it is allows not to have extra classes in HTML layout but it is up to you which you want to have
Use it as custom Tailwind variant like item:any-tailwind-class
<div class="item:mt-5 item:bg-red-500">1 - No `item` above - no CSS applied</div>
<div class="item:mt-5 item:text-yellow-500">2 - There is `item` above - CSS applied</div>
<div class="item:mt-5 item:bg-red-500 item:text-yellow-500">3</div>
<div class="item:mt-5 item:bg-red-500">4</div>
<div class="item:mt-5">5 - Has margin-top</div>
<div class="">6 - Nothing here</div>
<div class="item:bg-red-500">7- No `item` above - no color applied</div>
<div class="item">8 - This class starts with `item` so the next div should have styles</div>
<div class="item:mt-5 item:bg-red-500">9 - There is `item` above - CSS applied</div>
DEMO
Related
No fancy webpack, simple Vue custom element with some global css and some inline css for overrides.
I would like to use some styling library, like from getbootstrap.com and have it change styles inside custom element.
https://jsfiddle.net/Deele/6xk1atrn/25/
<div class="btn bg-info">Zero</div>
<test-widget id="One"></test-widget>
<test-widget id="Two"></test-widget>
const TestWidget = Vue.defineCustomElement({
props: {
id: String
},
data: () => {
return {
message: 'Test'
}
},
emits: {},
template: `<div class="btn bg-info">{{id}} {{message}}</div>`,
styles: [`div { color: green; }`]
})
customElements.define('test-widget', TestWidget)
.bg-info {
background-color: red!important;
}
Was expecting divs inside rendered elements would be styled as buttons, but it does not work!?
From what I have found in the internet, it has something to do with Shadow DOM not inheriting any global styles.
Please, tell me if there is a solution to this approach? I would like to create small widgets for my website using Vue.js, but this hurdle creates fatal limitation.
Custom elements defined using the Vue API always use a shadow DOM, so they are isolated from the parent document and any global styles in the app.
So to make it happen, You can inject the bootstrap styles or any global style url's in the styles option by using #import statement.
Live Demo :
const TestWidget = Vue.defineCustomElement({
props: {
id: String
},
data: () => {
return {
message: 'Test'
}
},
template: `<div class="btn bg-info">{{id}} {{message}}</div>`,
styles: [`#import url("https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css"); div { color: green; }`]
});
customElements.define('test-widget', TestWidget);
<script src="https://unpkg.com/vue#next"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css"/>
<div class="btn bg-info">Zero</div>
<test-widget id="One"></test-widget>
<test-widget id="Two"></test-widget>
I'm facing an extrange behavior trying to implement dynamic class by props on child element when using Nuxt 3 SSR + Tailwind.
My parent component includes a child component
<section-latest-news :count="12" :columns="4" />
My child component tries to render columns based on columns property
<template>
<p class="text-xl text-center uppercase font-semibold border-b-2 mb-4 pb-1 tracking-widest">Ăšltimas noticias {{gridCols}}</p>
<div :class="`grid gap-5 md:grid-cols-${columns}`" >
<div v-for="post in posts" :key="post.id" class="md:mb-0">
<post-card-image :post="post" />
</div>
</div>
</template>
<script setup>
import camelcaseKeys from 'camelcase-keys'
const props = defineProps({
excludeSlug: {
type: String,
required: false
},
count: {
type: Number,
required: false,
default: 6
},
columns: {
type: Number,
required: false,
default: 3
}
})
const runtimeConfig = useRuntimeConfig()
const route = useRoute()
const { data: posts } = await useFetch(`/public/latest`, {
params: {
count: props.count,
exclude_slug: props.excludeSlug
},
key: route.fullPath,
baseURL: runtimeConfig.public.apiBase,
transform: (response) => {
return camelcaseKeys(response, {deep: true})
}
})
</script>
For some reason, despite I correctly see the class md:grid-cols-3 in dev tools elements inspector, the class is not applied.
Please note that if I manually set the class without using backticks, the class works as expected, so it's not about CSS layout.
I'm guessing that is something related to SSR and lifecycle, but not sure how to fix it.
Actually, you cannot use dynamic classes with Tailwind: https://tailwindcss.com/docs/content-configuration#dynamic-class-names
Nothing related to Nuxt, interpolating a utility class is just not feasible since all the classes need to be known ahead of time (during the build): getting written in their full name in the code.
Here is how you can still achieve a similar result, but with more work of course.
I tried to change it with this line of code it but it doesn't work
const [click, setClick] = useState(false);
const closeNav = () => {
setClick(!click);
};
const openNav = () => {
setClick(!click);
};
<div
className=" absolute inset-0 ${click ? translate-x-0 : -translate-x-full }
transform z-400 h-screen w-1/4 bg-blue-300 "
>
<XIcon onClick={closeNav} className=" absolute h-8 w-8 right-0 " />
</div>;
Do it like this:
<div className={`absolute inset-0 ${click ? 'translate-x-0' : '-translate-x-full'} transform z-400 h-screen w-1/4 bg-blue-300`}></div>
// Alternatively (without template literals):
<div className={'absolute inset-0 ' + (click ? 'translate-x-0' : '-translate-x-full') + ' transform z-400 h-screen w-1/4 bg-blue-300'}></div>
Just keep in mind not to use string concatenation to create class names, like this:
<div className={`text-${error ? 'red' : 'green'}-600`}></div>
Instead you can select complete class name:
<div className={`${error ? 'text-red-600' : 'text-green-600'}`}></div>
// following is also valid if you don't need to concat the classnames
<div className={error ? 'text-red-600' : 'text-green-600'}></div>
As long as a class name appears in your template in its entirety, Tailwind will not remove it from production build.
There are some more options available for you like using a library like classnames or clsx, or maybe Tailwind specific solutions like twin.macro, twind, xwind.
Further Reading:
React.js conditionally applying class names
How to dynamically add a class to manual class names?
Correct way to handle conditional styling in React
Embedding Expressions in JSX
Template literals - MDN
Optimizing for Production - Writing purgeable HTML - Tailwind CSS
const bgClass: any = {
gray: " bg-gray-300",
red: " bg-red-300",
orange: " bg-orange-300",
yellow: " bg-yellow-300",
green: " bg-green-300",
teal: " bg-teal-300",
blue: " bg-blue-300",
indigo: " bg-indigo-300",
purple: " bg-purple-300",
pink: " bg-pink-300 ",
}
const convertLabelToBg = (label: string, baseClass: string): string => {
let className: string = baseClass;
if (label) {
className += bgClass[label];
}
return className;
}
It worked for me.
I have followed the documentation.
https://tailwindcss.com/docs/content-configuration#dynamic-class-names
I have the following class with a function, that opens a modal (open_modal(...)) in a separate file to a component as I have a large number of modals that use this functionality.
import open from "open";
import $ from "jquery";
class ReactHelpers {
static open_webpage(page_url) {
open(page_url);
}
static open_modal(overlay_id, modal_id) {
$(overlay_id).css("display", "block");
$(modal_id).css("display", "block");
}
static close_modal(overlay_id, modal_id) {
$(overlay_id).css("display", "none");
$(modal_id).css("display", "none");
}
}
export default ReactHelpers;
I am trying to assert that the open_modal function has added css to the divs in question as below:
it('should close the modal', function () {
const wrapper = shallow(
<div id="overlay_id">
<div id="modal_id">
<p>modal</p>
</div>
</div>
)
const overlay = wrapper.find('#overlay_id')
const modal = wrapper.find('#modal_id')
ReactHelpers.open_modal(overlay, modal);
console.log('OVERLAY ', overlay);
expect(overlay.prop('style')).toHaveProperty('display', 'block');
expect(modal_style).toHaveProperty('display', 'block');
});
Further, I'm sure to how the open_webpage function would be tested as this is a library function. In my other tests in my other components, I'm mocking this so it's never actually been tested.
Any help is greatly appreciated.
Thanks
To test style of dom elements:
You should mount the component (using mount), instead of just creating it (using shallow).
Since you're changing the style of dom element directly, You should test the style of the dom element (component.getDOMNode().style.display), instead of testing the react style property (component.prop.style).
example:
import $ from "jquery";
it("should create a div and changes its color to red", () => {
const wrap = mount(
<div id="red_el"></div>
);
const el = wrap.find("#red_el").getDOMNode()
$(el).css("color", "red");
expect(el.style.color).toEqual("red");
});
In your case:
it("should open modal", () => {
const wrapper = mount(
<div>
<div id="overlay" style={{ display: "none" }}>
<div id="modal" style={{ display: "none" }}>
overlay
</div>
</div>
</div>
);
const overlay = wrapper.find("#overlay").getDOMNode();
const modal = wrapper.find("#modal").getDOMNode();
ReactHelpers.open_modal(overlay, modal);
expect(overlay.style.display).toEqual("block");
expect(modal.style.display).toEqual("block");
});
See it live on codesandbox (switch to the tests tab to run the tests .)
So I am creating an Electron app that allows the user to create an invoice and then produce a PDF of that invoice. I'm using a PDF npm package that allows me to pass in HTML. So I am using JSX to create the invoice dynamically then I am turning that JSX into an HTML string. Because I am doing it like this I cannot use typical css files to set styling. So I need to use JSX's inline styling. But for some reason I can't get it to work with CSS Grids. Am I doing anything wrong. I've tried researching it and can't really find any good information on what I'm trying to do.
let headerStyle = {
display: 'grid',
gridTemplateColumns: '3fr 1fr',
gridTemplateRows: 'auto auto',
}
let invoiceHeaderInfoStyle = {
gridColumn: '2 / 3',
gridRow: '1 / 2'
}
let invoiceHeaderCompanyInfoStyle = {
gridColumn: '1 / 2',
gridRow: '1 / 2'
}
let invoiceHeaderClientInfoStyle = {
gridColumnStart: '1',
gridColumnEnd: '2',
gridRow: '2 / 3'
}
return (<div>
<header style={headerStyle}>
<h1>Invoice</h1>
<section style={invoiceHeaderInfoStyle}>
<div className="invoiceHeaderInfoDetails">
<span>Invoice #:</span>
<span>{invoice.number}</span>
</div>
<div className="invoiceHeaderInfoDetails">
<span>Date:</span>
<span>{invoice.date}</span>
</div>
</section>
<section style={invoiceHeaderCompanyInfoStyle}>
<div><p>Company Name</p></div>
<div><p>Some City, KS</p></div>
<div><p>(555)555-5555</p></div>
<div><p>companyname#email.com</p></div>
</section>
<section style={invoiceHeaderClientInfoStyle}>
<div>{this.getFormattedAddressHeader()}</div>
</section>
</header>
</div>)
I'm sure you figured this out, but for others, you have to include style as JSX object:
import style from './app.css';
export default () => (
...
<div className={style.box}>A</div>
...
It's interesting the element style works in normal way, but references to classes have to be specified as JSX variables.