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.
Related
I'm trying to create a modifier for a button for when it's in a loading state.
Based on the documentation here, I added the following in my tailwind.config.js
// I assume this is included in tailwindcss
// and doesn't need to be installed separately
const plugin = require('tailwindcss/plugin')
module.exports = {
// ...
plugins: [
plugin(function({ addVariant }) {
addVariant('loading', '&:loading')
})
],
};
I assume this allows me to add a string of loading in the class such that it will apply those styles. This doesn't seem to work though, what am I doing wrong?
<!-- I assume this should be blue-600 -->
<button className="bg-blue-600 loading:bg-blue-100">
This is a normal button
</button>
<!-- I assume this should be blue-100 since it has className, "loading" -->
<button className="loading bg-blue-600 loading:bg-blue-100">
This is a loading button
</button>
& sign points to an element with THIS variant applied, so it should be translated in CSS as "this element with class .loading". In your example :loading will be translated as loading state which is not valid
So it should be addVariant('loading', '&.loading') not addVariant('loading', '&:loading')
const plugin = require('tailwindcss/plugin')
module.exports = {
plugins: [
plugin(function({ addVariant }) {
addVariant('loading', '&.loading') // here
})
],
};
I am trying to create a web component in Vue3. For this I use the Vue cli with the target library. Everything works as expected.
The problem is that I can't get any values from the props parameter of the setup function.
If I use the component as a Vue component the props work. As a web component i cant get any value from the props parameter
Its seams that the property wc-Test is not forwarded properly.
Does anyone have any ideas?
The Component Code
<template>
<a class="btn">
<slot></slot>
</a>
</template>
<script>
import {version } from "vue";
export default {
name: 'xn-button',
props: {
variant: {
default: 'normal',
type: String
}
},
setup(props, context) {
console.log(`Vue: ${version}`)
console.log(props)
console.log(props.variant)
},
}
</script>
<style></style>
Usage as Part of Vue Library:
<xn-button variant="Vue-Test">Test1</xn-button>
Usage as WebComponent:
<xn-button variant="wc-Test">Test2</xn-button>
Console output:
image
I am learning React via the course on Coursera. Here is the part where the tutor didn't mention how we can utilize the below "special" mechanism for CSS styling.
In my HeaderComponent.js file, the tutor pass the data as props to the Navbar pre-built component as below.
...
<Navbar light expand="md">
...
</Navbar>
...
Below is the documentation of Navbar properties(Link)
Navbar.propTypes = {
light: PropTypes.bool,
dark: PropTypes.bool,
fixed: PropTypes.string,
color: PropTypes.string,
role: PropTypes.string,
expand: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string])
// pass in custom element to use
}
In the App.css file
.navbar-light {
background-color: rgb(211, 81, 81);
}
To my surprise, this CSS selector works! From what I knew, to use a class in JSX we should use classNama="navbar-light" in the Navbar tag.
I would like to learn more about this, how can I utilize this mechanism?
For example, if I have a Card reactstrap component, can I use <Card body> then in CSS
.card-body{
background-color: rgb(211, 81, 81);
}
since the body prop is bool type as well as below
Card.propTypes = {
// Pass in a Component to override default element
tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
inverse: PropTypes.bool,
color: PropTypes.string,
body: PropTypes.bool,
className: PropTypes.string
};
to specify the color of the Card? I can't find any guide online on how to utilize this mechanism.
Thanks in advance.
reactstrap uses a utility called classnames which helps you construct a list of class names.
Example:
const classes = classnames("class1", "class2");
// `classes` is set to the string "class1 class2"
// Not so useful yet, but read on ...
It also lets you perform boolean checks to decide whether to include a class or not.
The following example creates a string of class names that contains the class card-base, and card-large if large is true.
const classes = classnames(
"card-base",
"card-large": large,
);
// If `large` evaluates to `true`, this will result in "card-base card-large"
large can be passed in as a prop. Here's a working example:
import classnames from "classnames";
const Card = ({ large, className }) => {
const classes = classnames(
className, // Keep the classes that are passed in with the `className` prop
"card-base",
"card-large": large,
);
return <div className={classes}>... rest of the component ...</div>
}
// Validate the props:
Card.propTypes = {
large: PropTypes.bool,
className: PropTypes.string // The user can still provide their own classes
};
Remember to install the classnames package first: npm install classnames.
PropTypes is used to validate the data a component receives. You add the propTypes property to a component with a list of props and their expected datatypes, and warnings will be shown in the JavaScript console if any validations fail. Prop types have nothing directly to do with CSS or class names. It's a safety mechanism that adds no additional behavior.
I'm currently experimenting with StencilJS to create some web components.
Now I know that there is <slot /> and named slots and all that stuff. Coming from React, I guess slot is similar to children in React. You can do a lot of stuff using children in React. Things I often did:
Check if any children are provided
Iterate over children to do something to each child (e.g. wrap it in a div with a class etc.)
How would you do that using slot/web components/stencilJS?
I can get the Host Element of my web component in Stencil using
#Element() hostElement: HTMLElement;
I use my component like
<my-custom-component>
<button>1</button>
<button>2</button>
<button>3</button>
</my-custom-component>
I want to render something like
render() {
return slottedChildren ?
<span>No Elements</span> :
<ul class="my-custom-component">
slottedChildren.map(child => <li class="my-custom-element>{child}</li>)
</ul>;
}
Kind regards
Using slots you don't need to put a condition in your render function. You can put the no children element (in your example the span) inside the slot element and if no children are provided to the slot it will fall back to it.
For example:
render() {
return (
<div>
<slot><span>no elements</span></slot>
</div>
);
}
Answering the comment you wrote - you can do such a thing but with some coding and not out of the box. Every slot element has an assignedNodes function. Using that knowledge and the understanding of Stencil component life cycle you can do something such as:
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
#Element() host: HTMLDivElement;
#State() children: Array<any> = [];
componentWillLoad() {
let slotted = this.host.shadowRoot.querySelector('slot') as HTMLSlotElement;
this.children = slotted.assignedNodes().filter((node) => { return node.nodeName !== '#text'; });
}
render() {
return (
<div>
<slot />
<ul>
{this.children.map(child => { return <li innerHTML={child.outerHTML}></li>; })}
</ul>
</div>
);
}
}
This is not an optimal solution and it will require that the style of the slot should have display set to none (cause you don't want to show it).
Also, it will only work with simple elements that only need rendering and not requiring events or anything else (cause it only uses them as html string and not as objects).
Thank you for the answer Gil.
I was thinking of something similar before (setting state etc. - because of timing issues that might come up). I didn't like the solution though, because you're then doing a state change within componentDidLoad, which will trigger another load just after the component did load. This seems dirty and unperfomant.
The little bit with innerHTML={child.outerHTML} helped me alot though.
It seems like you can also simply do:
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
#Element() host: HTMLDivElement;
render() {
return (
<div>
<ul>
{Array.from(this.host.children)
.map(child => <li innerHTML={child.outerHTML} />)}
</ul>
</div>
);
}
}
I thought you might run into timing issues, because during render() the child elements of the host have already been removed to make space for whatever render() returns. But since shadow-dom and light-dom coexist nicely within the host component, I guess there shouldn't be any issues.
I don't really know why you have to use innerHTML though. Coming from React I'm used to doing:
{Array.from(this.host.children)
.map(child => <li>{child}</li>)}
And I thought that is basic JSX syntax and that since Stencil is also using JSX I could do that, too. Doesn't work though. innerHTML does the trick for me. Thanks again.
EDIT: The timing issues I mentioned will appear if you're not using shadow-dom though. Some strange things start to happen an you'll end up with a lot of duplicate children.
Though you can do (might have side effects):
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'slotted-element',
styleUrl: 'slotted-element.css',
shadow: true
})
export class SlottedElement {
children: Element[];
#Element() host: HTMLDivElement;
componentWillLoad() {
this.children = Array.from(this.host.children);
this.host.innerHTML = '';
}
render() {
return (
<div>
<ul>
{this.children.map(child => <li innerHTML={child.outerHTML} />)}
</ul>
</div>
);
}
}
I am trying to add a class to an element depending on whether the user has clicked on a link. There is a similar question here but it is not working as I wanted it to be.
I created a component which has its own internal data object which has the property, isShownNavigation: false. So when a user clicks on the a I change isShownNavigation: true and expect my css class isClicked to be added. Alas that is not happening - isShownNavigation stays false in the component when I displayed it {{isShownNavigation}} but I can see in the console that my method is working when clicked.
I imported my header component to the App. Code is below.
Header Component
<template>
<header class="header">
<a
href="#"
v-bind:class="{isClicked: isShowNavigation}"
v-on:click="showNavigation">
Click
</a>
</header>
</template>
<script>
export default {
name: 'header-component',
methods: {
showNavigation: () => {
this.isShowNavigation = !this.isShowNavigation
}
},
data: () => {
return {
isShowNavigation: false
}
}
}
</script>
Application
<template>
<div id="app">
<header-component></header-component>
</div>
</template>
<script>
import HeaderComponent from './components/Header.vue'
export default {
name: 'app',
components: {
'header-component': HeaderComponent
}
}
</script>
I am using the pwa template from https://github.com/vuejs-templates/pwa.
Thanks.
Don't use fat arrow functions to define your methods, data, computed, etc. When you do, this will not be bound to the Vue. Try
export default {
name: 'header-component',
methods: {
showNavigation(){
this.isShowNavigation = !this.isShowNavigation
}
},
data(){
return {
isShowNavigation: false
}
}
}
See VueJS: why is “this” undefined? In this case, you could also really just get rid of the showNavigation method and set that value directly in your template if you wanted to.
<a
href="#"
v-bind:class="{isClicked: isShowNavigation}"
v-on:click="isShowNavigation = true">
Click
</a>
Finally, if/when you end up with more than one link in your header, you will want to have a clicked property associated with each link, or an active link property instead of one global clicked property.