Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

aesthetic

Package Overview
Dependencies
Maintainers
1
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

aesthetic - npm Package Compare versions

Comparing version 0.0.4 to 0.1.0

4

lib/classNames.js

@@ -19,3 +19,3 @@ 'use strict';

/*:: type ClassName = string | number | { [key: string]: boolean } | ClassName[];*/
/*:: import type { MaybeClassName } from '../../types';*/

@@ -36,3 +36,3 @@

values.forEach(function (value /*: ClassName*/) {
values.forEach(function (value /*: MaybeClassName*/) {
// Empty value or failed condition

@@ -39,0 +39,0 @@ if (!value) {

@@ -39,3 +39,3 @@ 'use strict';

/* eslint-disable react/sort-comp */
/* eslint-disable react/sort-comp, react/no-unused-prop-types */

@@ -48,2 +48,6 @@ /*:: import type {

} from '../../types';*/
/*:: type PropsAndState = {
classNames?: ClassNames,
theme?: string,
};*/
function style(aesthetic /*: Aesthetic*/) /*: (WrappedComponent) => HOCComponent*/ {

@@ -93,5 +97,26 @@ var defaultStyles /*: StyleOrCallback*/ = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};

value: function componentWillMount() {
this.transformStyles(this.getTheme(this.props));
}
// Re-transform if the theme changes
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(nextProps /*: PropsAndState*/) {
var theme = this.getTheme(nextProps);
if (theme !== this.state[themePropName]) {
this.transformStyles(theme);
}
}
}, {
key: 'getTheme',
value: function getTheme(props /*: PropsAndState*/) /*: string*/ {
return props[themePropName] || this.context.themeName || '';
}
}, {
key: 'transformStyles',
value: function transformStyles(theme /*: string*/) {
var _setState;
var theme = this.props[themePropName] || this.context.themeName || '';
var classNames = aesthetic.transformStyles(styleName, theme);

@@ -98,0 +123,0 @@

{
"name": "aesthetic",
"version": "0.0.4",
"version": "0.1.0",
"description": "Abstract library to support a range of styling options for React components.",

@@ -10,6 +10,3 @@ "keywords": [

],
"repository": {
"type": "git",
"url": "git+https://github.com/milesj/aesthetic.git"
},
"repository": "https://github.com/milesj/aesthetic/tree/master/packages/aesthetic",
"license": "MIT",

@@ -16,0 +13,0 @@ "main": "./lib/index.js",

@@ -1,47 +0,286 @@

# Aesthetic
# Aesthetic v0.1.0
[![Build Status](https://travis-ci.org/milesj/aesthetic.svg?branch=master)](https://travis-ci.org/milesj/aesthetic)
Abstract library to support a range of styling options for React components.
Aesthetic is a powerful React library for styling components, whether it be CSS-in-JS
using objects, importing stylesheets, or simply referencing external class names.
Simply put, Aesthetic is an abstraction layer that utilizes higher-order-components for
the compilation of styles via third-party libraries, all the while providing customizability,
theming, and a unified syntax.
## Usage
```javascript
import React, { PropTypes } from 'react';
import { classes, ClassNamesPropType } from 'aesthetic';
import style from '../path/to/styler';
How to use this library.
class Carousel extends React.Component {
static propTypes = {
children: PropTypes.node,
classNames: ClassNamesPropType,
};
### Base Component
// ...
The base component, an abstract component with default styles,
one which all consumers compose around.
render() {
const { children, classNames } = this.props;
const { animating } = this.state;
Is usually provided by a third-party library, like Toolkit,
or simply defined and consumed directly within an application.
return (
<div
role="tablist"
className={classes({
classNames.carousel,
animating && classNames.carousel__animating,
})}
>
<ul className={classNames.list}>
{children}
</ul>
Say we're using a third-party library, like Toolkit, which provides
and styles this reusable `Button` component.
<button
type="button"
onClick={this.handlePrev}
className={classes(classNames.button, classNames.prev)}
>
</button>
<button
type="button"
onClick={this.handleNext}
className={classes(classNames.button, classNames.next)}
>
</button>
</div>
);
}
}
export default style({
carousel: {
position: 'relative',
maxWidth: '100%',
// ...
},
carousel__animating: { ... },
list: { ... },
button: { ... },
prev: { ... },
next: { ... },
})(Carousel);
```
Aesthetic was built for the sole purpose of solving the following scenarios, most of which
competing styling libraries fail to solve.
**Multiple styling patterns**
Want to use external CSS or Sass files? Or maybe CSS modules? Or perhaps CSS-in-JS?
What about JSS instead of Aphrodite? All of these patterns are supported through the
use of [adapters](#style-adapters). However, inline styles *are not supported*
as we prefer the more performant option of compiling styles and attaching them to the DOM.
**Styling third-party libraries**
Using a third-party provided UI component library has the unintended side-effect
of hard-coded and non-customizable styles. Aesthetic solves this problem by allowing
[unlocked styles](#creating-a-styler) to be overwritten by the consumer at most one time.
It also has the added benefit of choosing the styling pattern, as mentioned previously.
```javascript
function Button({ children, styles }) {
// Provider
function Button() {
// ...
}
export default style({
button: { ... },
}, {
lockStyling: false,
})(Button);
// Consumer
import Button from 'toolkit/components/Button';
Button.setStyles({
button: { ... },
});
```
## Installation
Aesthetic requires React as a peer dependency.
```
npm install aesthetic react --save
// Or
yarn add aesthetic react
```
## Documentation
* [Initial Setup](#initial-setup)
* [Webpack](#webpack)
* [Browserify](#browserify)
* [Style Adapters](#style-adapters)
* [Creating A Styler](#creating-a-styler)
* [Defining Components](#defining-components)
* [Overwriting Styles](#overwriting-styles)
* [Combining Classes](#combining-classes)
* [Styling Components](#styling-components)
* [External Classes](#external-classes)
* [Style Objects](#style-objects)
* [Style Functions](#style-functions)
* [Theming Components](#theming-components)
* [Using Theme Styles](#using-theme-styles)
* [Activating Themes](#activating-themes)
* [Unified Syntax](#unified-syntax)
* [Properties](#properties)
* [Pseudos](#pseudos)
* [Fallbacks](#fallbacks)
* [Media Queries](#media-queries)
* [Font Faces](#font-faces)
* [Animations](#animations)
* [Selectors](#selectors)
* [Competitors Comparison](#competitors-comparison)
* [Features](#features)
* [Adapters](#adapters)
### Initial Setup
Aesthetic makes heavy use of `process.env.NODE_ENV` for logging errors in development.
These errors will be entirely removed in production if the following build steps are configured.
#### Webpack
[DefinePlugin](https://webpack.github.io/docs/list-of-plugins.html#defineplugin) plugin
is required when using Webpack.
```javascript
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
}),
```
#### Browserify
[Envify](https://github.com/hughsk/envify) transformer is required when using Browserify.
```javascript
envify({
NODE_ENV: process.env.NODE_ENV || 'production',
});
```
### Style Adapters
An adapter in the context of Aesthetic is a third-party library that supports CSS in JavaScript,
whether it be injecting CSS styles based off JavaScript objects, importing CSS during a build
process, or simply referencing CSS class names.
The following libraries and their features are officially supported by Aesthetic.
| Adapter | Unified Syntax | Pseudos | Fallbacks | Fonts | Animations | Media Queries |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: |
| [CSS class names](#external-classes) | | ✓ | ✓ | ✓ | ✓ | ✓ |
| [CSS modules][css-modules] | | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Aphrodite][aphrodite] | ✓ | ✓ | | ✓ | ✓ | ✓ |
| [Glamor][glamor] | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| [JSS][jss] | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
And the following libraries are not supported.
* [CSSX](https://github.com/krasimir/cssx) -
Does not generate unique class names during compilation and instead
uses the literal class names and or tag names defined in the style declaration.
This allows for global style collisions, which we want to avoid.
### Creating A Styler
To start using Aesthetic, a styler function must be created. This styler function
acts as a factory for the creation of higher-order-components
([HOC](https://medium.com/@franleplant/react-higher-order-components-in-depth-cf9032ee6c3e)).
These HOC's are used in transforming styles via adapters and passing down CSS
class names to the original wrapped component.
To begin, we must create an instance of `Aesthetic` with an [adapter](#style-adapters),
pass it to `createStyler`, and export the new function. I suggest doing this an a file
that can be imported for reusability.
```javascript
import Aesthetic, { createStyler } from 'aesthetic';
import JSSAdapter from 'aesthetic-jss'; // Or your chosen adapter
export default createStyler(new Aesthetic(new JSSAdapter()));
```
Once we have a styler function, we can import it and wrap our React components.
The styler function accepts a [style declaration](#styling-components) as its first argument,
and an object of configurable options as the second. The following options are supported.
* `styleName` (string) - The unique style name of the component. This name is primarily
used in logging and caching. Defaults to the component name.
* `lockStyling` (boolean) - Will lock styles from being written after the default styles
have been set. Defaults to `true`.
* `classNamesPropName` (string) - Name of the prop in which the compiled class names
object is passed to. Defaults to `classNames`.
* `themePropName` (string) - Name of the prop in which the theme name is passed to.
Defaults to `theme`.
```javascript
export default style({
button: { ... },
}, {
styleName: 'CustomButton',
lockStyling: false,
classNamesPropName: 'classes',
themePropName: 'appTheme',
})(Button);
```
### Defining Components
Now that we have a styler function, we can start styling our components by wrapping
the component declaration with the styler function and passing an object of styles.
When this component is rendered, the style object is transformed into an object of class names,
and passed to the `classNames` prop.
```javascript
import React, { PropTypes } from 'react';
import { ClassNamesPropType } from 'aesthetic';
import style from '../path/to/styler';
function Button({ children, classNames, icon }) {
return (
<button className={styles.button}>{children}</button>
<button type="button" className={classNames.button}>
{icon && (
<span className={classNames.icon}>{icon}</span>
)}
{children}
</button>
);
}
Button.propTypes = {
children: PropTypes.node,
classNames: ClassNamesPropType,
icon: PropTypes.node,
};
export default style({
button: {
display: 'inline-block',
padding: 5,
},
button: { ... },
icon: { ... }
})(Button);
```
Now, if I consume this library, how can I customize the styles of this button?
What if I want to use class names? Or change the name of existing class names?
What about a theme? Etc.
#### Overwriting Styles
### Overriding Default Styles
Since styles are isolated and colocated within a component, they can be impossible to
customize, especially if the component comes from a third-party library. If a component
hasn't been locked via the `lockStyling` option, styles can be customized by calling
the static `setStyles` method on the wrapped component instance.
All base components can have their styles overridden by the consumer, like so.
This can only be done once in an effort to promote strict isolation and encapsulation.
Style overriding can also be disabled all together.
```javascript
import Button from 'toolkit/components/Button';
import Button from '../Button';

@@ -52,30 +291,51 @@ Button.setStyles({

fontWeight: 'bold',
// ...
},
});
// Or merge with the default styles
Button.mergeStyles({ ... });
```
And done. We now have a `Button` component that is styled to our application.
Any previous styles that were overwritten will be available when using a
[style function](#style-functions).
We can take this a step further, if need be, by wrapping the base component
with a custom component.
> `setStyles` can only be called once, as styles are immediately locked.
> This avoids unwanted style injections.
### Composed Component
#### Combining Classes
Say we want to execute some logic, or prepare props, before rendering the base button.
We can do this by wrapping the base component with our own component.
When multiple class names need to be applied to a single element, the `classes`
function provided by Aesthetic can be used. This function accepts an arbitrary
number of arguments, all of which can be strings, arrays, or objects that evaluate to true.
```javascript
import BaseButton from 'toolkit/components/Button';
import { classes } from 'aesthetic';
// Set styles like before
BaseButton.setStyles({ ... });
classes(
'foo',
expression && 'bar',
{
baz: false,
qux: true,
},
); // foo qux
```
export default function Button({ children, ...props }) {
// Do something
Using our button style examples above, let's add an active state and can combine classes
like so. Specificity is important, so define styles from top to bottom!
```javascript
function Button({ children, classNames, icon, active = false }) {
return (
<BaseButton {...props}>{children}</BaseButton>
<button
type="button"
className={classes(
classNames.button,
active && classNames.button__active,
)}
>
{icon && (
<span className={classNames.icon}>{icon}</span>
)}
{children}
</button>
);

@@ -85,59 +345,342 @@ }

## Adapters
### Styling Components
Only adapters that score a star in all categories are supported: https://github.com/MicheleBertoli/css-in-js
As mentioned previously, to style a component, an object or function must be passed
as the first argument to the [styler function](#creating-a-styler). This object
represents a mapping of elements (and modifiers) to declarations. For example:
### CSS Classes
```javascript
style({
foo: 'foo',
bar: 'bar',
})(Component);
button: { ... },
button__active: { ... },
icon: { ... },
})(Button)
```
### CSS Modules
The following types of declarations are permitted.
```css
.foo {
color: 'red';
display: 'inline';
}
.bar {
color: 'blue';
padding: 5px;
}
#### External Classes
External CSS class names can be referenced by passing a string of the class name.
```javascript
style({
button: 'button',
button__active: 'button--active',
icon: 'button__icon',
})(Button)
```
To make use of class names, the provided `ClassNameAdapter` must be used.
```javascript
import styles from './styles.css';
import Aesthetic, { createStyler, ClassNameAdapter } from 'aesthetic';
style(styles)(Component);
export default createStyler(new Aesthetic(new ClassNameAdapter()));
```
### Aphrodite, JSS, Glamor, Fela, VStyle, Styletron, Babel CSS-In-JS
#### Style Objects
CSS styles can be defined using an object of properties to values. These objects are
transformed using [adapters](#style-adapters) and optionally support the
[unified syntax](#unified-syntax) defined by Aesthetic.
```javascript
style({
foo: {
color: 'red',
display: 'inline',
button: {
background: '#eee',
// ...
},
bar: {
color: 'blue',
padding: 5,
button__active: {
background: '#fff',
// ...
},
})(Component);
icon: {
display: 'inline-block',
verticalAlign: 'middle',
// ...
},
})(Button)
```
### Unsupported Adapters
#### Style Functions
* **CSSX** - Does not generate unique class names during compilation and instead
uses the literal class names and or tag names defined in the style declaration.
This allows for global style collisions, which we want to avoid.
* **Radium** - Uses inline styles instead of compiling and attaching CSS styles
to the DOM.
Style functions are simply functions that return a style object. The benefits of using a
function is that it provides the [current theme](#using-themes) as the first argument,
and the [previous styles](#overwriting-styles) as the second argument.
## Themes
```javascript
style(function (theme, prevStyles) {
// ...
})(Button)
```
TODO
### Theming Components
Themes are great in that they enable components to be styled in different ways based
on pre-defined style guide parameters, like font size, color hex codes, and more.
To make use of a theme, register it through the `Aesthetic` instance using `registerTheme`.
This method accepts a name, an object of parameters, and an optional
[style object](#style-objects) used for globals (like font faces and animation keyframes).
```javascript
aesthetic.registerTheme('dark', {
unit: 'em',
unitSize: 8,
spacing: 5,
font: 'Roboto',
}, {
'@font-face': {
roboto: {
fontFamily: 'Roboto',
fontStyle: 'normal',
fontWeight: 'normal',
src: "url('roboto.woff2') format('roboto')",
},
},
});
```
> Global styles are immediately compiled and attached the DOM. Be wary of conflicts.
#### Using Theme Styles
Once a theme has been registered, we can access the style parameters by using a
[style function](#style-functions). The parameters object is passed as the first
argument to the function.
```javascript
style((theme) => ({
button: {
fontSize: `${theme.unitSize}${theme.unit}`,
fontFamily: theme.font,
padding: theme.spacing,
},
}))(Component);
```
#### Activating Themes
To activate and inform components to use a specific theme, we must use the `ThemeProvider`,
which accepts a `name` of the theme.
```javascript
import { ThemeProvider } from 'aesthetic';
<ThemeProvider name="default">
// All components within here will use the "default" theme
<ThemeProvider name="dark">
// And all components here will use "dark"
</ThemeProvider>
</ThemeProvider>
```
Or by passing a `theme` prop to an individual component.
```javascript
<Button theme="dark">Save</Button>
```
### Unified Syntax
Aesthetic provides an optional, but enabled by default, unified CSS-in-JS syntax.
This unified syntax permits easy [drop-in replacements](https://en.wikipedia.org/wiki/Drop-in_replacement)
between adapters that utilize CSS-in-JS objects.
**Pros**
* Easily swap between CSS-in-JS adapters (for either performance or extensibility reasons)
without having to rewrite all CSS style object syntax.
* Only have to learn one form of syntax.
**Cons**
* Slight overhead (like milliseconds) converting the unified syntax to the adapters native
syntax. However, Aesthetic caches heavily.
* Must learn a new form of syntax (hopefully the last one).
**Why a new syntax?**
While implementing adapters and writing tests for all their syntax and use cases, I noticed
that all adapters shared about 90-95% of the same syntax. That remaining percentage could
easily be abstracted away by a library, and hence, this unified syntax was created. In the end,
it was mostly for fun, but can easily be disabled if need be.
**Why a different at-rule structure?**
The major difference between the unified syntax and native adapters syntax, is that at-rules
in the unified syntax are now multi-dimensional objects indexed by the name of the at-rule
(`@media`), while at-rules in the native syntax are single objects indexed by the at-rule
declaration (`@media (min-width: 100px)`).
Supporting the native syntax incurred an linear (`O(n)`) lookup, as we would have to loop
through each object recursively to find all at-rules, while the unified syntax is a simple
constant (`O(1)`) lookup as we know the names ahead of time. This constant time lookup is
what enables a fast conversion process between the unified and native syntaxes.
**What if I want to use the adapter's syntax?**
If you'd like to use the native syntax of your chosen adapter, simply call
`disableUnifiedSyntax()` on the instance of your adapter.
#### Properties
Standard structure for defining properties.
* Supports camel case property names.
* Units can be written is literal numbers.
```javascript
button: {
margin: 0,
padding: 5,
display: 'inline-block',
lineHeight: 'normal',
textAlign: 'center',
cursor: 'pointer',
backgroundColor: '#ccc',
color: '#000',
},
buttonGroup: {
// ...
},
```
> JSS requires the `jss-default-unit`, `jss-camel-case`, and `jss-nested`
> plugins for unified syntax support.
#### Pseudos
Pseudo elements and classes are defined inside an element as nested objects.
```javascript
button: {
// ...
':hover': {
backgroundColor: '#eee',
},
'::before': {
content: '"★"',
display: 'inline-block',
marginRight: 5,
},
},
```
#### Fallbacks
Property fallbacks for old browsers are defined under the `@fallbacks` object.
Each property accepts a single value or an array of values.
```javascript
wrapper: {
// ...
background: 'linear-gradient(...)',
display: 'flex',
'@fallbacks': {
background: 'red',
display: ['box', 'flex-box'],
},
},
```
> Aphrodite does not support fallback styles.
#### Media Queries
Media queries are defined inside an element using a `@media` object.
```javascript
tooltip: {
// ...
maxWidth: 300,
'@media': {
'(min-width: 400px)': {
maxWidth: 'auto',
},
},
},
```
#### Font Faces
Font faces are defined outside the element using a `@font-face` object
and are referenced by font family name.
```javascript
'@font-face': {
roboto: {
fontFamily: 'Roboto',
fontStyle: 'normal',
fontWeight: 'normal',
src: "url('roboto.woff2') format('roboto')",
},
},
button: {
// ...
fontFamily: 'Roboto',
},
tooltip: {
// ...
fontFamily: 'Roboto, sans-serif',
},
```
#### Animations
Animation keyframes are defined outside the element using a `@keyframes` object
and are referenced by animation name (the object key).
```javascript
'@keyframes': {
fade: {
from: { opacity: 0 },
to: { opacity: 1 },
},
},
button: {
// ...
animationName: 'fade',
animationDuration: '3s',
},
```
#### Selectors
Parent, child, and sibling selectors are purposefully not supported. Use unique and
isolated element names and style declarations instead.
### Competitors Comparison
A brief comparison of Aesthetic to competing React style abstraction libraries.
#### Features
| | aesthetic | [react-with-styles][react-with-styles] | [styled-components][styled-components] | [radium][radium] |
| --- | :---: | :---: | :---: | :---: |
| Abstraction | HOC | HOC | Template Literals | HOC |
| Type | Classes | Classes, Inline styles | Classes | Inline styles |
| Unified Syntax | ✓ | | | |
| Caching | ✓ | | ✓ | N/A |
| Themes | ✓ | ✓ | ✓ | |
| Style Overwriting | ✓ | | | ||
#### Adapters
| | aesthetic | [react-with-styles][react-with-styles] | [styled-components][styled-components] | [radium][radium] |
| --- | :---: | :---: | :---: | :---: |
| [CSS class names](#external-classes) | ✓ | | | |
| [CSS Modules][css-modules] | ✓ | | | |
| [Aphrodite][aphrodite] | ✓ | ✓ | | |
| [Glamor][glamor] | ✓ | | ✓ | |
| [JSS][jss] | ✓ | ✓ | | |
| [React Native][react-native] | | ✓ | | ||
[css-modules]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-css-modules
[aphrodite]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-aphrodite
[glamor]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-glamor
[jss]: https://github.com/milesj/aesthetic/tree/master/packages/aesthetic-jss
[radium]: https://github.com/FormidableLabs/radium
[react-native]: https://github.com/facebook/react-native
[react-with-styles]: https://github.com/airbnb/react-with-styles
[styled-components]: https://github.com/styled-components/styled-components
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc