Add and use clsx (classnames alternative) (#2249)

Co-authored-by: David Luzar <luzar.david@gmail.com>
This commit is contained in:
Danila 2020-10-19 17:14:28 +03:00 committed by GitHub
parent 1484c5a63b
commit b50c54f855
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 108 additions and 72 deletions

5
package-lock.json generated
View File

@ -5399,6 +5399,11 @@
"mimic-response": "^1.0.0" "mimic-response": "^1.0.0"
} }
}, },
"clsx": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz",
"integrity": "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="
},
"co": { "co": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",

View File

@ -29,6 +29,7 @@
"@types/react-dom": "16.9.8", "@types/react-dom": "16.9.8",
"@types/socket.io-client": "1.4.34", "@types/socket.io-client": "1.4.34",
"browser-nativefs": "0.11.0", "browser-nativefs": "0.11.0",
"clsx": "1.1.1",
"firebase": "7.23.0", "firebase": "7.23.0",
"i18next-browser-languagedetector": "6.0.1", "i18next-browser-languagedetector": "6.0.1",
"lodash.throttle": "4.1.1", "lodash.throttle": "4.1.1",

View File

@ -9,7 +9,7 @@ type AvatarProps = {
}; };
export const Avatar = ({ children, color, onClick }: AvatarProps) => ( export const Avatar = ({ children, color, onClick }: AvatarProps) => (
<div className={`Avatar`} style={{ background: color }} onClick={onClick}> <div className="Avatar" style={{ background: color }} onClick={onClick}>
{children} {children}
</div> </div>
); );

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import clsx from "clsx";
export const ButtonSelect = <T extends Object>({ export const ButtonSelect = <T extends Object>({
options, options,
@ -15,7 +16,7 @@ export const ButtonSelect = <T extends Object>({
{options.map((option) => ( {options.map((option) => (
<label <label
key={option.text} key={option.text}
className={value === option.value ? "active" : ""} className={clsx({ active: value === option.value })}
> >
<input <input
type="radio" type="radio"

View File

@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { Popover } from "./Popover";
import { render, unmountComponentAtNode } from "react-dom"; import { render, unmountComponentAtNode } from "react-dom";
import clsx from "clsx";
import { Popover } from "./Popover";
import "./ContextMenu.scss"; import "./ContextMenu.scss";
@ -20,11 +21,13 @@ const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => {
const isDarkTheme = !!document const isDarkTheme = !!document
.querySelector(".excalidraw") .querySelector(".excalidraw")
?.classList.contains("Appearance_dark"); ?.classList.contains("Appearance_dark");
const wrapperClasses = `excalidraw ${
isDarkTheme ? "Appearance_dark Appearance_dark-background-none" : ""
}`;
return ( return (
<div className={wrapperClasses}> <div
className={clsx("excalidraw", {
"Appearance_dark Appearance_dark-background-none": isDarkTheme,
})}
>
<Popover <Popover
onCloseRequest={onCloseRequest} onCloseRequest={onCloseRequest}
top={top} top={top}

View File

@ -1,4 +1,5 @@
import React, { useEffect, useRef } from "react"; import React, { useEffect, useRef } from "react";
import clsx from "clsx";
import { Modal } from "./Modal"; import { Modal } from "./Modal";
import { Island } from "./Island"; import { Island } from "./Island";
import { t } from "../i18n"; import { t } from "../i18n";
@ -68,7 +69,7 @@ export const Dialog = (props: {
return ( return (
<Modal <Modal
className={`${props.className ?? ""} Dialog`} className={clsx("Dialog", props.className)}
labelledBy="dialog-title" labelledBy="dialog-title"
maxWidth={props.maxWidth} maxWidth={props.maxWidth}
onCloseRequest={props.onCloseRequest} onCloseRequest={props.onCloseRequest}

View File

@ -1,6 +1,7 @@
import "./FixedSideContainer.scss"; import "./FixedSideContainer.scss";
import React from "react"; import React from "react";
import clsx from "clsx";
type FixedSideContainerProps = { type FixedSideContainerProps = {
children: React.ReactNode; children: React.ReactNode;
@ -14,7 +15,11 @@ export const FixedSideContainer = ({
className, className,
}: FixedSideContainerProps) => ( }: FixedSideContainerProps) => (
<div <div
className={`FixedSideContainer FixedSideContainer_side_${side} ${className}`} className={clsx(
"FixedSideContainer",
`FixedSideContainer_side_${side}`,
className,
)}
> >
{children} {children}
</div> </div>

View File

@ -1,6 +1,7 @@
import "./Island.scss"; import "./Island.scss";
import React from "react"; import React from "react";
import clsx from "clsx";
type IslandProps = { type IslandProps = {
children: React.ReactNode; children: React.ReactNode;
@ -12,7 +13,7 @@ type IslandProps = {
export const Island = React.forwardRef<HTMLDivElement, IslandProps>( export const Island = React.forwardRef<HTMLDivElement, IslandProps>(
({ children, padding, className, style }, ref) => ( ({ children, padding, className, style }, ref) => (
<div <div
className={`${className ?? ""} Island`} className={clsx("Island", className)}
style={{ "--padding": padding, ...style } as React.CSSProperties} style={{ "--padding": padding, ...style } as React.CSSProperties}
ref={ref} ref={ref}
> >

View File

@ -1,4 +1,5 @@
import React from "react"; import React from "react";
import clsx from "clsx";
import * as i18n from "../i18n"; import * as i18n from "../i18n";
export const LanguageList = ({ export const LanguageList = ({
@ -14,9 +15,9 @@ export const LanguageList = ({
}) => ( }) => (
<React.Fragment> <React.Fragment>
<select <select
className={`dropdown-select dropdown-select__language${ className={clsx("dropdown-select dropdown-select__language", {
floating ? " dropdown-select--floating" : "" "dropdown-select--floating": floating,
}`} })}
onChange={({ target }) => onChange(target.value)} onChange={({ target }) => onChange(target.value)}
value={currentLanguage} value={currentLanguage}
aria-label={i18n.t("buttons.selectLanguage")} aria-label={i18n.t("buttons.selectLanguage")}

View File

@ -44,6 +44,7 @@ import { ToolButton } from "./ToolButton";
import { saveLibraryAsJSON, importLibraryFromJSON } from "../data/json"; import { saveLibraryAsJSON, importLibraryFromJSON } from "../data/json";
import { muteFSAbortError } from "../utils"; import { muteFSAbortError } from "../utils";
import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle"; import { BackgroundPickerAndDarkModeToggle } from "./BackgroundPickerAndDarkModeToggle";
import clsx from "clsx";
interface LayerUIProps { interface LayerUIProps {
actionManager: ActionManager; actionManager: ActionManager;
@ -294,9 +295,9 @@ const LayerUI = ({
// TODO: Extend tooltip component and use here. // TODO: Extend tooltip component and use here.
const renderEncryptedIcon = () => ( const renderEncryptedIcon = () => (
<a <a
className={`encrypted-icon tooltip zen-mode-visibility ${ className={clsx("encrypted-icon tooltip zen-mode-visibility", {
zenModeEnabled ? "zen-mode-visibility--hidden" : "" "zen-mode-visibility--hidden": zenModeEnabled,
}`} })}
href="https://blog.excalidraw.com/end-to-end-encryption/" href="https://blog.excalidraw.com/end-to-end-encryption/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@ -362,7 +363,9 @@ const LayerUI = ({
const renderCanvasActions = () => ( const renderCanvasActions = () => (
<Section <Section
heading="canvasActions" heading="canvasActions"
className={`zen-mode-transition ${zenModeEnabled && "transition-left"}`} className={clsx("zen-mode-transition", {
"transition-left": zenModeEnabled,
})}
> >
{/* the zIndex ensures this menu has higher stacking order, {/* the zIndex ensures this menu has higher stacking order,
see https://github.com/excalidraw/excalidraw/pull/1445 */} see https://github.com/excalidraw/excalidraw/pull/1445 */}
@ -399,7 +402,9 @@ const LayerUI = ({
const renderSelectedShapeActions = () => ( const renderSelectedShapeActions = () => (
<Section <Section
heading="selectedShapeActions" heading="selectedShapeActions"
className={`zen-mode-transition ${zenModeEnabled && "transition-left"}`} className={clsx("zen-mode-transition", {
"transition-left": zenModeEnabled,
})}
> >
<Island className={CLASSES.SHAPE_ACTIONS_MENU} padding={2}> <Island className={CLASSES.SHAPE_ACTIONS_MENU} padding={2}>
<SelectedShapeActions <SelectedShapeActions
@ -447,7 +452,7 @@ const LayerUI = ({
<div className="App-menu App-menu_top"> <div className="App-menu App-menu_top">
<Stack.Col <Stack.Col
gap={4} gap={4}
className={zenModeEnabled && "disable-pointerEvents"} className={clsx({ "disable-pointerEvents": zenModeEnabled })}
> >
{renderCanvasActions()} {renderCanvasActions()}
{shouldRenderSelectedShapeActions && renderSelectedShapeActions()} {shouldRenderSelectedShapeActions && renderSelectedShapeActions()}
@ -456,7 +461,10 @@ const LayerUI = ({
{(heading) => ( {(heading) => (
<Stack.Col gap={4} align="start"> <Stack.Col gap={4} align="start">
<Stack.Row gap={1}> <Stack.Row gap={1}>
<Island padding={1} className={zenModeEnabled && "zen-mode"}> <Island
padding={1}
className={clsx({ "zen-mode": zenModeEnabled })}
>
<HintViewer appState={appState} elements={elements} /> <HintViewer appState={appState} elements={elements} />
{heading} {heading}
<Stack.Row gap={1}> <Stack.Row gap={1}>
@ -479,9 +487,9 @@ const LayerUI = ({
)} )}
</Section> </Section>
<UserList <UserList
className={`zen-mode-transition ${ className={clsx("zen-mode-transition", {
zenModeEnabled && "transition-right" "transition-right": zenModeEnabled,
}`} })}
> >
{Array.from(appState.collaborators) {Array.from(appState.collaborators)
// Collaborator is either not initialized or is actually the current user. // Collaborator is either not initialized or is actually the current user.
@ -503,9 +511,9 @@ const LayerUI = ({
const renderBottomAppMenu = () => { const renderBottomAppMenu = () => {
return ( return (
<div <div
className={`App-menu App-menu_bottom zen-mode-transition ${ className={clsx("App-menu App-menu_bottom zen-mode-transition", {
zenModeEnabled && "App-menu_bottom--transition-left" "App-menu_bottom--transition-left": zenModeEnabled,
}`} })}
> >
<Stack.Col gap={2}> <Stack.Col gap={2}>
<Section heading="canvasActions"> <Section heading="canvasActions">
@ -525,9 +533,9 @@ const LayerUI = ({
const renderFooter = () => ( const renderFooter = () => (
<footer role="contentinfo" className="layer-ui__wrapper__footer"> <footer role="contentinfo" className="layer-ui__wrapper__footer">
<div <div
className={`zen-mode-transition ${ className={clsx("zen-mode-transition", {
zenModeEnabled && "transition-right disable-pointerEvents" "transition-right disable-pointerEvents": zenModeEnabled,
}`} })}
> >
<LanguageList <LanguageList
onChange={async (lng) => { onChange={async (lng) => {
@ -540,9 +548,9 @@ const LayerUI = ({
{actionManager.renderAction("toggleShortcuts")} {actionManager.renderAction("toggleShortcuts")}
</div> </div>
<button <button
className={`disable-zen-mode ${ className={clsx("disable-zen-mode", {
zenModeEnabled && "disable-zen-mode--visible" "disable-zen-mode--visible": zenModeEnabled,
}`} })}
onClick={toggleZenMode} onClick={toggleZenMode}
> >
{t("buttons.exitZenMode")} {t("buttons.exitZenMode")}
@ -594,9 +602,12 @@ const LayerUI = ({
{renderBottomAppMenu()} {renderBottomAppMenu()}
{ {
<aside <aside
className={`layer-ui__wrapper__github-corner zen-mode-transition ${ className={clsx(
zenModeEnabled && "transition-right" "layer-ui__wrapper__github-corner zen-mode-transition",
}`} {
"transition-right": zenModeEnabled,
},
)}
> >
<GitHubCorner appearance={appState.appearance} /> <GitHubCorner appearance={appState.appearance} />
</aside> </aside>

View File

@ -1,4 +1,5 @@
import React, { useRef, useEffect, useState } from "react"; import React, { useRef, useEffect, useState } from "react";
import clsx from "clsx";
import { exportToSvg } from "../scene/export"; import { exportToSvg } from "../scene/export";
import { close } from "../components/icons"; import { close } from "../components/icons";
@ -63,16 +64,16 @@ export const LibraryUnit = ({
return ( return (
<div <div
className={`library-unit ${ className={clsx("library-unit", {
elements || pendingElements ? "library-unit__active" : "" "library-unit__active": elements || pendingElements,
}`} })}
onMouseEnter={() => setIsHovered(true)} onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)} onMouseLeave={() => setIsHovered(false)}
> >
<div <div
className={`library-unit__dragger ${ className={clsx("library-unit__dragger", {
!!pendingElements ? "library-unit__pulse" : "" "library-unit__pulse": !!pendingElements,
}`} })}
ref={ref} ref={ref}
draggable={!!elements} draggable={!!elements}
onClick={!!elements || !!pendingElements ? onClick : undefined} onClick={!!elements || !!pendingElements ? onClick : undefined}

View File

@ -1,6 +1,7 @@
import "./ToolIcon.scss"; import "./ToolIcon.scss";
import React from "react"; import React from "react";
import clsx from "clsx";
type LockIconSize = "s" | "m"; type LockIconSize = "s" | "m";
@ -41,13 +42,15 @@ const ICONS = {
}; };
export const LockIcon = (props: LockIconProps) => { export const LockIcon = (props: LockIconProps) => {
const sizeCn = `ToolIcon_size_${props.size || DEFAULT_SIZE}`;
return ( return (
<label <label
className={`ToolIcon ToolIcon__lock ToolIcon_type_floating ${sizeCn} zen-mode-visibility ${ className={clsx(
props.zenModeEnabled ? "zen-mode-visibility--hidden" : "" "ToolIcon ToolIcon__lock ToolIcon_type_floating zen-mode-visibility",
}`} `ToolIcon_size_${props.size || DEFAULT_SIZE}`,
{
"zen-mode-visibility--hidden": props.zenModeEnabled,
},
)}
title={`${props.title} — Q`} title={`${props.title} — Q`}
> >
<input <input

View File

@ -2,6 +2,7 @@ import "./Modal.scss";
import React, { useState, useLayoutEffect } from "react"; import React, { useState, useLayoutEffect } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
import clsx from "clsx";
import { KEYS } from "../keys"; import { KEYS } from "../keys";
export const Modal = (props: { export const Modal = (props: {
@ -26,7 +27,7 @@ export const Modal = (props: {
return createPortal( return createPortal(
<div <div
className={`Modal ${props.className ?? ""}`} className={clsx("Modal", props.className)}
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
onKeyDown={handleKeydown} onKeyDown={handleKeydown}

View File

@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef } from "react"; import React, { useState, useEffect, useRef } from "react";
import clsx from "clsx";
import { ToolButton } from "./ToolButton"; import { ToolButton } from "./ToolButton";
import { t } from "../i18n"; import { t } from "../i18n";
import useIsMobile from "../is-mobile"; import useIsMobile from "../is-mobile";
@ -154,9 +155,9 @@ export const RoomDialog = ({
return ( return (
<> <>
<ToolButton <ToolButton
className={`RoomDialog-modalButton ${ className={clsx("RoomDialog-modalButton", {
isCollaborating ? "is-collaborating" : "" "is-collaborating": isCollaborating,
}`} })}
onClick={() => setModalIsShown(true)} onClick={() => setModalIsShown(true)}
icon={users} icon={users}
type="button" type="button"

View File

@ -1,6 +1,7 @@
import "./Stack.scss"; import "./Stack.scss";
import React from "react"; import React from "react";
import clsx from "clsx";
type StackProps = { type StackProps = {
children: React.ReactNode; children: React.ReactNode;
@ -19,7 +20,7 @@ const RowStack = ({
}: StackProps) => { }: StackProps) => {
return ( return (
<div <div
className={`Stack Stack_horizontal ${className || ""}`} className={clsx("Stack Stack_horizontal", className)}
style={ style={
{ {
"--gap": gap, "--gap": gap,
@ -42,7 +43,7 @@ const ColStack = ({
}: StackProps) => { }: StackProps) => {
return ( return (
<div <div
className={`Stack Stack_vertical ${className || ""}`} className={clsx("Stack Stack_vertical", className)}
style={ style={
{ {
"--gap": gap, "--gap": gap,

View File

@ -1,6 +1,7 @@
import "./ToolIcon.scss"; import "./ToolIcon.scss";
import React from "react"; import React from "react";
import clsx from "clsx";
type ToolIconSize = "s" | "m"; type ToolIconSize = "s" | "m";
@ -45,15 +46,18 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
if (props.type === "button") { if (props.type === "button") {
return ( return (
<button <button
className={`ToolIcon_type_button ${ className={clsx(
!props.hidden ? "ToolIcon" : "" "ToolIcon_type_button",
} ${sizeCn}${props.selected ? " ToolIcon--selected" : ""} ${ sizeCn,
props.className props.className,
} ${
props.visible && !props.hidden props.visible && !props.hidden
? "ToolIcon_type_button--show" ? "ToolIcon_type_button--show"
: "ToolIcon_type_button--hide" : "ToolIcon_type_button--hide",
}`} {
ToolIcon: !props.hidden,
"ToolIcon--selected": props.selected,
},
)}
hidden={props.hidden} hidden={props.hidden}
title={props.title} title={props.title}
aria-label={props["aria-label"]} aria-label={props["aria-label"]}
@ -78,7 +82,7 @@ export const ToolButton = React.forwardRef((props: ToolButtonProps, ref) => {
} }
return ( return (
<label className={`ToolIcon ${props.className ?? ""}`} title={props.title}> <label className={clsx("ToolIcon", props.className)} title={props.title}>
<input <input
className={`ToolIcon_type_radio ${sizeCn}`} className={`ToolIcon_type_radio ${sizeCn}`}
type="radio" type="radio"

View File

@ -1,6 +1,7 @@
import "./UserList.scss"; import "./UserList.scss";
import React from "react"; import React from "react";
import clsx from "clsx";
type UserListProps = { type UserListProps = {
children: React.ReactNode; children: React.ReactNode;
@ -9,15 +10,9 @@ type UserListProps = {
}; };
export const UserList = ({ children, className, mobile }: UserListProps) => { export const UserList = ({ children, className, mobile }: UserListProps) => {
let compClassName = "UserList"; return (
<div className={clsx("UserList", className, { UserList_mobile: mobile })}>
if (className) { {children}
compClassName += ` ${className}`; </div>
} );
if (mobile) {
compClassName += " UserList_mobile";
}
return <div className={compClassName}>{children}</div>;
}; };

View File

@ -6,6 +6,7 @@
import React from "react"; import React from "react";
import oc from "open-color"; import oc from "open-color";
import clsx from "clsx";
const activeElementColor = (appearance: "light" | "dark") => const activeElementColor = (appearance: "light" | "dark") =>
appearance === "light" ? oc.orange[4] : oc.orange[9]; appearance === "light" ? oc.orange[4] : oc.orange[9];
@ -27,7 +28,7 @@ const createIcon = (d: string | React.ReactNode, opts: number | Opts = 512) => {
focusable="false" focusable="false"
role="img" role="img"
viewBox={`0 0 ${width} ${height}`} viewBox={`0 0 ${width} ${height}`}
className={mirror && "rtl-mirror"} className={clsx({ "rtl-mirror": mirror })}
style={style} style={style}
> >
{typeof d === "string" ? <path fill="currentColor" d={d} /> : d} {typeof d === "string" ? <path fill="currentColor" d={d} /> : d}