I'm building a site in Hugo. I'm using the hello friend ng theme. I want to add a svg image to the main site, but I want it to change depending on whether the selected theme is light or dark.
That theme toggle is handled by theme.js:
const theme = window.localStorage && window.localStorage.getItem("theme");
const themeToggle = document.querySelector(".theme-toggle");
const isDark = theme === "dark";
var metaThemeColor = document.querySelector("meta[name=theme-color]");
if (theme !== null) {
document.body.classList.toggle("dark-theme", isDark);
isDark
? metaThemeColor.setAttribute("content", "#252627")
: metaThemeColor.setAttribute("content", "#fafafa");
}
themeToggle.addEventListener("click", () => {
document.body.classList.toggle("dark-theme");
window.localStorage &&
window.localStorage.setItem(
"theme",
document.body.classList.contains("dark-theme") ? "dark" : "light"
);
document.body.classList.contains("dark-theme")
? metaThemeColor.setAttribute("content", "#252627")
: metaThemeColor.setAttribute("content", "#fafafa");
});
But i don't know how to access the variable it's using.
I didn't manage to do exactly that, but I found a workaround which satisfies my needs, so I'm posting it in case someone finds it valuable.
Rather than using <img> with links to different svg images, I decided to use <svg> and copy the content of my image there, since it's just xml. Then, I removed properties such as stroke and fill, wrapped that <svg> in a <div with a custom class that I then control by css.
I'm not sure if that's the best approach but it seems to work for me.
Related
I am developing a library of react components and asked an important question. How to correctly change styles (css) when changing the application theme.
Appeal to those who already have experience in this and who can advise their approach or solution.
Now I'll tell you how it works for me:
I have my own themeProvider wrapped in a Context with the ability to change the theme.
When the theme changes in the provider, the changeCssVariables method is called.
export const changeCssVariables = (theme) => {
const root = document.querySelector(':root');
// root.style.setProperty('--default-color', 'orange');
const cssVariables = [
'color',
'background',
'color-uibutton',
'background-uibutton',
'box-shadow-uibutton',
'color-disabled-uibutton',
'background-disabled-uibutton',
'background-uiradiobutton',
'color-uiinputext',
'color-notes-uiinputext',
'color-subsection',
'background-subsection',
'box-shadow-subsection',
'alt-subsection',
'on-subsection',
'off-subsection',
];
cssVariables.forEach(element => {
root.style.setProperty(
`--default-${element}`,
`var(--theme-${theme}-${element})`
);
})
}
What happens in general: it has a global CSS with variables default, light and dark themes. When changing the theme, a method is called that changes the default variable to the variable of the selected theme.
Example css:
--default-color: var(--theme-light-color);
/* Themes */
--theme-light-color: #000;
--theme-dark-color: #fff;
I don't like that you have to pull in all the css and change it this way. What is the solution?
Thanks for the help!
Found a solution in using styled-components package
Created my wrapper as ThemeProvider (Context)
I want to change the CSS property based on if a user has clicked on dark mode or light. I can get the a subscription going to capture the value every time the modes are switch but i want to be able to change certain property's in CSS when the different modes are clicked.
I'm using Angular 9
Any idea on how I can do this?
You can use BehaviourSubject RXJS to implement this feature. By default you can set to false and use light mode when ever User clicks the switch using next() method change the behaviour subject to true then dark etc. you have to subscribe to BehaviourSubject variable on init and handle the response.
You can have the reference at BehaviourSubject.
I personally implemented this dark mode feature in my project here is the link telivic.com the approach I have used in that site is as followed.Hope this works for you.
addDark(){
document.getElementById('nav').classList.add('dark');
document.getElementById('box').style.backgroundColor = "#35363A";
document.getElementsByTagName("BODY")[0].classList.add('dark');
document.getElementById('modal').style.backgroundColor ="bisque";
if(this.service.flag){
document.getElementById('opt').classList.add('dark');
}
document.getElementById('gen').style.color="white";
}
removeDark(){
document.getElementById('nav').classList.remove('dark');
document.getElementById('box').style.backgroundColor = "#fff";
document.getElementsByTagName("BODY")[0].classList.remove('dark');
document.getElementById('modal').style.backgroundColor ="white";
if(this.service.flag){
document.getElementById('opt').classList.remove('dark');
}
document.getElementById('gen').style.color="black";
}
/*Binded my switch to this function I have used service so that user dark/light mode preference can be processed in other pages also.*/
darkMode(){
this.dark = !this.dark;
this.userService.setMode();
if(this.dark){
this.addDark();
}else{
this.removeDark();
}
}
You can use styled-components. One example would be:
import styled from 'styled-components'
const ComponentName = styled.div`
color: ${props => props.mode === `dark` ? `white` : `black`};
`
And then in your render, do this:
<ComponentName mode={mode} />
You can add a variable in ts file,
modeType: string;
This will change as 'dark' or 'light' depending on user's selection.
In your html to change the css property use ngClass.
Or use
<div class="{{modeType == 'dark' ? 'dark-property': 'light-property'}}">
In your css file,
.dark-property{
add your "dark" css styles
}
.light-property{
add your "light" css styles
}
I am trying to dynamically assign a background color to a div using gatsby JS. Everything works well in development but after running a build, the page is static and does not change styles dynamically anymore (same with classes). Please take a quick look at this:
let currentTime = new Date().toTimeString()
return (
<React.Fragment>
<div
style={{
background: parseInt(currentTime[7]) % 2 == 0 ? "green" : "yellow",
}}
>
{currentTime}
</div>
</React.Fragment>
)
When built, the text renders the current Time correctly, however the style of the div stays static to whatever style gatsby assigned based on the time in the moment of building.
The UseCase for this will be to have a traffic light system to display if a shop is currently open (by comparing opening times to current time)
I am very thankful for any help. :)
Import { newGlobalColor } from "styled-components"
const changeDivColor = newGlobalColor`
body {
color : ${props => (props.theme ? "yellow" : "green")};
}`
let currentTime = parseInt( new Date().toTimeString()[7])%2;
let return (
<React.Fragment>
<changeDivColor theme={{currentTime}} />
<div>{currentTime}</div>
</React.Fragment>
)
According to docs, it should work with styled-components (https://www.gatsbyjs.org/docs/css-in-js/).
Additionally, you could try to do your magic in gatsby-browser config. (https://www.gatsbyjs.org/docs/api-files-gatsby-browser/)
UPDATE
Here's are some demos
contentEditable demo - requires double click for H1 to become editable
replace with input demo - adopts event.target styles but makes the UI 'twitch' when rendered
So I have some functional components, let's say:
component1.js
import React from 'react';
const component1 = props => (
<div>
<h1>Title</h1>
</div>
);
export { component1 };
They are variable. event.target could be anything with text, so paragraph, heading, anything. I'm trying to let users edit content inline by clicking on it, so I'll pass a function editMode to these functional components, that'll update parent state with editing info, let's say like this:
<h1 onClick={event => {editMode(event, props.name, props.title, 'title')}}>title</h1>
This changes parent local state to have all the necessary information to grab the value from redux, define a target etc. For this example, props.name is the name of the component, props.title is the value, and 'title' is object key in redux.
So I'll add something to my component1.js and make it look a bit like this:
import React from 'react';
const component1 = props => (
<div>
{props.editState === 'true' &&
<EditLayout
name={props.name}
target={props.target}
value={props.value}
onChange={event => someFunc(event)}
/>
}
<h1>Title</h1>
</div>
);
export { component1 };
Now this works fine, except it doesn't scale. EditLayout, in this case, will just return an input with correct value. What I need it to do is to adapt to whatever is being clicked, get font size, background, padding, margin, position. Am I doing this right? Every way I try, I run into huge issues:
Idea 1 - move EditLayout component outside of the functional component
Issue: positioning
So I'll move EditLayout to parent component that contains both component1.js and EditLayout. This will allow me to manipulate it from inside the functional component, without having to include it everywhere. I'll then grab coordinates and other important information from event.target like so:
const coords = event.target.getBoundingClientRect();
const offsetX = coords.left;
const offsetY = coords.top;
const childHeight = coords.height;
const childWidth = coords.width;
const childClass = event.target.className;
I'll then wrap the EditLayout to return a container which contains an input, and apply size/coordinates to the absolutely positioned container. This'll present an issue of input being offset by a random amount of pixels, depending on how big/where is the event.target.
Idea 2 - pass relevant computed styles to EditLayout
Issue: twitching on render, and I have to add EditLayout for every possible event.target there is, as well as condition its' render
So I'll grab all important computed styles like this:
const computedTarget = window.getComputedStyle(event.target);
const childMargins = computedTarget.marginBottom;
const childPaddings = computedTarget.padding;
const childFontSize = computedTarget.fontSize;
const childTextAlign = computedTarget.textAlign;
And pass it to component1.js, and then pass it to EditLayout component inside the component1.js. I'll then condition theevent.target to hide if it's being edited like this:
<h1 className={ props.target === 'title' ? 'd-none' : ''}>Title</h1>
And condition the EditLayout to show only if it's needed:
{props.target === 'title' && <EditLayout />}
In this example, clicking h1 will show the input, but the layout itself with twitch on render. Input will have the exact same margin and font size as the h1, or event.target, but it'll appear bigger and extend the layout. Demo:
Idea 3 - Use conditional contentEditable
Issue: Requires double click to enable, doesn't work in safari, doesn't let me preselect the value
This is the weirdest of them all. I figured it'd be pretty simple, do something like this inside the functional component render:
<h1 contentEditable={props.target === 'title'} onClick={event => props.setTarget(event)}>Title</h1>
However, I have to double click to enable it. I have no idea why, if I attach a console log every time onClick is fired, I'll get correct outputs, I'll get the correct target value as well. I've tried numerous ways, but it simply requires double click. Even attempted to handle this inside the functional component, as most of the stuff is handled by a parent component, doesn't make a difference.
I have oversimplified the examples, so it's safe to assume/understand the following:
I am passing props in a correct fashion, they aren't undefined
I am using bootstrap
I am using styled components, and EditLayout is a styled component
which accepts props and turns them into CSS like: font-size: ${props
=> props.fontSize};
The values should be correct, I am not manipulating anything I get back from getComputedStyle() or getBoundingClientRect()
I am keen on keeping my functional components functional, and easy to
add. Functional components, in this case, are simple HTML structures,
and I'd like to keep them as simple as possible
So there's a neat solution to contentEditable requiring two clicks instead of one, instead of binding onClick and passing it to enable contentEditable, simply keep contentEditable true and handle the change however you like. Here's a working h1 that doesn't require two clicks to enable contentEditable, unlike the one in the demo
<h1
className="display-4 text-center"
contentEditable
suppressContentEditableWarning
onBlur={event => updateValues(event)}
>
Title
</h1>
The available methods for trigger update could be onBlur or onInput.
I'm working on a Google Fonts plugin for WordPress and I try to have the same effect as the core WYSIWYG editor. Basically when you click on element (inside the Editor) with font class I want to get the class and then based on that reload the font family/style listbox in the Toolbar.
(I found couple of hacks here on SO like this one Proper Way Of Modifying Toolbar After Init in TinyMCE but nothing that works like the WP core example)
There is the same functionality when you click on P, H1, H3, H3 ... How they do it? Can you point me at least to the JS file in WordPress distro; I think I can figure it out if see the code.
Here is GIF that demonstrates what I'm talking about. Thanks in advance.
I found the solution and because it's not a hack, like the other ones I found on SO, I will post it in here and hopes it will help anyone else that's trying to do something similar.
First to access the button/listbox need to use onpostrender with a callback function.
editor.addButton( 'developry_google_font_family_button', {
type : 'listbox',
onpostrender : fontFamilyNodeChange,
value : '',
...
Next the callback function should look something like this:
function fontFamilyNodeChange() {
var listbox = this;
editor.on('NodeChange', function( e ) {
// This next part is specific for my needs but I will post it as an example.
var selected = [];
if ( $( e.element ).hasClass( 'mce-ga' ) ) { // this a class I add to all elements that have google fonts format
// Then I strip the classes from classList that I don't need and add the rest into an array (e.g ['roboto', '100'])
var gfont_options = $( e.element ).attr('class')
.replace('mce-ga', '')
.replace('mce-family-', '')
.replace('mce-weight-', '')
.trim()
.split(' ');
selected.push( gfont_options );
// At end I add the new value to listbox select[0][0] (e.g. 'roboto')
listbox.value(selected[0][0]);
}
});
}
And here is an example: