storybook darkmode not playing nice - html

So I am using storybook for my svelte + tailwind app, and I am now trying to make sure that I can toggle darkmode.
So for my tailwind.config.js I added this
module.exports = {
darkMode: "class",
and I installed this addon to storybook
https://github.com/hipstersmoothie/storybook-dark-mode
with this config .storybook/preview.js
export const parameters = {
darkMode: {
darkClass: "dark",
stylePreview: false,
},
And by looking in the DOM of the storybook iframe I can see that "dark" is applied to the body.
But when I create a component with this HTML
<div class="inline">
<div class="w-8 h-8 bg-blue-500 dark:bg-green-500" />
</div>
the box is always blue.
So I thought maybe purgecss was removing it, and so I added safelist: ["dark"] to it's options but without any luck.
So to make things more complicated I tested this component
<div class="inline">
<div class="w-8 h-8 bg-blue-500 dark:bg-green-500" />
</div>
<div class="inline dark">
<div class="w-8 h-8 bg-blue-500 dark:bg-green-500" />
</div>
and to my surprise, one of the boxes turned green.
Honestly, I am not entirely sure if this is because of svelte, storybook, tailwind, or the darkmode storybook plugin.
But I would really appreciate help if anyone has seen something similar

You could try ignoring purgecss when watching for storybook.
I am not sure about your exact setup but in my case I added a conditional in postcss.config.js for storybook to work correctly:
const isProduction =
!process.env.ROLLUP_WATCH &&
!process.env.LIVERELOAD &&
process.env.NODE_ENV !== 'development'
module.exports = {
plugins: [
require('tailwindcss'),
...(isProduction ? [purgecss] : [])
]
};
My .storybook/preview.js contains the following:
export const parameters = {
darkMode: {
stylePreview: true,
darkClass: 'dark',
lightClass: 'light',
}
}
The only thing which still doesn't work after this is the white text in dark mode, so I had to add .dark { color: white; } to my css.

I had this issue as well but it was because I defined a prefix of vc- in my tailwind.config.js file.
When I configured the addon https://github.com/hipstersmoothie/storybook-dark-mode, I used the class dark not vc-dark in .storybook/preview.js:
export const parameters = {
darkMode: {
dark: { ...themes.dark },
light: { ...themes.light },
darkClass: 'dark',
stylePreview: true
}
}
should be
export const parameters = {
darkMode: {
dark: { ...themes.dark },
light: { ...themes.light },
darkClass: 'vc-dark',
stylePreview: true
}
}
Not sure if you, (OP), have a prefix defined in your tailwind.config.js file but it's something to watch out for, if others are having the same issue.
Even with the prefix, you can still use the dark variant normally, just don't forget to use the prefix when referencing class names after the variant:
<div class="vc-bg-blue-500 dark:vc-bg-green-500" />

This happens because components are rendered inside of an iframe and storybook-dark-mode (SDM) only sets the class to "dark" on the body of the main document.
I verified this by inspecting and adding it manually. Assuming that you have darkMode: 'class' set in your tailwind config, you should see it work as soon as you set <body class="dark"> inside that iframe. This is why when OP wrapped it in a parent with "dark", it worked for that instance only.
First attempt
The question to me is how to get that class applied to the body of the iframe as well? Reading SDM docs, it implies that it would apply it to the app as well as the preview window, but that doesn't seem to happen for me.
Interestingly, there is an add-on called storybook-tailwind-dark-mode (STDM) which adds "dark" to the <html> of the iframed document, so that's good; but it's a separate button. You can have your components render in dark or light mode independent of dark mode on the app itself.
This is currently the only way it's working for me and I'd like to see/make a fork off one of these where it does both at once.
FWIW, without Tailwind, we were using a ThemeProvider from StyledComponents that leveraged useDarkMode() from SDM to then pass that down to all the StyledComponents (which we're migrating away from in favor of Tailwind). It would be nice to leverage that somehow.
Final answer
That previous paragraph gave me some inspiration. Storybook has decorators, which are basically functions that return components. We can wrap our stories with some HTML and give it a class based on useDarkMode().
Below is more or less what I ended up using and it's working great. One button to control dark mode, no need for an additional tailwind-specific dependency, and I'm still able to use my StyledComponent theming for the components that haven't been migrated yet.
.storybook/theme.js
import React from 'react'
import { ThemeProvider } from 'styled-components'
import { themeV2, GlobalStylesV2 } from 'propritary-design-library'
import { useDarkMode } from 'storybook-dark-mode'
import '../src/index.css'
const ThemeDecorator = storyFn => {
const mode = useDarkMode() ? 'dark' : 'light'
return (
<ThemeProvider theme={themeV2(mode)}>
<section className={mode}>
<GlobalStylesV2 />
{storyFn()}
</section>
</ThemeProvider>
)
}
export default ThemeDecorator
.storybook/preview.js
import { addDecorator } from '#storybook/react'
import ThemeDecorator from './theme'
addDecorator(ThemeDecorator)
export const parameters = {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}

Related

Anchor Tag in Next.js

I'm tryin got use an anchor tag in Next.js
I don't get any console errors when I set it up and click the link, but the page does not jump to the id tag.
This issue on github suggests that people need to figure out a lot of custom code to use anchors. That can't be right.
I have:
const links = [
{ label: 'Solutions', href: '#solutions', id: 'solutions' },
]
<NavLink.Desktop key={index} href={link.href} id={link.id}>
{link.label}
</NavLink.Desktop>
I get no errors, but the page does not jump to the label that has an id of 'solutions'.
Does anyone know how to solve this, or where to go for ideas on how - it can't be intented that complex custom code is required to use an anchor tag?
Chakra UI has a Link component
<Link href='https://chakra-ui.com' isExternal>
Chakra Design system <ExternalLinkIcon mx='2px' />
</Link>
If you use the regular anchor tags
<Link href="#anchor_one">Menu one</Link>
<Link href="#anchor_two">Menu two</Link>
Then you can add the id for the anchors to the sections you want to navigate into
<div id="anchor_one" />
<div id="anchor_two" />
This can be either pages or components.
I hope this helped a little bit.
As said by #juliomalves in the comments, you have to specify the id attribute on the element in which you wish to navigate to. Not on the anchor tag.
The id for the anchor should be set on the element you want to link to, not on the link itself.
The below code works for me in Next.js -
export default function Home() {
return (
<div>
Click
<section
style={{ marginTop: "1000px", marginBottom: "1000px" }}
id="section"
>
<h1>Test</h1>
</section>
</div>
);
}
Your code should look like this -
const links = [{ label: 'Solutions', href: '#solutions', id: 'solutions' }]
<NavLink.Desktop
key={index}
href={link.href}
// id={link.id} - This is wrong, as you're referring to the same element
>
{link.label}
</NavLink.Desktop>
// Rather set the id attribute in a separate div/section element
<div id={link.id}>
<h2>Solutions</h2>
</div>
maybe try
const links = [
{ label: 'Solutions', href: '#solutions', id: 'solutions' },
]
<NavLink.Desktop key={index} href={links[0].href} id={links[0].id}>
{link.label}
</NavLink.Desktop>
since you only have 1 element in the links array, if you have multiple just map through the array
It is possible to scroll to anchor programatically using Router.push:
import { useRouter } from 'next/router'
const Foo = () => {
const { push } = useRouter()
const handleClick = () => {
push("#blah")
}
return (
<div>
<button onClick={handleClick}>Scroll</button>
<div>Foo</div>
<div>Bar</div>
<div id="blah">Blah</div>
</div>
)
}
Next.js recognises that you are passing something that is not a link to a new page and will concat it (in the example #blah) to the end of the URL.
Have a read about Link from next/link its a built in feature.
https://nextjs.org/docs/api-reference/next/link
https://github.com/vercel/next.js/blob/canary/examples/hello-world/pages/index.js#L7

¿How to use or add two or more components within a tab section?

I have this tab system, which works perfectly for me. I am learning VueJs. I have a concern
regarding components and/or templates. My concern is:
Using any tab windows as an example,how do I add two components inside a tab,I mean
one of its windows or sections.
Any help,please?
This is my codepen:
https://codepen.io/luis-tavarez/pen/dyOeRwO
It would be worth checking out Vue's Single File Components.
Using the layout you already have in place, you could add additional components to a tab by doing the following. Let's assume the new component will be named Custom.
Add an additional router-view to your HTML:
<section class="mainBody">
<router-view name="header"><button>asaaasas</button></router-view>
<router-view name="content"></router-view>
<router-view name="custom"></router-view>
</section>
Add a new template block to Home, for example above line 43 where you've described the Header template:
const Custom = {
template: `
<section class="content">
<div>
<h1>Here is your new custom component</h1>
</div>
</secion>
`
};
Update Components in your Routes description:
{
path: "/three",
name: 'three',
components: {
header: Header,
content: contentThree,
custom: Custom
},
props: {
header: true,
content: false
}
},
And here it is rendered:

Angular/Typescript Text with routerLink

Updated Question for more Clarity:
Need to display some texts and links as innerHTML(data from service/DB) in the Angular HTML and when user clicks, it should go to Typescript and programmatically navigates by router.navigate
Also, How to add DomSanitizer from #ViewChild/ElementRef
Added all example in below code
Here is the updated stackblitz code
As shown in screenshot from angular.io some texts and some links
Sorry, I didn't realize you answered my comment. Angular routing is not secondary, if you don't use Angular modules you'll end up with just an HTML/CSS/Typescript application. you need at least the RouterModule for Angular to be able to use routing and hence, do what it's supposed to with the DOM.
First:
You are not importing RouterModule
solution:
imports: [
BrowserModule,
FormsModule,
RouterModule.forRoot([]) // this one
]
Second:
You can't bind Angular events through innerHTML property
fix:
Make use of #ViewChild directive to change your innerHTML property and manually bind to the click event, so change in your app.component.html from
<div id="box" [innerHTML]="shouldbedivcontent" ></div>
to
<div #box id="box"></div>
Now, in your app.component.ts, add a property to hold a reference to that "box" element so you can later make some changes to the dom with it:
#ViewChild('box') container: ElementRef;
Implement AfterViewInit, that hook is where you will be able to actually handle your container, if you try using it for example in OnInit you'd get undefined because that component's html is not in the dom yet.
export class AppComponent implements AfterViewInit {
and
ngAfterViewInit() {
this.container.nativeElement.innerHTML = this.shouldbedivcontent;
this.container.nativeElement.addEventListener('click',
() => this.goto('bar')
);
}
change shouldbedivcontent property from:
'1) this is a click
<a (click)="goto("bar")">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto("bar")">Click</a><br>
3)This only works with href
bar and test'
to
'1) this is a click
<a id="link_1">Click</a><br>
2)this is with routerlink
<a [routerLink]="" (click)="goto(\'bar\')">Click</a><br>
3)This only works with href
bar and test'
And even so you'd still not get the default anchor style unless you apply some styling yourself.
Third
You are not HTML sanitizing, which could be dangerous. read more here
MY SUGGESTION:
Seems like a lot to do for you and a lot to read for someone else working alongside you for something you could easily do like in the example below!
Move your html to your app.component.html:
<div id="box">
1) this is a click
<a (click)="goto('bar')">Click</a><br>
2)this is with routerlink
<a routerLink="" (click)="goto('bar')">Click</a><br>
3)This only works with href
bar and test
</div>
<p>Below is actual content</p>
You'll notice that everything works now, except the anchor without routerLink or href, because that's not a link.
EDIT:
Looking at the new stackblitz, i suggest a change of approach, binding to innerHTML is ok when working with plain text or even some simple html but not a great choice to bind events or routing logic.
Angular's Renderer2 provides with a bunch of methods to dyncamically add elements to the DOM. With that on the table, you just need a little effort to take that simple html you get from your backend and turn it into something like (paste this property in your code to test it along the rest of the code provided below):
public jsonHTML = [
{
tagName: '',
text: 'some text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'bar',
attributes: {
value: 'bar' // goto parameter
}
},
{
tagName: '',
text: ' some more text with click ',
attributes: {
}
},
{
tagName: 'a',
text: 'foo',
attributes: {
value: 'foo' // goto parameter
}
}
]
Once you have it, it's way easier to create all of those elements dynamically:
this is for the code in your Q1:
Inject Renderer2 with private r2: Renderer2
And replace the Q1 related code in AfterViewInit hook to:
const parent = this.r2.createElement('div'); // container div to our stuff
this.jsonHTML.forEach((element) => {
const attributes = Object.keys(element.attributes);
const el = element.tagName && this.r2.createElement(element.tagName);
const text = this.r2.createText(element.text);
if (!el) { // when there's no tag to create we just create text directly into the div.
this.r2.appendChild(
parent,
text
);
} else { // otherwise we create it inside <a></a>
this.r2.appendChild(
el,
text
);
this.r2.appendChild(
parent,
el
);
}
if (attributes.length > 0) {
attributes.forEach((name) => {
if (el) {
this.r2.setAttribute(el, name, element.attributes[name]); // just the value attribute for now
if (name === 'value') {
this.r2.listen(el, 'click', () => {
this.goto(element.attributes[name]); // event binding with property "value" as parameter to navigate to
})
}
} else {
throw new Error('no html tag specified as element...');
}
})
}
})
this.r2.appendChild(this.container.nativeElement, parent); // div added to the DOM
No html sanitizer needed and no need to use routerLink either just inject Router and navigate to the route you want! Make improvements to the code t make it fit your needs, it should be at least a good starting point
Good Luck!
You have a css problem.
looks like a link
<a [routerLink]="something"></a> looks like a link, because if you inspect the HTML it actually gets an href property added because of routerLink
<a (click)="goTo()"></a> does NOT look like a link, because there is no href
Chrome and Safari default user agents css will not style <a> without an href (haven't confirmed Firefox but I'm sure its likely). Same thing for frameworks like bootstrap.
Updated stackblitz with CSS moved to global, not app.css
https://stackblitz.com/edit/angular-ivy-kkgmkc?embed=1&file=src/styles.css
This will style all links as the default blue, or -webkit-link if that browser supports it. It should be in your global.css file if you want it to work through the whole app.
a {
color: rgb(0, 0, 238);
color: -webkit-link;
cursor: pointer;
text-decoration: underline;
}
this works perfectly for me :D
#Directive({
selector: "[linkify]",
})
// * Apply Angular Routing behavior, PreventDefault behavior
export class CustomLinkDirective {
#Input()
appStyle: boolean = true;
constructor(
private router: Router,
private ref: ElementRef,
#Inject(PLATFORM_ID) private platformId: Object
) {}
#HostListener("click", ["$event"])
onClick(e: any) {
e.preventDefault();
const href = e.target.getAttribute("href");
href && this.router.navigate([href]);
}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
this.ref.nativeElement.querySelectorAll("a").forEach((a: HTMLElement) => {
const href = a.getAttribute("href");
href &&
this.appStyle &&
a.classList.add("text-indigo-600", "hover:text-indigo-500");
});
}
}
}
HOW I USE IT
<p linkify
class="mt-3 text-lg text-gray-500 include-link"
[innerHtml]="apiSectionText"
></p>
result

What is the React equivalent of an Angular directive that only works on attributes?

For example you could have a directive in angular like so:
angular.module('app')
.directive('classy', function() {
return {
restrict: 'A',
link: function($scope, $el) {
$el.addClass('stay-classy');
}
}
}
And implement like so:
<div classy></div>
There doesn't seem to be an equivalent in React that I've seen after reading through most the docs and googling. I was hoping for something like:
...
render: function() {
return (
<MyComponent classy></MyComponent>
);
}
Is there something like that possible that I've been missing? Is there a different yet functionally similar equivalent? Or maybe this question just shows that I'm missing some part of the "React way" and I shouldn't ever want to do this. Thanks!
It will be helpful to consider what Angular and React are each doing "behind the scenes."
In your Angular example, when you write <div classy/></div> you're saying "render a DIV element and then attach to it the behaviors defined by the classy directive.
In your React example, when you write <MyComponent classy></MyComponent>, you're saying, "create an instance of MyComponent and pass it the props { classy: true }. The transpiler (Babel or whathaveyou) will turn it into the following JavaScript:
React.createElement(MyComponent, { classy: true });
So the answer to your question is that you can't write <MyComponent classy></MyComponent> because MyComponent component doesn't know what to do with the classy prop. In React, you might write something like this instead:
class ClassyDiv extends React.Component {
render() {
const { className, ...rest } = this.props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
}
}
This works because we know the React.DOM.div component (like most DOM components) knows what to do with the className prop.
Since React 0.14 we can express something like this more simply, as a "pure" stateless functional component, i.e. a function that accepts props and returns the rendered result:
function AlsoClassyDiv(props) {
const { className, ...rest } = props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
};
You can see both approaches in action in the below snippet.
class ClassyDiv extends React.Component {
render() {
const { className, ...rest } = this.props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
}
}
function AlsoClassyDiv({ className, ...props }) {
return <div className={`${className || ''} stay-classy`} {...props}/>;
};
ReactDOM.render(
<div id="container">
<div>Regular div</div>
<ClassyDiv>ClassyDiv!</ClassyDiv>
<AlsoClassyDiv>AlsoClassyDiv!</AlsoClassyDiv>
</div>,
document.body
);
.stay-classy { font: bold 3em Helvetica; text-shadow: 4px 4px 2px #aaa; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
One way you could implement similar behavior is using React class mixins
A great example of a useful directive in angular is
Target
The smoothScroll directive would intercept the click event then use window scroll or jquery scrollTo to apply all manner of animation.
Anywhere in the html one could then simply use the directive powered class name.
This sort of thing is not available in React. To do it in React you would have to create a special link component to use instead of:
<a> like ASmooth....
I was looking to find a way to reproduce the directive system for applying style or play with the component.
You can create a component that play with children and then render them :
function TextCenter(props) {
// Iterates over children and clone it with custom props
const children = React.Children.map(
props.children,
(child) => React.cloneElement(child, { className: 'text-center' }
)
// Render the children
return <>{children}</>;
}
function MyComponent() {
return (
<TextCenter>
<div>
<h1>Hello centered world</h1>
<p>Yessss</p>
</div>
</TextCenter>
)
}
Here is a more powerfull example for responsive text alignement :
interface Props extends Breakpoints<'start' | 'center' | 'end'>{}
export const TextAlign: FunctionComponent<Props> = (props) => {
const className = generateClassName('text', props);
const children = React.Children.map(props.children, child => React.cloneElement(child as ReactElement, { className }))
return (
<>
{children}
</>
)
}
export const MyComponent: FunctionComponent<Props> = (props) => {
return (
<div>
<TextCenter xs="center" md="start">
<h1>I am centered on mobile but not on desktop</h1>
</TextCenter>
</div>
)
}
There are two problems with this solution, when the children is a component, it must also have the prop className and it also makes the HTML less clean as it adds a level in hierarchy.
Look my friend i didn't get you well but long story short, angularJS directives is actually a component. So the idea behind angularJs directive is to create component that has its own scope data and it's own method to operate on it. I was thinking the same way you did and found your post here and i couldn't find an answer for that. But thanks for working experience, i thought about it and know how to do it.
I wanted to add an edit button for each link item in a list to toggle the edit form for each one only so each ListItem should be a stand alone component, that way i have standalone state for each one and i toggle it on & off.

Paper-Button always as upper case

I am using Paper-Button but I am facing issue that the button text always gets capitalized instead or normal case.
I do not see any CSS or Javascript property being applied to make it upper case.
How should I resolve this problem?
I had the same issue and I solved the problem via adjusting the default theme. Add the following code to a file (name of your choice).js
import { createMuiTheme } from '#material-ui/core/styles';
const theme = createMuiTheme({
typography: {
button: {
textTransform: 'none'
}
}
});
export default theme;
You can then add the file to your app in index.js. I named it theme.js:
...
import theme from './theme';
...
const app = () => (
<ThemeProvider theme={theme}>
<CssBaseline />
<App />
</ThemeProvider>
);
ReactDOM.render(app, document.getElementById('root'));
As was mentioned in the comments above, the material design spec for buttons specifies that the text should be uppercase, but you can easily override its CSS property:
paper-button {
text-transform: none;
}
Inspired by the the CSS style above here is the inline styling for localized Button text transformation -
import {Button} from '#material-ui/core';
// Begin Component Logic
<Button style={{textTransform: 'none'}}>
Hello World
</Button>
// End Component Logic
If you use Mui 5 then you can use the sx syntax
<Button sx={{textTransform: "none"}}/>