How to use variables to dynamic change TailwindCSS properties? - css

I'm building a portfolio and i'm currently stuck in a section where i'm looping thru an object which contains the respective color that I want to use (eg:
export const skills = [
{
id: 1,
name: "front-end",
Icon: FaReact,
color: "#61DAFB",
},
In the component mapped receiving these props, I already logged the color variable and it's logging correctly. But when I try to use that variable to dynamic change the color of the component, it doesn't work at all.
const SkillCard = ({ name, Icon, tools, color }) => {
console.log(`[${color}]`);
return (
<article
className={`bg-black text-gray-300 w-full hover:shadow-lg hover:shadow-gray-800 flex flex-col gap-4 p-8 rounded-lg grayscale hover:grayscale-0 duration-200 border-b-[${color}] `}
>
<Icon style={style} size={50} />
<h1 className="font-bold text-xl capitalize">{name}</h1>
<p>{tools}</p>
</article>
);
};
Here you can see that I'm trying to use the border-bottom property to change depending on the color contained in the array of objects, but I just couldn't find the solution.
I already tried chanching the value of the property, to contain the square brackets, but didn't work as well.
Update January 23:
const SkillCard = ({ skill }) => {
const { name, Icon, tools, color } = skill;
const style = { color };
return (
<article
style={{
borderBottomStyle: "solid",
borderBottomColor: color,
borderBottomWidth: "8px",
}}
className="bg-black text-gray-400 hover:text-white w-full hover:shadow-lg hover:shadow-gray-800 flex flex-col gap-4 p-8 rounded-lg grayscale hover:grayscale-0 duration-200"
>
<Icon style={style} size={50} />
<h1 className="font-bold text-xl capitalize">{name}</h1>
<p>{tools}</p>
</article>
);
};
Just added that color property as an inline style and now it works as intended. Not shure if it's best practice, but it's what I achieved so far.

It seems that this is because Tailwind need the full class name (complete unbroken strings) to assign the correct style, according to Tailwind document.
Live demo of the example: stackblitz
For example, perhaps try something like:
export const skills = [
{
id: 1,
name: "front-end",
Icon: FaReact,
borderBotttomColor: "border-b-[#61DAFB]",
},
Then apply to the component, perhaps also add a bottom border width such as border-b-4:
const SkillCard = ({ name, Icon, tools, borderBotttomColor }) => {
return (
<article
className={`${borderBotttomColor} border-b-4 bg-black text-gray-300 w-full hover:shadow-lg hover:shadow-gray-800 flex flex-col gap-4 p-8 rounded-lg grayscale hover:grayscale-0 duration-200`}
>
<Icon style={style} size={50} />
<h1 className="font-bold text-xl capitalize">{name}</h1>
<p>{tools}</p>
</article>
);
};
Alternatively, dynamic class names could be defined as safelist in Tailwind configuration, although it might not be suitable for this use case.

Related

Popover on table rendering multiple instances in Vue3

I am trying to create a Vue component which wraps a Popoverfrom headlessui
I am trying to toggle open close using the slot binding as suggested in the popover docs. I cannot get this to work. Nothing appears in the UI, even if I had it working ok from within the element itself, granted that is neither a good place to put it because the elements render as many rows there are in the table.
Does anyone have any wisdom surrounding headless UI popovers in Vue3 on a table row?
EDIT:
Ok adding static to the PopoverPanelmakes it render when open=true
, the issue now being that multiple instances render based on the number of elements in the table. So this is a new issue.
<template>
<div>
<Popover :slot="{pop}">
<transition
enter-active-class="transition duration-200 ease-out"
enter-from-class="translate-y-1 opacity-0"
enter-to-class="translate-y-0 opacity-100"
leave-active-class="transition duration-150 ease-in"
leave-from-class="translate-y-0 opacity-100"
leave-to-class="translate-y-1 opacity-0"
>
<PopoverPanel static class="absolute z-9 mt-3 max-w-sm -translate-x-1/2 transform sm:px-0 lg:max-w-3xl">
<div class="rounded-lg shadow-lg w-full m-2">
<div
v-if="pop"
ref="popover"
class="flex flex-col p-2"
>
<ButtonTemp label="Send reminder" btn-type="m-0.5 bg-neutral-200 text-neutral-900 text-left text-caption hover:bg-neutral-900 hover:text-neutral-100" icon-left>
<IconsMail />
</ButtonTemp>
<ButtonTemp label="Share candidate" btn-type="m-0.5 bg-neutral-200 text-neutral-900 text-caption hover:bg-neutral-900 hover:text-neutral-100" icon-left>
<IconsExternalLink />
</ButtonTemp>
<ButtonTemp label="Remove from assignment" btn-type="m-0.5 bg-neutral-200 text-neutral-900 text-caption hover:bg-danger-100 hover:text-neutral-100" icon-left>
<IconsUserMinus />
</ButtonTemp>
</div>
</div>
</PopoverPanel>
</transition>
</Popover>
<script setup lang="ts">
import { Ref } from '#vue/runtime-core';
import { Popover, PopoverButton, PopoverPanel } from '#headlessui/vue';
import { ButtonTemp, IconsMail, IconsExternalLink, IconsUserMinus } from '#/.nuxt/components';
const pop: Ref<boolean> = ref(true);
interface IContextMenu {
open: boolean;
}
const props = defineProps<IContextMenu>();
const isOpen = toRef(props, 'open');
watch(isOpen, (is) => {
if (is) {
console.log('open', is);
pop.value = !pop.value;
}
});
onMounted(() => {
pop.value = true;
});
</script>
Component where it is to be used:
......
<td class="group-hover:text-neutral-100 rounded-r">
<button
class="m-auto h-8 w-8 rounded-sm flex justify-center items-center group-hover:bg-neutral-700 cursor-pointer"
#click="handleClick(candidate.id, '')"
>
<ContextMenu :open="open" /> // Popover wrapped component
<IconsVerticalMenu />
</button>
</td>
</tr>

How to give equal space between the items using tailwind css v3

I am using Tailwind CSS with next.js.
I have 8 images of different sizes and want to keep 4 images in a row for mid and large-size devices and 2 images in a row for small-size devices.
I want to give equal space between the items in a row but I want the first item in a row to be at the leftmost place and the last item in a row to be at the rightmost place.
The rest of the items should maintain equal distance between them.
How can I achieve this using Tailwind CSS? Normally with some conditions, I know how to do it, but is there any way to do it directly with Tailwind?
JSON response from API:
const ourPartners = [
{
id: "partner01",
pic: partner_01
},
{
id: "partner02",
pic: partner_02,
},
{
id: "partner03",
pic: partner_03,
},
{
id: "partner04",
pic: partner_04,
},
{
id: "partner05",
pic: partner_05,
},
{
id: "partner06",
pic: partner_06,
},
{
id: "partner07",
pic: partner_07,
},
{
id: "partner08",
pic: partner_08,
}
]
The code where I'm mapping over the array:
<div className="flex flex-row flex-wrap justify-between items-center">
{
ourPartners.map((item, index) => {
return (
<div className={`md:w-1/4 w-1/2 mb-10 ${(index % 4 !== 0 ? ('flex justify-center') : ('flex justify-start'))}`} key={item.id}>
<div className="w-32 object-contain">
<Image
alt="partner"
width="auto"
height="auto"
src={item.pic}
quality={100}
/>
</div>
</div>
)
})
}
</div>
EDITED:
I actually learned something new here too. You can use nth-child now with Tailwind.
const Partners = () => {
return (
<div className="grid md:grid-cols-4 grid-cols-2 gap-y-10 justify-between">
{ourPartners.map((item, index) => {
return (
<div
key={item.id}
className="odd:justify-self-start even:justify-self-end md:[&:nth-child(4n+2)]:justify-self-center md:[&:nth-child(4n+3)]:justify-self-center"
>
<div className="w-32">
<img alt="partner" width="auto" height="auto" src={item.pic} />
</div>
</div>
);
})}
</div>
);
};
export default Partners;

How to give hover class less precedence in TailwindCSS

I'm new to Tailwind, and I'm not sure if there's a way to solve this edge case. Here is the scenario:
We have different variants listed on the product page(for example different color tags). When you hover we are showing a faded border around the tag, and when you select the variant, the tag becomes active, and its border should get darker.
The problem:
Even when the user clicks on the tag to make it active, the user still sees hover still rather than the 'active' style.
These are the classes I'm using for now
<Tag
clssName={`flex rounded border border-gray-200 bg-white hover:border-gray-400 ${active && 'border-gray-700'}`}
...prop
/>
Now the question is if there's a way to override the hover styles on when the item is active. One way could be to remove the hover class when the item is active, but I was wording if there is a Tailwind way to fix it.
You can add different styles for active and non-active variants.
<Tag
clssName={`flex rounded border bg-white ${active && 'border-gray-700 hover:border-black'}`} ${!active && "border-gray-200 hover:border-gray-400"}
...prop
/>
Well you can use focus utility for this.
Below is the example you can see where button has different behaviour on hover and focus.
<script src="https://cdn.tailwindcss.com"></script>
<div class="p-10">
<button class="p-4 bg-pink-100 hover:bg-pink-300 focus:bg-red-500 focus:border-2 focus:border-red-700">Click </button>
</div>
You can achieve this with a ternary operator on className. By default we have border-gray-200 hover:border-gray-400 when state changes, we replace border-gray-700 instead of border-gray-200 hover:border-gray-400.
const App = () => {
const [active, setActive] = React.useState(false);
return (
<button onClick = {() => setActive(!active)}
className={`flex p-3 rounded border bg-white ${active ? 'border-gray-700' : 'border-gray-200 hover:border-gray-400'}`
}>
{active ? 'Active' : 'Inactive'}
</button>
);
};
const rootElement = document.getElementById('root');
ReactDOM.createRoot(rootElement).render( < App / > );
<script src="https://cdn.tailwindcss.com"></script>
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root" class="p-10"></div>

Google Photos API - Automatic Scaling?

I've been making a website to showcase my personal photography, and I've been getting my photos from Google Photos, using their features. (There's a medium post on how to do this)!
However, one problem I have been having is with scaling. I would like my images to be as quality as possible but also at a small enough size for them to fit into a grid on my page. However, when I use the built in parameters to do this (according to the docs) scaling is not preserved!
Example:
My page's code is as follows:
const axios = require('axios');
const Image = require('next/image');
const _ = require('lodash');
export default function Home({ pictures }) {
return (
<div>
<div className="hero min-h-screen" style={{ backgroundImage: 'url(https://lh3.googleusercontent.com/wwilPCF5L98Osl7_HVohVi34EP4SHUNKbxCe-fBooyNcTdvAWawcP3paqGxvAW3gCzXBl4aQOT_oxwYuXXaMG3ICM7cOkWJH6eYcozUqr9agShnjQu8kWFsPxtL7WD7H5sF5rkR9Vdk=w2048)' }}>
<div className="hero-overlay bg-opacity-60" />
<div className="hero-content text-center text-neutral-content">
<div className="max-w-md">
<h1 className="mb-5 text-5xl font-bold">Photography</h1>
<p className="mb-5">Aside from programming, I also love photography! </p>
</div>
</div>
</div>
<div className="flex flex-col flex-wrap m-10">
<div className="flex flex-row basis-1 flex-wrap m-4">
{
pictures.map(((x) => (
<img src={`${x}`} alt="Hello." key={`${x}?`} className=" flex-col flex-auto m-3 rounded-md shadow-md" />
)))
}
</div>
</div>
</div>
);
}
export async function getServerSideProps({ req, res }) {
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
const regex = /\["(https:\/\/lh3\.googleusercontent\.com\/[a-zA-Z0-9\-_]*)"/g;
function extractPhotos(content) {
const links = new Set();
let match;
while (match = regex.exec(content)) {
links.add(match[1]);
}
return Array.from(links);
}
async function getAlbum(id) {
const response = await axios.get(`https://photos.app.goo.gl/${id}`);
const photos = _.shuffle(extractPhotos(response.data));
return photos;
}
const pictures = await getAlbum('ZbGaHdrs62q5Jyrk8');
return {
props: { pictures }, // will be passed to the page component as props
};
}
How can I ensure the images are properly scaled? Is this even possible? Will I need some CSS trick? Thanks!

Dynamic classes with tailwind and clsx [duplicate]

I'm just learning React and Tailwind CSS and had a strange experience with CSS grid using Tailwind classes. I've made the buttons for a calculator, with the last Button spanning two columns:
App.js:
export default function App() {
return (
<div className="flex min-h-screen items-center justify-center bg-blue-400">
<Calculator />
</div>
);
}
Calculator.js
import { IoBackspaceOutline } from "react-icons/io5";
export const Calculator = () => {
return (
<div className="grid grid-cols-4 grid-rows-5 gap-2">
<Button>AC</Button>
<Button>
<IoBackspaceOutline size={26} />
</Button>
<Button>%</Button>
<Button>รท</Button>
<Button>7</Button>
<Button>8</Button>
<Button>9</Button>
<Button>x</Button>
<Button>4</Button>
<Button>5</Button>
<Button>6</Button>
<Button>-</Button>
<Button>1</Button>
<Button>2</Button>
<Button>3</Button>
<Button>+</Button>
<Button>0</Button>
<Button>.</Button>
<Button colSpan={2}>=</Button>
</div>
);
};
const Button = ({ colSpan = 1, rowSpan = 1, children }) => {
return (
<div
className={`col-span-${colSpan} row-span-${rowSpan} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
This doesn't work (tested in Chrome):
Now here comes the weird part. I replaced the returned JSX from the App component with HTML from a Tailwind tutorial and deleted it again.
<div className="bg-blue-400 text-blue-400 min-h-screen flex items-center justify-center">
<div className="grid grid-cols-3 gap-2">
<div className="col-span-2 bg-white p-10 rounded">1</div>
<div className="bg-white p-10 rounded">2</div>
<div className="row-span-3 bg-white p-10 rounded">3</div>
<div className="bg-white p-10 rounded">4</div>
<div className="bg-white p-10 rounded">5</div>
<div className="bg-white p-10 rounded">6</div>
<div className="col-span-2 bg-white p-10 rounded">7</div>
<div className="bg-white p-10 rounded">8</div>
<div className="bg-white p-10 rounded">9</div>
</div>
</div>
After I Ctrl-Z'd a bunch of times, so I had only the previous code, my button suddenly spans two columns as intended:
I checked to make sure that there were no changes in the code:
My friend even cloned my repo, followed the same steps and got the same result.
He suspects that it has something to do with the variable classNames in my Button component with regards to Tailwind's JIT compiler, but none of us can pinpoint the error.
Am I using variable CSS classes wrong?
This has been a WTF moment. What could be the reason for this?
The CSS file generated by Tailwind will only include classes that it recognizes when it scans your code, which means that dynamically generated classes (e.g. col-span-${colSpan}) will not be included.
If you only need to span 2 columns, you could pass boolean values which will trigger the addition of a full col-span-2 or row-span-2 utility class to be added:
const Button = ({ colSpan = false, rowSpan = false, children }) => {
return (
<div
className={`${colSpan ? 'col-span-2' : ''} ${rowSpan ? 'row-span-2' : ''} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
Otherwise, you could pass the values as classes to the Button component:
<Button className='col-span-2 row-span-1'>=</Button>
const Button = ({ className, children }) => {
return (
<div
className={`${className} bg-white p-3 rounded`}
>
<div className="flex items-center justify-center">{children}</div>
</div>
);
};
More information: https://tailwindcss.com/docs/content-configuration#dynamic-class-names
Another tricky solution that worked for me is to use variable with forced type of the possible className values (in typescript) like :
export type TTextSizeClass =
'text-xl' |
'text-2xl' |
'text-3xl' |
'text-4xl' |
'text-5xl' |
'text-6xl' |
'text-7xl' |
'text-8xl' |
'text-9xl'
;
...
const type : number = 6 ;
const textSizeClass : TTextSizeClass = type != 1 ? `text-${type}xl` : 'text-xl';
...
<div className={`font-semibold ${textSizeClass} ${className}`}>text</div>
As Ed Lucas said:
The CSS file generated by Tailwind will only include classes that it recognizes when it scans your code, which means that dynamically generated classes (e.g. col-span-${colSpan}) will not be included
But now could use safeListing
and
tailwind-safelist-generator package to "pregenerate" our dynamics styles.
With tailwind-safelist-generator, you can generate a safelist.txt file for your theme based on a set of patterns.
Tailwind's JIT mode scans your codebase for class names, and generates
CSS based on what it finds. If a class name is not listed explicitly,
like text-${error ? 'red' : 'green'}-500, Tailwind won't discover it.
To ensure these utilities are generated, you can maintain a file that
lists them explicitly, like a safelist.txt file in the root of your
project.

Resources