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>
Related
I am creating a directory of users in Next.js. The users are stored in Supabase. I need for all the users to be displayed in the index.js file, looping through them and showing them on a grid. This is working with getStaticProps, fetching the data and passing it as props profiles.
However, when clicking on each profile, it does redirect me to the [id].js page, but it appends /undefined to the url, rather than the id.
My file tree looks as follows:
pages
people
index.js
[id].js
export default function People({ profiles }) {
return (
<div className="body min-h-[90vh]">
<Head>
<title>People</title>
<link rel="icon" href="/logo" />
</Head>
<div
key={profiles.id}
profiles={profiles}
className="flex flex-col items-center py-16"
>
<div className="grid md:grid-flow-col md:grid-cols-3 xl:grid-cols-4 gap-8 lg:gap-12">
{profiles.map((profile) => (
<Link href={`/people/${profiles.id}`} key={profiles.id}>
<div
profile={profile}
id={profile.id}
className="flex flex-col w-full justify-center items-center p-8 shadow-md hover:shadow-lg"
>
{profile.avatar_url && (
<Image
src={profile.avatar_url}
alt="profile picture"
width={200}
height={200}
className="rounded-full"
object-fit="cover"
/>
)}
<h1 className="text-2xl pt-8 text-center">
{profile.full_name}
</h1>
<p>{profile.skills.skill}</p>
<button className="button w-full">See lessons</button>
</div>
</Link>
))}
</div>
</div>
</div>
);
}
export async function getStaticProps() {
const supabaseAdmin = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL || "",
process.env.SUPABASE_SERVICE_ROLE_KEY || ""
);
const { data } = await supabaseAdmin
.from("profiles")
.select("*, skills(skill)")
.order("id");
console.log(data);
return {
props: {
profiles: data,
},
};
}
Any ideas as to what I am doing wrong are highly appreciated.
Thanks.
I'm creating an app in Laravel 9 using Vue 3. I have a list that I would like to filter, based on the selection of a dropdown. To ensure the continuity of the selection, I store the value in a ref (gamekk). My code is working to a point: gamekk is updated with the selected value, my computed list filters on the value of gamekk, and the list is displayed. It's only there momentarily, though. Immediately after, gamekk is reset and the list disappears. I can confirm this by displaying gamekk in my page - the value appears then disappears. I don't reference gamekk anywhere else (I added the 'kk' to be sure').
My script is:
<script setup>
import AuthenticatedLayout from '#/Layouts/AuthenticatedLayout.vue';
import { Head, Link, usePage } from '#inertiajs/inertia-vue3';
import PrimaryButton from '#/Components/PrimaryButton.vue';
import Dropdown from '#/Components/Dropdown.vue';
import DropdownLink from '#/Components/DropdownLink.vue';
import { Inertia } from '#inertiajs/inertia';
import {ref, reactive, computed} from 'vue';
const props = defineProps({
categories: {
type: Object,
default: () => ({}),
},
games: {
type: Object,
default: () => ({})
},
languages: {
type: Object,
default: () => ({})
}
});
const gamekk = ref();
const langFil = ref();
const cats = computed( () => {
return usePage().props.value.categories.filter(cat =>
(cat.game == gamekk.value)
);
});
function filterCats(filter, name) {
switch(filter) {
case 'game':
gamekk.value = name;
break;
case 'language':
langFil.value = name;
break;
}
}
</script>
I filter the list using:
<div class="flex">
Game
<div class="hidden sm:flex sm:items-center sm:ml-6">
<!-- Settings Dropdown -->
<div class="ml-3 relative">
<Dropdown align="right" width="48">
<template #trigger>
<span class="inline-flex rounded-md">
<button type="button" class="inline-flex items-center px-3 border border-transparent text-lg leading-4 font-medium rounded-md text-gray-700 hover:text-gray-500 focus:outline-none transition ease-in-out duration-150">
<svg class="ml-2 -mr-0.5 h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd" />
</svg>
</button>
</span>
</template>
<template #content>
<DropdownLink v-for="game in $page.props.games" :key="game.id" #click="filterCats('game', game.name)">{{ game.name }}</DropdownLink>
</template>
</Dropdown>
</div>
</div>
</div>
I've used refs in this way before without problem. What am I missing that might reset the value automatically?
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.
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.
I tried to search for the answer but I am not getting anything to solve it.
I am loading my image using require.context as you can see in the code but it's not getting loaded. It used to work perfectly before in previous versions of react js. Now I am using react version 17.0.1. There are no errors in the console. If I import the image and use it in the src it works fine. I have also tried to change the images with some previous images used in previous projects (using react version 16.x.x) which are working fine there. I am creating react app using npx-create-react-app. Path to image is correct as in case of incorrect path "module named xxx not found error occurs".
Current behavior:
Image not showing up instead alt value is showing up.
Desired behavior:
Image should show up instead of alt value.
import React, { Component } from "react";
import commonStyles from "../css/common.module.css";
import loginStyles from "../css/login.module.css";
import { TextField, Button, Paper, Typography } from "#material-ui/core";
class Login extends Component {
state = {
userName: "",
password: "",
error: "",
};
render() {
const images = require.context("../images", true);
return (
<div
className={`${loginStyles.root} d-flex justify-content-center align-items-center ${commonStyles.bg}`}
>
<Paper
classes={{
root: `${commonStyles.paper} mt-2`,
}}
elevation={3}
>
<div className={`${loginStyles.child}`}>
<div className={`d-flex justify-content-center align-items-center`}>
<img
src={images(`./Shahmeer.png`)}
alt={`Shahmeer Avenue Logo`}
width="100"
height="100"
/>
</div>
<Typography
classes={{
root: `font-weight-bold`,
}}
variant="h5"
gutterBottom
>
Login
</Typography>
<form noValidate autoComplete="off">
<TextField
classes={{
root: `${commonStyles.textField}`,
}}
onChange={(e) => this.handleChange(e)}
id={"userName"}
label={"User Name"}
variant="outlined"
error={this.state.error ? true : false}
helperText={this.state.error}
value={this.state["userName"]}
/>
<TextField
classes={{
root: `${commonStyles.textField}`,
}}
onChange={(e) => this.handleChange(e)}
id={"password"}
label={"Password"}
variant="outlined"
error={this.state.error ? true : false}
helperText={this.state.error}
value={this.state["password"]}
/>
<div className={`w-100 d-flex justify-content-end mt-2`}>
<Button variant="contained" color="primary">
Login
</Button>
</div>
</form>
</div>
</Paper>
</div>
);
}
}
export default Login;
snapshot of browser:
You should use the default property for the images:
<img
src={images(`./Shahmeer.png`).default}
alt={`Shahmeer Avenue Logo`}
width="100"
height="100"
/>