Automatic splitting of long Tailwind strings in VSCode - javascript

I'm looking for a neat way to split this Mississippi River:
<input
value={props.profile.get('email') ?? ''}
placeholder="Email Address"
className="z-10 flex-shrink-0 w-12 h-12 px-3 py-1 rounded-lg border border-gray-300 placeholder-gray-300 focus:outline-none focus:ring-[5px] focus:ring-indigo-100 focus:border focus:border-indigo-400 hover:outline-none hover:ring-[5px] hover:ring-indigo-100 hover:border hover:border-indigo-200 transform-gpu transition-all duration-50"
onChange={e => props.updateProfile('email', e.currentTarget.value)}
/>
into something more digestible when scrolling through big repos.
Currently I'm using this approach for splitting strings:
<input
value={props.profile.get('email') ?? ''}
placeholder="Email Address"
className={classNames(
"z-10 block w-full h-12 px-3 py-1 rounded-lg border border-gray-300 placeholder-gray-300",
"focus:outline-none focus:ring-[5px] focus:ring-indigo-100 focus:border focus:border-indigo-400",
"hover:outline-none hover:ring-[5px] hover:ring-indigo-100 hover:border hover:border-indigo-200",
"transform-gpu transition-all duration-50",
)}
onChange={e => props.updateProfile('email', e.currentTarget.value)}
/>
But there should be more convenient way, so how do you handle long rules, fellow Tailwinders?

The only way out of this is using twin.macro, but even then the long rules will be pretty much the same.

I'd bundle these into logical groups if I were you and put them in a common file:
styles.js:
const form = 'block h-12 px-3 py-1 focus:outline-none focus: ....';
const button = 'rounded-lg border border-gray-300';
Your file then becomes:
import { form, button, input, grey } from 'styles';
...
classNames(`${form} ${button} ${input} ${grey}`)

Related

Expand Div smoothly in react with tailwind not happening

I am trying to animate a div containing my input elements which should expand smoothly, the text area at first is small and the other two input and buttons are hidden, when clicking the text area input and button are shown, however there is no transition, just a jump in size, I tried to use framer motion but it doesn't accept the tailwindcss values, putting the transitions class in the parent div didn't help either, can someone please help me with this
<div
ref={mainDivRef}
className='z-10 flex justify-center flex-wrap flex-col gap-2 p-2 bg-slate-300 bg-opacity-30 rounded-md'
onClick={() => handleClickInside()}>
<textarea
className='transition ease-in-out delay-150 resize-none rounded-md p-1 focus:outline-none'
value={mainText}
placeholder={"Ask me something"}
rows={rows}
cols={columns}
onChange={(e) => setmainText(e.target.value)} />
{extraElements &&
<div className='transition-all ease-in-out duration-150 delay-150 inline-flex gap-3 min-w-full'>
<input className='rounded-md p-1 grow focus:outline-none ' type="email" name="sender" id="senderText" placeholder='Your email' />
<button className='rounded-md p-1 bg-blue-200'>Send</button>
</div>
}
</div>
const handleClickInside = () => {
setExtraElements(true)
setRows(10)
setColumns(100)
props.blurBackground(true)
}
Since you're indirectly changing the textarea height/width by changing the rows property, the CSS transitions don't apply. Instead, you need to explicitly set the height of the text area.
For example, you can use CSS inline styles:
<textarea
className='transition ease-in-out delay-150 resize-none rounded-md p-1 focus:outline-none'
value={mainText}
placeholder={"Ask me something"}
style={{ height: `${rows * 10}px`, width: `${columns * 10}px` }}
onChange={(e) => setmainText(e.target.value)} />
As an alternative to framer-motion you can use the Transition component from the #headlessui/react package. It works perfectly with the tailwindcss classes for both entry and exit animations.

REACT onMouseEnter/onMouseLeave PopUp flickering loop

I'm building a button that shows a PopUp when hovered. To achieve this, I'm using the state "activeToolTip" that turns true "onMouseEnter" / false "onMouseLeave", and in turn renders the PopUp ({activeToolTip ? ( <>popUp<> : null)).
However, when hovering the button, the PopUp flickers between the onMouseEnter and onMouseLeave.
Can you help me understand the problem?
My code:
import { TooltipClose } from "../../../utils/svg";
export default function CustomTooltipsRed() {
const [activeToolTip, setActiveToolTip] = useState(false);
let TooltipTimeout;
const showToolTip = () => {
TooltipTimeout = setTimeout(() => {
setActiveToolTip(true);
}, 50);
};
const hideToolTip = () => {
setActiveToolTip(false);
clearInterval(TooltipTimeout);
};
return (
<div className="flex items-center justify-center ">
<button
className="bg-pink-500 text-white font-bold uppercase text-sm px-6 py-3 rounded shadow mr-1 mb-1 "
onMouseEnter={() => showToolTip()}
onMouseLeave={() => hideToolTip()}
>
Hover button
</button>
{activeToolTip ? (
<>
<div className="justify-center items-center flex fixed inset-0 outline-none focus:outline-none ">
<div className="relative w-auto my-6 mx-auto max-w-xl">
{/*content*/}
<div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-[#F43F5E] outline-none">
{/*header*/}
<div className="flex items-center justify-start p-2 rounded-t">
<div className="mr-2">
<i><TooltipClose /></i>
</div>
<h3 className="text-base font-semibold text-white">
P1 - Priority Issue
</h3>
</div>
{/*body*/}
<div className="relative p-6 flex-auto bg-[#1E293B] rounded-b-lg">
<p className="text-[#E2E8F0] leading-relaxed">
We have detected that your tenant has legacy protocols enabled.
Why is this an issue? Legacy protocols can not enforce multi factor
authentication and are considered a security risk.
</p>
Read More
</div>
</div>
</div>
</div>
<div className="inset-0 z-40"></div>
</>
) : null}
</div >
);
}
Add pointer-events-none to the tooltip.

How to map large data in nextjs when in viewport?

I want to make make a dropdown where a user can select an erc20 token from a tokenlist in Nextjs.
I tried a regular mapping function on the token list but then the site doesn't respond and is very slow because the tokenlist.json. I would like to render the data when in viewport. How can I achieve this?
I would like to make it fast, like in the token select modal in
Uniswap
I used nextjs Image and this loads the token image when in view but it is still slow because it needs to render the token name and symbol
This is how I fetch the tokenlist and render it:
import { Fragment, useEffect, useState } from 'react';
import { Combobox, Transition } from '#headlessui/react';
import { CheckIcon, SelectorIcon } from '#heroicons/react/solid';
import { PlusSmIcon } from '#heroicons/react/outline';
import axios from 'axios';
import tokensJson from '../web3/tokens.json';
import Image from 'next/image';
export default function SelectErc20() {
const [selected, setSelected] = useState(tokensJson.tokens[0]);
const [tokenlist, setTokenlist] = useState([]);
const [query, setQuery] = useState('');
const filteredTokens =
query === ''
? tokenlist
: tokenlist.filter((token) =>
token.name
.toLowerCase()
.replace(/\s+/g, '')
.includes(query.toLowerCase().replace(/\s+/g, ''))
);
useEffect(() => {
axios
.get('https://tokens.coingecko.com/uniswap/all.json')
.then((res) => {
setTokenlist(res.data.tokens);
})
.catch(setTokenlist(tokensJson.tokens));
}, []);
return (
<div className="flex items-center space-x-3">
<img src={selected.logoURI} alt="token" className="h-6 w-6" />
<div className="w-64">
<Combobox value={selected} onChange={setSelected}>
<div className="relative mt-1">
<div className="relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left shadow-md focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-emerald-300 sm:text-sm">
<Combobox.Input
className="w-full border-none py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 focus:ring-0"
displayValue={(token) => token.name}
onChange={(event) => setQuery(event.target.value)}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center pr-2">
<SelectorIcon
className="h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</Combobox.Button>
</div>
<Transition
as={Fragment}
leave="transition ease-in duration-100"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Combobox.Options className="absolute mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
<a
href="#"
className="relative mb-3 flex select-none items-center space-x-3 py-2 px-4 text-gray-700 hover:bg-neutral-100"
>
<PlusSmIcon className="h-5 w-5" />
<span>Add custom token</span>
</a>
{filteredTokens.length === 0 && query !== '' ? (
<div className="relative select-none py-2 px-4 text-gray-700">
<span>Nothing found..</span>
</div>
) : (
filteredTokens.map((token) => (
<Combobox.Option
key={token.address}
className={({ active }) =>
`relative cursor-default select-none py-2 pl-10 pr-4 ${
active ? 'bg-emerald-600 text-white' : 'text-gray-900'
}`
}
value={token}
>
{({ selected, active }) => (
<div className="flex items-center justify-between">
<div className="flex items-center truncate">
<Image
src={token.logoURI}
alt={token.name}
width="24"
height="24"
className="mr-3"
/>
<span
className={`block truncate ${
selected ? 'font-medium' : 'font-normal'
}`}
>
{token.name}
</span>
</div>
<span
className={`block text-xs text-gray-400 ${
selected ? 'font-medium' : 'font-normal'
} ${active ? 'text-white' : null}`}
>
{token.symbol}
</span>
{selected ? (
<span
className={`absolute inset-y-0 left-0 flex items-center pl-3 ${
active ? 'text-white' : 'text-emerald-600'
}`}
>
<CheckIcon
className="h-5 w-5"
aria-hidden="true"
/>
</span>
) : null}
</div>
)}
</Combobox.Option>
))
)}
</Combobox.Options>
</Transition>
</div>
</Combobox>
</div>
</div>
);
}
It's because you're rendering too much HTML node, your navigator can't paint it.
In order to do what you need, you must use what we call a 'virtual list'.
There are few libraries to virtualize, you're not the first.
Look at for exemple React Window

React - Hide parent's child element on blur event

I'm working on a custom input component. so when user clicks on the input element I want to show a dropdown that will contain default ten records and if user types some keyword in the input the dropdown will be updated with the results matching the keyword.
when user clicks on the input I want to show the dropdown and If they click out the container I want to hide the dropdown.
so I've added an onBlur event to the container and now if I click somewhere else it hides the dropdown but If I try to select a record It's also hidding the dropdown which is incorrect.
How can I hide the dropdown when the clicks outside of the input container? any suggestions?
export const ListBoxInput = ({ label, data, keyName }) => {
const [selected, setSelected] = useState({ key: null, value: null });
const [showDropdown, setShowDropdown] = useState(false);
return (
<div
className='flex items-center space-x-3'
onBlur={() => setShowDropdown(false)}
>
<label className='block text-sm font-medium text-gray-700'>{label}</label>
<div className='relative group'>
<input
type='text'
value={selected.key}
onFocus={() => setShowDropdown(true)}
onChange={(e) => setSelected({ key: e.target.value, value: null })}
disabled={!data.length}
placeholder={!data.length ? 'No data...' : ''}
className='w-full py-2 pl-3 pr-10 mt-1 text-left bg-white border border-gray-300 rounded-md shadow-sm disabled:opacity-50 focus:outline-none focus:ring-1 focus:ring-brand focus:border-brand sm:text-sm'
/>
{showDropdown && (
<div className='absolute z-10 w-full py-1 mt-3 overflow-auto text-base bg-white rounded-md shadow-lg max-h-56 ring-1 ring-black ring-opacity-5 focus:outline-none '>
{data.map((r) => (
<button
key={r.id}
type='button'
onClick={() => {
setSelected({ key: r[keyName], value: r.id });
setShowDropdown(false);
}}
className='block w-full py-2 pl-3 text-left hover:bg-gray-100 pr-9'
>
{r[keyName]}
</button>
))}
</div>
)}
</div>
</div>
);
};

printing out items from an array within an array of objects

I am trying to create some pills for a card display. I have an array of objects which I am mapping over to produce them on the page. Within the objects I have an array called tech which has something like tech: ['python', 'react.js'] etc.
something like this:
const data = [
{
imgUrl: "https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80",
description: 'oifonefpempfewpvpewmpew',
title: "Some project name",
tech: ["python", "react.js"],
},
{
imgUrl: "https://images.unsplash.com/photo-1551882547-ff40c63fe5fa?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80",
description: 'oifonefpempfewpvpewmpew',
title: "Some project name",
tech: ["python", "react.js"],
},
]
I have mapped over that array like so.
<div className="">
{data.map(tech => (
<span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2">
{tech.tech}
</span>
))}
</div>
Which it prints out the items, but it doesnt split them up into seperate pills just prints the array in one pill.
I am using Gatsby for my project
How can i split them up?
You need to have another .map();
<div className="">
{data.map(el => (
el.tech.map(currTech => (
<span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2">
{currTech}
</span>
))
))}
</div>
From your code, you can see that you're just outputting the array at {tech.tech}:
Try this, assuming you already created the component as Pill and data hold the array of project objects; I am also assuming this is a project component and the Pill component lives in it, then:
<div className="">
{data.map(project => (
<span className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2">
{project.tech.map(t => <Pill tech={t} />)}
</span>
))}
</div>

Categories