Package Exports
- react-classnaming
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (react-classnaming) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
react-classnaming
Tools to establish CSS classes as an explicit abstraction layer and to handle it as an interface between React and CSSStyleDeclaration.
[TOC]
Objectives
- Make CSS classes to be explicit and predictable project layer
- Enforce declaration style programming
- Enforce contract based development via TypeScript
- Enforce single source of truth
- Enforce class-conditions to be strictly
boolean, notfalsy|truthy - Use IDE type hints as developers UX for faster issues resolving
- CSS-modules agnostic
Use package like postcss-plugin-d-ts to prepare strict declaration of CSS
Installation and import
npm install --save react-classnamingimport {
classNaming, // Returns function for building `className: string` from conditioned CSS classes with "context" (if was provided) from `props` for using only declared CSS classes
classNamesMap, // Similar to classNaming, specifies mapping to component's (i.e. 3rd-party) `className`-related props
classNamesCheck // Identical function for TS restriction on classes determed in CSS and not used in component
} from "react-classnaming"
// Default export is the most frequently used function
import classNaming from "react-classnaming"
// Import module with specific function only
import { classNaming } from "react-classnaming/naming"
import { classNamesCheck } from "react-classnaming/check"
import { classNamesMap } from "react-classnaming/map"
import type {
ClassNamesProperty, // Type to declare component's self CSS classes
ClassNames, // Type to gather required CSS classes of sub-components
ClassHash, // `= string | undefined` – type to declare CSS class, global or local
ClassNamed // `= {className: string}` – useful shortcut
} from "react-classnaming/types"Basic usage
Example of simple CSS classes conditioning – _tests_/readme.spec.tsx:9
import classNaming from "react-classnaming"
type Props = {
isValid: boolean
readOnly: boolean
}
// isValid = false, readOnly = false
function FormButtons({isValid, readOnly}: Props) {
const cssClasses = classNaming()
const buttonClass = cssClasses({"button": true}) // "button"
return <>
<button {
...buttonClass // className="button"
}>Close</button>
<button type="reset" {
...buttonClass({"button--disabled": readOnly}) // className="button"
}>Reset</button>
<button type="submit" className={`button_submit ${
buttonClass({"button--disabled": readOnly || !isValid}) // "button button--disabled"
}`}>Submit</button>
</>
} As shown, producing function classNaming returns a multipurpose object. It can be
- recalled to stack more CSS classes on conditions:
anotherClass = someClass({...})({...}) - destructed in component's props as
classNamesingleton:<div {...someClass}/> <button {...anotherClass}/> - used as a string:
``${someClass} ${anotherClass}``
Demos
You can find demonstration with all main points in folder ./_examples_/, in addition *.test.* and *.spec.*.
Getting more
Condition is strictly boolean
Conditions with falsy values may lead to hardly caught bugs due to not obvious behavior for humans. In addition, as a possible true shortcut, the value can be not empty string as class-hash from CSS-module, and undefined for global CSS-class or modules simulation. Thus, to not keep in mind that undefined appears to be a truthy condition, it is prohibited on TypeScript level to mix in value type boolean with ClassHash = string | undefined and not allowed to use any other types like 0, null. _tests_/readme.spec.tsx:43

Single source of truth
There can be only ONE condition for each class in call pipe. Already conditioned classes are propagated to next call type notation so you can see currently stacked with according modality: true, false or boolean. _tests_/readme.spec.tsx:55

Declare own component's CSS classes
Only declared CSS classes will be allowed as keys with IDE hint on possibilities – _tests_/readme.spec.tsx:71
+ import type { ClassHash, ClassNamesProperty } from "react-classnaming"
+ type MyClassNames = ClassNamesProperty<{
+ button: ClassHash
+ button_submit: ClassHash
+ "button--disabled": ClassHash
+ }>
- const cssClasses = classNaming()
+ const cssClasses = classNaming<MyClassNames>()
Reference
type ClassNamed
Shortcut for {className: string}.
type ClassHash
For serving global and local CSS classes and not moduled CSS stylesheets. CSS-module will be imported as {[cssClasses: string]: string}, while for ordinary CSS import require returns just empty object {}. Their common notation is {[cssClasses: string]: string | undefined} , thus type ClassHash = string | undefined
function classNaming
Sets context for further type checks in supplying and toggling.
classNaming()
classNaming<MyProps>()
classNaming<MyClassNames>()
classNaming({classnames: require("./some.css")})
classNaming({classnames: module_css, className})
classNaming(this.props)Returns pipe-able (recallable) callback, that also can be destructed as ClassNamed or stringifyed
const cssClasses = classNaming(...)
const btnClass = cssClasses({ button })
return
<div {...btnClass } />
<div data-block={`${btnClass}`} />
<Component {...{
...btnClass(...)(...)(...)}
}/>On TS-level checks that Component's propagated className and certain CSS-class are conditioned once
const conditionForClass1: boolean = false
const containerClass = classes(true, {class1: conditionForClass1})
const withClass1Twice = containerClass({
class2: true,
//@ts-expect-error – TS tracks that in chain there's only 1 place for class to be conditionally included
class1: otherCondiition
})
const withClassNameTwice = containerClass(
//@ts-expect-error - Same for `className` - it is already added
true
)On const hovering will be tooltip with already conditioned classes under this chain
function classNamesMap
Function to map classnames to string props of some (i.e. 3rd-party) component.
const { Root } = classnames
const mapping = classNamesMap(classnames)
<ThirdPartyComponent {...mapping({} as typeof ThirdPartyComponent, {
ContainerClassName: { Root, "Theme--dark": true },
Checked___true: classes({ "Item--active": true }),
Checked___false: {}
})} />For hint will be used such props of target component that can be assigned to string. After calling mapping function and setting other properties, usual TypeScript will check for presence of target's required properties and other ordinary for TS things.

type ClassNamesProperty
Declaration of self Component's classnames
type MyClasses = ClassNamesProperty<{
class1: ClassHash
class2: ClassHash
}>Can be restricted to use classes only from CSS module. Note Currently no IDE's tooltip for hints
type MyProps = ClassNamesProperty<
typeof some_module_css,
//@ts-expect-error
{class1: ClassHash, class2: ClassHash, unknownClass: ClassHash}
>type ClassNames
Collects/gathers required classnames from used sub-Components
type MyProps = ClassNames<true> // === ClassNamed === {className: string}
type MyProps = ClassNames<Props> // {classnames: Props["classnames"]}
type MyProps = ClassNames<typeof Component>
type MyProps = ClassNames<true, Props, typeof ClassComponent, typeof FunctionalComponent>type Props = ClassNames<true, Sub1Props, typeof Sub2>
function Component({className, classnames, "classnames": {Sub1Class}}: Props) {
const classes = classNaming({classnames, className})
return <div>
<Sub1 {...classes(true, {Sub1Class})} classnames={classnames}/>
<Sub2 {...{
...classes({Sub2Class: true}),
classnames
}}/>
</div>
}type ClassNamesFrom
Obtain classnames-object from props of functional component, class component or props type
ClassNamesFrom<ComponentProps>;
ClassNamesFrom<typeof Component>;function classNamesCheck
Identical function or returning constant EMPTY_OBJECT for keys check of not used classes in components tree
import css from "./page.scss"
import App from "./App.tsx"
ReactDOM.render(<App classnames={classNamesCheck(...)} />- Dummies shape
<Component classnames={classNamesCheck()} />;- Checks CSS with defined (not indexed) classes keys. To produce such declaration you can use package
postcss-plugin-d-ts.
import type { ClassNamesFrom } from "react-classnaming/types";
import css_module from "./some.css"; // With class `.never-used {...}`
<Component classnames={classNamesCheck(
css_module,
//@ts-expect-error Property 'never-used' is missing
{} as ClassNamesFrom<typeof Component>
)} />;Misc
Restructuring
Using CSS-modules or simulation
It is possible to use CSS modules or simulation without "context" by supplying class-hash value with variable _tests_/readme.spec.tsx:114
// CSS-module, assuming "button" will be replaced with "BTN"
+ import css_module from "./button.module.css"
+ const { button } = css_module
// Module simulation
+ type CssModuleSimulation = { button_submit: ClassHash }
+ const { button_submit } = {} as CssModuleSimulation
type MyClassNames = ClassNamesProperty<
+ typeof css_module &
+ CssModuleSimulation &
{
- button: ClassHash
- button_submit: ClassHash
"button--disabled": ClassHash
}
>
- const buttonClass = cssClasses({ button: true })
+ const buttonClass = cssClasses({ button })
<button type="submit" {...buttonClass({
- "button_submit": true,
+ button_submit,
"button--disabled": readOnly || !isValid
})}>Submit</button> Versus classnames package
See src/versus-classnames.test.ts
//TODO Copy here the most significant TS errors
No css-modules, just simulation
import classnames from "classnames"
<div className={classnames("class1", "class2")} />
<div id={classnames("class1", "class2")} />
// VERSUS
import css from "./some.css"
import classNaming, {classNamesCheck} from "react-classnaming"
import type {ClassNames} from "react-classnaming"
const { class1,
//@ts-expect-error
whatever
} = classNamesCheck<...>(css)
const props: ClassNames<"class2"> = {"classnames": css}
const {class2} = props.classnames
<div {...classNaming({class1, class2})} />
<div id={`${classNaming({class1, class2})}`} />CSS module
import module_css from "./some.module.css" // {"class1": "hash1", "class2": "hash2"}
import classnames_bind from "classnames/bind"
const cx = classnames_bind.bind(module_css)
// No error on redundant CSS-class
<div className={cx("class1", {"class3": true})} />
// VERSUS
import classNaming from "react-classnaming"
const clases = classNaming({classnames: module_css})
//@ts-expect-error Argument of type '"class3"' is not assignable to parameter
<div {...clases({class1: true, class3: true})} />