Stitches theme not updating Radix UI's Dropdown.Content component - javascript

so I am currently working with the stitches styling library and the radix-ui library. I am using the theme feature (darkTheme/lightTheme).
It all works perfectly in updating all but one of the radix-ui components.
The Dropdown.Content doesn't get updated right and I don't understand why. I am currently passing in a $backgroundColor to the styles and instead of it changing depending on the theme, it sticks to the color from the default theme.
This is the code from my styles.ts file:
export const DropdownContent = styled(DropdownMenu.Content, {
background: '$backgroundColor',
border: '2px solid $borderColor',
)},
This is a snippet from my stitches.config.ts file:
export const {
styled,
css,
globalCss,
keyframes,
theme,
createTheme,
config,
} = createStitches({
theme: {
colors: {
backgroundColor: '#ffffff',
borderColor: '#E5E8EB',
},
space: {},
fonts: {},
},
media: {},
utils: {},
});
export const darkTheme = createTheme({
colors: {
backgroundColor: '#141416',
borderColor: '#353945',
}
});

This is because the content is rendered directly in the body tag through a React Portal, a default Radix behavior. Try setting open={true} on the Select and inspect the content elements to see for yourself.
You're most likely adding the dark theme class somewhere along your component tree, i.e. on your App component. That will not work for elements outside of that tree, for instance ones rendered in the body tag through a portal.
You need to apply the dark theme class to your body tag, like this:
const [darkTheme, setDarkTheme] = useState(true);
useLayoutEffect(() => {
const { classList } = document.body;
if (darkTheme) classList.add(darkThemeCssClass);
else classList.remove(darkThemeCssClass);
}, [darkTheme]);
This will allow the theme-dependent styling to be applied to all elements on the page, not just ones in your component tree.

Related

#next/font works everywhere except one specific component

The bounty expires in 5 days. Answers to this question are eligible for a +50 reputation bounty.
andrilla wants to draw more attention to this question:
I would love to see some ideas of what might be causing this and how I might fix it. Ideally I'd love an actual answer, but just some good ideas of what I might try would be super helpful.
#next/font
Uses Next.js with TypeScript and Tailwind CSS
This is my first time using the new #next/font package. I followed Next.js' tutorial, and it was easy to set up. I'm using both Inter and a custom local typeface called App Takeoff. To actually use both of these typefaces, I'm using Tailwind CSS, where Inter is connected to font-sans and App Takeoff is connected to font-display.
Everything works except in one spot
I have done plenty of testing between files, and for some reason both typefaces work everywhere except my Modal component.
Example
index.tsx
modal.tsx via index.tsx
As you can see, the typefaces work just fine when they aren't inside the modal, but as soon as they're in the modal they don't work.
Here's some relevant code:
// app.tsx
import '#/styles/globals.css'
import type { AppProps } from 'next/app'
import { Inter } from '#next/font/google'
const inter = Inter({
subsets: ['latin'],
variable: '--font-inter'
})
import localFont from '#next/font/local'
const appTakeoff = localFont({
src: [
{
path: '../fonts/app-takeoff/regular.otf',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.eot',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.woff2',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.woff',
weight: '400',
style: 'normal'
},
{
path: '../fonts/app-takeoff/regular.ttf',
weight: '400',
style: 'normal'
}
],
variable: '--font-app-takeoff'
})
const App = ({ Component, pageProps }: AppProps) => {
return (
<div className={`${inter.variable} font-sans ${appTakeoff.variable}`}>
<Component {...pageProps} />
</div>
)
}
export default App
// modal.tsx
import type { FunctionComponent } from 'react'
import type { Modal as ModalProps } from '#/typings/components'
import React, { useState } from 'react'
import { Fragment } from 'react'
import { Transition, Dialog } from '#headlessui/react'
const Modal: FunctionComponent<ModalProps> = ({ trigger, place = 'bottom', className, addClass, children }) => {
const [isOpen, setIsOpen] = useState(false),
openModal = () => setIsOpen(true),
closeModal = () => setIsOpen(false)
const Trigger = () => React.cloneElement(trigger, { onClick: openModal })
const enterFrom = place === 'center'
? '-translate-y-[calc(50%-12rem)]'
: 'translate-y-full sm:-translate-y-[calc(50%-12rem)]'
const mainPosition = place === 'center'
? '-translate-y-1/2'
: 'translate-y-0 sm:-translate-y-1/2'
const leaveTo = place === 'center'
? '-translate-y-[calc(50%+8rem)]'
: 'translate-y-full sm:-translate-y-[calc(50%+8rem)]'
return (
<>
<Trigger />
<Dialog open={isOpen} onClose={closeModal} className='z-50'>
{/* Backdrop */}
<div className='fixed inset-0 bg-zinc-200/50 dark:bg-zinc-900/50 backdrop-blur-sm cursor-pointer' aria-hidden='true' />
<Dialog.Panel
className={`
${className || `
fixed left-1/2
${
place === 'center'
? 'top-1/2 rounded-2xl'
: 'bottom-0 sm:bottom-auto sm:top-1/2 rounded-t-2xl xs:rounded-b-2xl'
}
bg-zinc-50 dark:bg-zinc-900
w-min
-translate-x-1/2
overflow-hidden
px-2 xs:px-6
shadow-3xl shadow-primary-400/10
`}
${addClass || ''}
`}
>
{children}
</Dialog.Panel>
<button
onClick={closeModal}
className='
fixed top-4 right-4
bg-primary-600 hover:bg-primary-400
rounded-full
h-7 w-7 desktop:hover:w-20
overflow-x-hidden
transition-[background-color_width] duration-300 ease-in-out
group/button
'
aria-role='button'
>
Close
</button>
</Dialog>
</>
)
}
export default Modal
I hope this information helps. Let me know if there's anything else that would be helpful to know.
Helpful Update
Thank you Jonathan Wieben for explanation of why this isn't working (See Explanation). The issue simply has to do with the scope of the applied styles, and Headless UI's usage of the React Portal component. If anyone has some ideas of how I can either change where the Portal is rendered or change the scope of the styles, that would be super helpful. Jonathan Wieben pointed out a way to do this, however—from my testing—it doesn't work with Tailwind CSS.
The Dialog component you are using renders in a portal (see here).
you typically want to render them as a sibling to the root-most node of your React application. That way you can rely on natural DOM ordering to ensure that their content is rendered on top of your existing application UI.
You can confirm this by inspecting your modal DOM element in your browser and seeing if it is indeed placed outside the div wrapper from your App component (I suspect it is).
If so, this is the explanation for why the modal content does not render with the expected font: It is rendered outside the component that defines the font.
To get around this, you could define your font on a higher level, e.g. in your head like described here: Next docs.

Fontawesome icons not accepting color props through react functional components via tailwindcss

My Problem
I have a project which requires icons everywhere. Instead of rendering a Fontawesome Icon in every script, I have a functional component which renders an icon when given props.
When calling the function, sometimes it doesn't accept the color prop. Only certain colors seem to be working, such as darkBlue, lightBlue, and green. Colors which haven't accepted the prop are defaulting to white.
I'm using Tailwindcss to inject classes into the components.
Tailwind Config
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
colors: {
dark: "#121212",
white: "#fff",
secondary: "#F0A500",
lightBlue: "#0EA5E9",
darkBlue: "#2563EB",
beige: "#FDBA74",
silver: "#9CA3AF",
red: "#DC2626",
green: "#10B981",
orange: "#F97316",
hotPink: "#EC4899",
purple: "#6D28D9",
yellow: "#FDE047",
},
extend: {
},
},
plugins: [],
};
FC: Icon Render
import React from "react";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
// color props must be passed as a string
function Icon({ name, color, scale }) {
return (
<FontAwesomeIcon
icon={name}
className={`text-${color}`}
size={scale}
/>
);
}
export default Icon;
Calling Icon Render
import React from "react";
import Button from "../../shared/components/Button";
import Typography from "../../shared/utilities/Typography";
import Mugshot from "../../shared/assets/mugshot.jpg";
import Icon from "../../shared/components/Icon";
import {
faGlobe,
faHandSpock,
faComment,
} from "#fortawesome/free-solid-svg-icons";
import Avatar from "../../shared/components/Avatar";
function example() {
return (
<section className="section" id="home-hero">
<Typography variant="label">Some text</Typography>
<Typography variant="h2">
Some text <Icon name={faHandSpock} color="beige" />
</Typography>
</section>
);
}
export default example;
What I've Tried / Fun Facts
No errors in the console.
Some colors may be preserved tailwind color names?
Tried changing color names in tailwind config
Tried changing hex values in tailwind config
Conclusion
Edit: Discovered an easier way:
<Icon name={faHandSpock} color="text-beige" /> // full classname
// remove partial className, pass in object
function Icon({ name, color, scale }) {
return (
<FontAwesomeIcon
icon={name}
className={color}
size={scale}
/>
);
}
export default Icon;
TailwindCSS doesn't allow you to generate classes dynamically. So when you use the following to generate the class…
className={`text-${color}`}
…TailwindCSS will not pick that up as a valid TailwindCSS class and therefore will not produce the necessary CSS.
Instead, you must include the full name of the class in your source code.
For this you can create any function which returns the required string like this:
function changeFAColor (color) {
if(color === dark) return "text-dark"
(color === white) return "text-white"
(color === secondary) return "text-secondary")
.
.
.
(color === purple) return "text-purple")
(color === yellow) return "text-yellow")
}
And use it in the component
<FontAwesomeIcon
icon={name}
className={`${changeFAcolor(color)}`}
size={scale}
/>
Tailwind generates a CSS file which contains only the classes that you've used in the project.
The problem you're experiencing is because Tailwind doesn't recognise the generated class you're applying in "FC: Icon Render". In particular, this line:
className={`text-${color}`}
To quote the documentation:
The most important implication of how Tailwind extracts class names is
that it will only find classes that exist as complete unbroken strings
in your source files.
If you use string interpolation or concatenate partial class names
together, Tailwind will not find them and therefore will not generate
the corresponding CSS:
https://tailwindcss.com/docs/content-configuration#class-detection-in-depth
To resolve your problem, either pass in the full class name instead of generating it or safelist all of your text-{color} classes in your config file.
Assign your colors to a variable:
const colors = {
dark: "#121212",
white: "#fff",
...
Pass them into your config for theme:
theme: {
colors,
. . .
Safelist your colors:
safelist: Object.keys(colors).map(color => `text-${color}`),

Chakra UI for React: change _focus borderColor not work

I set the default theme on Chakra UI for React with extendTheme().
When I try to change the Select component's style like this, the _focus style does not applied, while the color behaves as expected.
Refs
Chakra-UI Style Props
How to change the placeholder color globally?
Captions
Codes
index.ts
import { extendTheme } from '#chakra-ui/react';
import { Select } from './select';
export const theme = extendTheme({
colors: {
brand: '#008561',
brandLight: '#00b485',
},
components: {
Select,
},
});
select.ts
export const Select = {
parts: ['field'],
baseStyle: ({ colorMode }: any) => ({
field: {
color: 'brandLight',
_focus: {
borderColor: colorMode === 'dark' ? 'brandLight' : 'brand',
},
},
}),
sizes: {},
variants: {},
defaultProps: {},
};
To change the focus border color, you have to access the focusBorderColor selector,
This can either be set in the variant you want to change, or in the defaultProps selector of your component theme.
defaultProps: {
focusBorderColor: 'gray.500',
},
Another workaround I have seen, is to set the global shadows to a chakra color, note that any color you define, can also be accessed like the example below
const colors = { mycolor: "#F39C12" }
const shadows = {
outline: '0 0 0 3px var(--chakra-colors-mycolor-500)',
};
const theme = extendTheme({ config, colors, styles, components, shadows });
Workaround was found here: Chakra-UI Issue-663
Hope this helps your project
This worked for me.
_focusVisible={{
outline: "none",
}}

Emotion - pass style object to external library

I am using reactjs-popup, and one of it's props is contentStyle, which allow you to pass css-in-js object to style an internal div in the library.
however when I pass css object with #media in it, the library doesn't deal with it.
I wonder if there is a way to tell emotion to "translate" this object, or somehow wrap the library element, so it can treat the #media query as needed.
this is a code to demonstrate:
/** #jsx jsx */
import { jsx } from '#emotion/core';
import ReactJsPopup from 'reactjs-popup';
import { FC, PropsWithChildren } from 'react';
const Modal: FC<{}> = props => {
const style = {
padding: 0,
minHeight: '100%',
'#media (min-width: 576px)': {
minHeight: 'auto' // <----------- Doesn't work
}
}
return (
<ReactJsPopup contentStyle={style}>
{(close): JSX.Element => (
<div>
BODY
</div>
)}
</ReactJsPopup>
);
};
export default Modal;
Inline style objects currently do not support media queries.
The viable option here is to use the className prop to style the content. As the docs reads:
this class name will be merged with the component element: ex className='foo' means foo-arrow to style arrow, foo-overlay to style overlay and foo-content to style popup content
When using emotion, you can make sure that the selectors are unique using this property.
import { css } from "emotion";
<ReactJsPopup
className={css`
&-content {
color: red;
}
`}
>
</ReactJsPopup>
Note: The & is for the random classname that is going to be added by emotion. Followed by content that is added by the library

How do I pass complex styles (i.e. stylesheets) to React components as props?

I'm working on a large React project where each member of the team has been making components and stylesheets separately. I'm trying to find the common elements and re-write the code, creating re-usable components. At the moment each of these components has a stylesheet -- SCSS -- already written.
What I'd like to do is be able to pass styles to the component so that it can be customised (somewhat) in different locations. I know how to do this for the top-level HTML element in the component
export default class BoxWithSliderAndChevron extends Component {
render() {
const {
props: {
styles
},
} = this;
return (
<div className="BoxWithSliderAndChevron-main" style={styles}>
but as I understand it, these styles will only apply to this outer div? How can I pass styles such that I can re-style elements further down in the component's structure, using their classNames? As if I were passing a new stylesheet that would override the default stylesheet?
I suppose I could pass a number of style objects, but that seems cumbersome -- I'm wondering if there is a simpler way?
What you are trying to achieve kinda goes against the whole idea of inline styles (non-global, non-separated from implementation, etc), however you are right, passing a style prop and trying to apply it to a div will inmediatly result to only the parent having the styles applied.
One suggestion would be to merge the component styles with the props, ex:
import { StyleSheet } from 'react-native';
class Foo extends React.PureComponent {
render() {
return (
<div style={StyleSheet.merge([styles.parentStyle, styles.parentStyle])}>
<div style={StyleSheet.merge([styles.childStyle, styles.childStyle])}>
</div>
)
}
}
const styles = StyleSheet.create({
parentStyle: {
backgroundColor: 'red'
},
childStyle: {
backgroundColor: 'blue'
}
});
It is tedious work, but it is basically what you are trying to achieve, another approach is having theming globally applied:
import { StyleSheet } from 'react-native';
import { t } from '../theming'; // <- You switch themes on runtime
class Foo extends React.PureComponent {
render() {
return (
<div style={StyleSheet.merge([styles.parentStyle, t().parentStyle])}>
<div style={StyleSheet.merge([styles.childStyle, t().childStyle])}/>
</div>
)
}
}
const styles = StyleSheet.create({
parentStyle: {
backgroundColor: 'red'
},
childStyle: {
backgroundColor: 'blue'
}
});
/// Theming file would be something like:
// PSEUDO IMPLEMENTATION
import theme1 from 'theme1.json';
import theme2 from 'theme2.json';
availableThemes = {
theme1,
theme2
}
currentTheme = availableThemes.theme1
function setTheme(theme) {
currentTheme = availableThemes[theme]
}
export function t() {
return current theme
}

Categories