Unit 3: Style

If the structure represents the skeleton, the style is the external appearance, the skin of the app. The same structure can look completely different in two apps that apply different styles to it. In oneJS, you can style all your components using JavaScript. JavaScript style attributes match the CSS attributes on the web using camel case notation; e.g. border-radius is written as borderRadius. If you are already familiar with CSS, you can leverage all of its power to customize your app. For the web, Emotion is used to read and compile style objects. For iOS and Android, oneJS uses the default React Native style property.

Style Property

To apply your style, you can use the style property in any component. The style property can receive a JavaScript object or an array of object styles. The priority increases with the position in the array; the last style has precedence.

On the web, using Emotion, styles are internally compiled into a class and applied to your component. This enables to use nested structures, media queries, animations and much more!

In general, it should be avoided but if there is no other remedy, you can use inline styles by feeding the inlineStyle property. This can only be done in web components as there is no such distinction between class and inline styles in React Native.

/* Imports */
import {app} from '@onejs-dev/core';
import {View, Input} from '@onejs-dev/components';

/* Style */
const button1Style = {color: 'white', backgroundColor: 'blue'};
const button2Style = {backgroundColor: 'red', borderRadius: 30};

/* App Component: Takes no properties and returns structure */
const App = () => {
return View()([
Input({type: 'button', title: 'Button 1', style: button1Style}),
Input({type: 'button', title: 'Button 2', style: [button1Style, button2Style]}),
Input({type: 'button', title: 'Button 3', inlineStyle: {color: 'green'}}),
]);
};

/* App Function: Renders the App Component in the screen */
app({component: App});

As your app grows in complexity, it is often cleaner to wrap all your styles in single object. You can then feed this object to the app function's style property. This enables using the readStyle function to apply styles to your components:

const style = {
    button1Style: {color: 'white', backgroundColor: 'blue'},
    button2Style: {backgroundColor: 'red', borderRadius: 30}
}

const App = () => {
    return  View()([
        Input({type: 'button', title: 'Button 1', style: readStyle('button1Style')}),
        Input({type: 'button', title: 'Button 2', style: readStyle('button1Style', 'button2Style')}),
    ]);
};

app({component: App, style: style});

Theming

For small apps it may be enough to apply styles directly to components. In larger apps, it is helpful to define a set of guidelines that govern the look and feel of components without having to specify their style every time they are used. oneJS provides scalable theming capabilities; we are confident that if you give them a try you are going to love them. A theme, is the collection of all the variables that you would use to customize the look and feel of your app. Out of the box, oneJS provides the following selection of theme variables:

const themeVariables = {
    /* The values below show the default values for oneJS preset theme */
    //Color
    backgroundColor: '#ffffff', //The color for the component's background.
    primaryColor: '#094DFF',    //The primary contrast color for the component.
    neutralColor: '#b1b0c5',    //Neutral color that can be used for backgrounds or disabled buttons.
    acceptColor: '#60b33a',     //Color for affirmative actions. Usually green.
    rejectColor: '#ff5100',     //Color for negative actions. Usually red.
    warnColor: '#f7df00',       //Color for warning. Usually yellow.
    lightColor: '#e9e8ee',      //Light color for icons or backgrounds.
    darkColor: '#4c4b66',       //Dark color for icons or backgrounds

    //Text
    textFont: 'Avenir Next',    //Font family for texts.
    textColor: '#666488',       //Font color for texts.
    textSize: 18,               //Font size for texts.
    textWeight: 'normal',       //Font weight for texts.

    //Border
    radius: 10,                 //Border radius to create round corners.
    borderWidth: 1,             //Border width.
    borderStyle: 'solid',       //Border style.
    borderColor: '#e9e8ee',     //Border color.

    //Shadow
    shadow: {elevation: 0},     //The component's elevation creating a shadow. Value from 0 to 20.
};

These variables are then clustered into what we can flavors. A theme can have multiple flavors which in turn can have multiple variables. A flavor is a cluster of theme variables that possess a value differing from the default one. Out of the box, oneJS provides the following selection of flavors:

const themeFlavors = {
    //Shadow
    shadow: {
        shadow: {elevation: 10},
    },
    noShadow: {
        shadow: {elevation: 0},
    },
    //Border
    border: {
        borderWidth: 1,
        borderStyle: 'solid',
        borderColor: themeVariables?.borderColor,
    },
    noBorder: {
        borderWidth: 0,
        borderStyle: 'none',
        borderColor: 'transparent',
    },
    flat: {
        shadow: {elevation: 0},
        borderWidth: 0,
        borderColor: 'transparent',
        radius: 0
    },
    //Text     
    bold: {
        textWeight: 'bold'
    },
    normal: {
        textWeight: 'normal'
    },
    title: {
        textWeight: 'bold',
        textColor: themeVariables?.darkColor,
        textSize: typeof themeVariables?.textSize === 'number' ?
            themeVariables?.textSize * 3 : '300%'
    },
    header: {
        textWeight: 'bold',
        textColor: themeVariables?.darkColor,
        textSize: typeof themeVariables?.textSize === 'number' ?
            themeVariables?.textSize * 2 : '200%'
    },
    section: {
        textWeight: 'bold',
        textColor: themeVariables?.darkColor,
        textSize: typeof themeVariables?.textSize === 'number' ?
            Math.round(themeVariables?.textSize * 1.5) : '200%'
    },
    subsection: {
        textWeight: 'bold',
        textColor: themeVariables?.darkColor
    },
    summary: {
        textSize: typeof themeVariables?.textSize === 'number' ?
            Math.round(themeVariables?.textSize * 1.3) : '200%'
    },
    //Color 
    reverse: {
        primaryColor: themeVariables?.backgroundColor,
        textColor: themeVariables?.backgroundColor,
        backgroundColor: themeVariables?.primaryColor,
    },
    selected: {
        primaryColor: themeVariables?.primaryColor,
        textColor: themeVariables?.primaryColor, 
    },
    unselected: {
        primaryColor: themeVariables?.neutralColor,
        textColor: themeVariables?.neutralColor,
    },
    primary: {
        primaryColor: themeVariables?.primaryColor,
        textColor: themeVariables?.primaryColor
    },
    primaryBackground: {
        backgroundColor: themeVariables?.primaryColor,
    },
    white: {
        primaryColor: '#fff',
        textColor: '#fff'
    },
    whiteBackground: {
        backgroundColor: '#fff',
    },
    light: {
        primaryColor: themeVariables?.lightColor,
        textColor: themeVariables?.lightColor
    },
    lightBackground: {
        backgroundColor: themeVariables?.lightColor
    },
    neutral: {
        primaryColor: themeVariables?.neutralColor,
        textColor: themeVariables?.neutralColor
    },
    neutralBackground: {
        backgroundColor: themeVariables?.neutralColor,
    },
    dark: {
        primaryColor: themeVariables?.darkColor,
        textColor: themeVariables?.darkColor
    },
    darkBackground: {
        backgroundColor: themeVariables?.darkBackground,
    },
    accept: {
        primaryColor: themeVariables?.acceptColor,
        textColor: themeVariables?.acceptBackground
    },
    acceptBackground: {
        backgroundColor: themeVariables?.acceptBackground,
    },
    reject: {
        primaryColor: themeVariables?.rejectColor,
        textColor: themeVariables?.rejectColor
    },
    rejectBackground: {
        backgroundColor: themeVariables?.rejectColor,
    },
    warn: {
        primaryColor: themeVariables?.warnColor,
        textColor: themeVariables?.warnColor
    },
    warnBackground: {
        backgroundColor: themeVariables?.warnColor,
    }
};

To use theme flavors in your app, you need to first provide a configuration object to the App component. This configuration object specifies the following parameters:

const themeVariables = {
    backgroundColor: 'white',
    primaryColor: 'blue',
    neutralColor: 'gray',
    textSize: 18,
    textWeight: 'normal',
    radius: 10,  
    borderWidth: 1,
    borderStyle: 'solid'
    borderColor: 'black',
    shadow: {elevation: 10},
};
const themeFlavors = {
    flat: {
        radius: 0,
        borderWidth: 0,
        borderColor: 'transparent',
        shadow: {elevation: 0}
    },
    title: {
        textSize: 32,
        fontWeight: 'bold'
    }
};
app({component: App, theme: {variables: themeVariables, flavors: themeFlavors}});

Applying Flavors to Components

Now we need a way of applying these flavors to components. All oneJS components contain the flavor property; use the readFlavor function with the flavor id to retrieve the corresponding theme variables.

/* Imports */
import {app, readFlavor} from '@onejs-dev/core';
import {View, Input} from '@onejs-dev/components';

/* Theming */
const flavors = {
    button1Flavor: {textColor: 'white', radius: 30, backgroundColor: 'blue'}
};

/* App Component: Takes no properties and returns structure */
const App = () => {
    return  View()([
        Input({type: 'button', title: 'Button 1', flavor: readFlavor('button1Flavor')}),
    ]);
};

/* App Function: Renders the App Component in the screen */
app({component: App, theme: {flavors: flavors}});

You may wonder, what can be accomplished with oneJS themes that you couldn’t with CSS classes. There is a critical difference, CSS classes talk the language of CSS properties while oneJS themes talk the language of designers and content creators and are coding language agnostic. Defining your own flavors is intuitive and you are not required to know anything about the underlying language. See the oneJS preset flavors in action. Spice up your creation with different flavors!

/* Imports */
import {app, readFlavor} from '@onejs-dev/core';
import {View, Input} from '@onejs-dev/components';

const presetFlavors = [
    //Outline
    'default', 'shadow', 'noShadow', 'border', 'noBorder', 'flat', 
    //Text
    'bold', 'normal', 'title', 'header', 'section', 'subsection', 'summary', 
    //Selection
    'reverse', 'selected', 'unselected', 
    //Colors
    'primary', 'primaryBackground', 'white', 'whiteBackground', 
    'light', 'lightBackground',  'neutral', 'neutralBackground', 
    'dark', 'darkBackground', 'accept', 'acceptBackground',
    'reject', 'rejectBackground', 'warn', 'warnBackground'
];

/* App Component: Takes no properties and returns structure */
const App = () => {
    return  View({content: {wrap: true, gap: 10}})([
        presetFlavors.map(flavor => Input({
            type: 'button', title: flavor, key: flavor, flavor: readFlavor(flavor)
        }))
    ]);
};

/* App Function: Renders the App Component in the screen */
app({component: App, theme: {preset: 'oneJS'}});

Gradients

One unique feature offered by oneJS is extensive support for linear gradients. You may have worked with gradients before, and there is a high change that you have found it quite cumbersome. We wanted to simplify this process by providing an intuitive and unified way of working with them. Gradients have many ways of being specified; CSS, SVG and React Native each follow their own convention. In oneJS we provide a wrapper for all of them following a concept you may already be familiar with, the unit circle. To create a gradient you need to specify the following data; an angle in degrees and an array with two or more colors. The direction of the gradient is given by the angle θ in the unit circle. Colors go from origin (center of the circumference) to terminal point (tip of the vector) and are evenly distributed along the length of the vector. For example, a 45° linear gradient from purple to blue and from blue to red would look like the following images:

In oneJS, gradients are created as part of flavors. You can apply gradients to texts, backgrounds and icons by specifying the textGradient, backgroundGradient and primaryGradient theme variables respectively.

/* Imports */
import {app, readFlavor} from '@onejs-dev/core';
import {View, Text, Icon} from '@onejs-dev/components';
import {icons} from '@onejs-dev/icons';

/* Flavor Definition */
const themeFlavors = {
    myGradient: {
        textGradient: {angle: 0, colors: ['green', 'blue']},
        primaryGradient: {angle: 45, colors: ['pink', 'red']},
        backgroundGradient: {angle: 90, colors: ['silver', 'white']}
    }
};

/* App Component: Takes no properties and returns structure */
const App = () => {
    return  View({flavor: readFlavor('myGradient')})([
        Text({flavor: readFlavor('myGradient')})('Create beautiful gradients'),
        Icon({icon: icons['heart'], flavor: readFlavor('myGradient')})
    ]);
};

/* App Function: Renders the App Component in the screen */
app({component: App, theme: {flavors: themeFlavors}});

To add the icing on the cake, oneJS automatically creates the best looking gradients for the colors you choose for your theme. For example, if you have defined a theme variable named secondaryColor oneJS automatically creates a flavor named secondaryColorGradient that you can apply to your components. See below the gradients for the preset theme variables:

/* Imports */
import {app, readFlavor} from '@onejs-dev/core';
import {View, Input} from '@onejs-dev/components';

const presetGradients = [
    'primaryGradient', 'whiteGradient',
    'lightGradient', 'neutralGradient', 
    'darkGradient', 'acceptGradient',
    'rejectGradient', 'warnGradient',
];

/* App Component: Takes no properties and returns structure */
const App = () => {
    return  View({content: {wrap: true, gap: 10}})([
        presetGradients.map(flavor => Input({
            type: 'button', title: flavor, key: flavor, flavor: readFlavor(flavor)
        }))
    ]);
};

/* App Function: Renders the App Component in the screen */
app({component: App, theme: {preset: 'oneJS'}});

Congratulations! You have completed one of the most important units. Now you should be able to fully customize your app to your liking. Put all these new concepts to the test!