44 |
45 |
46 | ## Usage
47 |
48 | ### `useStyles()`
49 |
50 | ```jsx
51 | // Button.js
52 | import { useStyles } from "@andywer/style-hook"
53 | import React from "react"
54 |
55 | export function Button (props) {
56 | const classNames = useStyles({
57 | button: {
58 | padding: "0.6em 1.2em",
59 | background: theme => theme.button.default.background,
60 | color: theme => theme.button.default.textColor,
61 | border: "none",
62 | boxShadow: "0 0 0.5em #b0b0b0",
63 | "&:hover": {
64 | boxShadow: "0 0 0.5em #e0e0e0"
65 | }
66 | },
67 | buttonPrimary: {
68 | background: theme => theme.button.primary.background,
69 | color: theme => theme.button.primary.textColor
70 | }
71 | })
72 |
73 | const className = `${classNames.button} ${props.primary ? classNames.buttonPrimary : ""}`
74 | return (
75 |
78 | )
79 | }
80 | ```
81 |
82 | Rendering your app with style hook support is easy:
83 | Add a provider for the styling library at the top of your app.
84 |
85 | ```jsx
86 | // App.js
87 | import { JssProvider } from "@andywer/style-api-jss"
88 | import React from "react"
89 | import ReactDOM from "react-dom"
90 |
91 | const App = () => (
92 |
93 | {/* ... */}
94 |
95 | )
96 |
97 | ReactDOM.render(, document.getElementById("app"))
98 | ```
99 |
100 | ### `useStyle()`
101 |
102 | This is a convenience function to define a single CSS class.
103 |
104 | ```jsx
105 | import { useStyle } from "@andywer/style-hook"
106 | import React from "react"
107 |
108 | export function Button (props) {
109 | const className = useStyle({
110 | padding: "0.6em 1.2em",
111 | boxShadow: "0 0 0.5em #b0b0b0",
112 | "&:hover": {
113 | boxShadow: "0 0 0.5em #e0e0e0"
114 | }
115 | })
116 | return (
117 |
120 | )
121 | }
122 | ```
123 |
124 | ### Theming & props
125 |
126 | ```jsx
127 | import { useStyles } from "@andywer/style-hook"
128 | import React from "react"
129 |
130 | export function Button (props) {
131 | const classNames = useStyles({
132 | button: {
133 | background: theme => theme.button.default.background,
134 | color: theme => theme.button.default.textColor,
135 | border: () => props.border || "none",
136 | boxShadow: "0 0 0.5em #b0b0b0",
137 | "&:hover": {
138 | boxShadow: "0 0 0.5em #e0e0e0"
139 | }
140 | },
141 | buttonPrimary: {
142 | background: theme => theme.button.primary.background,
143 | color: theme => theme.button.primary.textColor
144 | }
145 | }, [props.border])
146 |
147 | const className = `${classNames.button} ${props.primary ? classNames.buttonPrimary : ""}`
148 | return (
149 |
152 | )
153 | }
154 | ```
155 |
156 | For details see the [`style-hook` package readme](https://github.com/andywer/react-usestyles/blob/master/packages/style-hook/README.md).
157 |
158 |
159 | ## Details
160 |
161 | **For more details about the API and usage instructions, check out the [`style-hook` package readme](./packages/style-hook/README.md).**
162 |
163 |
164 | ## API
165 |
166 | - [`style-hook`](./packages/style-hook) - The main package providing the hooks
167 | - [`style-api`](./packages/style-api) - Defines the API between styling library provider and hooks (internal)
168 | - [`style-api-jss`](./packages/style-api-jss) - The styling library provider for [JSS](http://cssinjs.org/)
169 |
170 |
171 | ## License
172 |
173 | MIT
174 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/*",
4 | "samples/*"
5 | ],
6 | "version": "0.1.0"
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-use-styles",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "build": "lerna run build",
7 | "watch": "lerna run watch",
8 | "release": "yarn build && lerna publish",
9 | "sample:serve": "cd samples/app/ && yarn dev"
10 | },
11 | "workspaces": [
12 | "packages/*",
13 | "samples/*"
14 | ],
15 | "devDependencies": {
16 | "@babel/cli": "^7.1.2",
17 | "@babel/core": "^7.1.2",
18 | "@babel/preset-env": "^7.1.0",
19 | "@babel/preset-react": "^7.0.0",
20 | "lerna": "^3.4.3",
21 | "react": "^16.7.0-alpha.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/style-api-jss/README.md:
--------------------------------------------------------------------------------
1 | # style-api-jss
2 |
3 | This package provides the glue to render the styles defined using the hooks using [JSS](http://cssinjs.org/).
4 |
5 | Check out the [project readme](https://github.com/andywer/react-usestyles) for more information.
6 |
7 |
8 | ## Installation
9 |
10 | ```sh
11 | $ npm install react@next react-dom@next @andywer/style-hook @andywer/style-api-jss
12 | ```
13 |
14 |
15 | ## Usage
16 |
17 | ```jsx
18 | // App.js
19 | import { JssProvider } from "@andywer/style-api-jss"
20 | import { ThemeContext } from "@andywer/style-hook"
21 | import React from "react"
22 | import ReactDOM from "react-dom"
23 | import Button from "./Button"
24 |
25 | const myTheme = {
26 | button: {
27 | default: {
28 | background: "#ffffff",
29 | textColor: "#000000"
30 | },
31 | primary: {
32 | background: "#3080ff",
33 | textColor: "#ffffff"
34 | }
35 | }
36 | }
37 |
38 | const App = () => (
39 |
40 |
41 |
42 |
43 |
44 | )
45 |
46 | ReactDOM.render(, document.getElementById("app"))
47 | ```
48 |
49 | ## Server-side rendering
50 |
51 | ```jsx
52 | // App.js
53 | import { JssProvider, SheetsRegistry } from "@andywer/style-api-jss"
54 | import React from "react"
55 | import ReactDOM from "react-dom"
56 |
57 | const registry = new SheetsRegistry()
58 |
59 | const App = () => (
60 |
61 | {/* ... */}
62 |
63 | )
64 |
65 | const appHTML = ReactDOM.renderToString()
66 | const appCSS = registry.toString()
67 | ```
68 |
69 |
70 | ## License
71 |
72 | MIT
73 |
--------------------------------------------------------------------------------
/packages/style-api-jss/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@andywer/style-api-jss",
3 | "version": "0.1.0",
4 | "author": "Andy Wermke (https://github.com/andywer)",
5 | "main": "lib/index.js",
6 | "license": "MIT",
7 | "repository": "andywer/react-usestyles",
8 | "scripts": {
9 | "build": "babel src/ -d lib/ --root-mode upward",
10 | "prepare": "build",
11 | "watch": "yarn build -- --watch"
12 | },
13 | "peerDependencies": {
14 | "react": ">= 16.7.0 || >= 16.7.0-alpha.0"
15 | },
16 | "dependencies": {
17 | "@andywer/style-api": "^0.1.0",
18 | "jss": "^9.8.7",
19 | "jss-preset-default": "^4.5.0"
20 | },
21 | "files": [
22 | "lib/**"
23 | ]
24 | }
25 |
--------------------------------------------------------------------------------
/packages/style-api-jss/src/index.jsx:
--------------------------------------------------------------------------------
1 | import { CssInJsProvider } from "@andywer/style-api"
2 | import { create, SheetsManager, SheetsRegistry } from "jss"
3 | import defaultPreset from "jss-preset-default"
4 | import React, { useState } from "react"
5 | import { getStaticStyles, isStaticStylesOnly, resolveStyles } from "./style-utils"
6 |
7 | export { SheetsRegistry }
8 |
9 | /**
10 | * Depending on the style hook's `inputs` argument (2nd argument), create a key
11 | * for the sheet, so that other useStyles() hook invocations working with the
12 | * same styles and updating in the same situations, share a key.
13 | *
14 | * That approach ensures that we can safely mutate the stylesheets.
15 | */
16 | function createSheetMeta (styles, themeID, inputs) {
17 | const randomSheetID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
18 |
19 | if (Array.isArray(inputs) && inputs.length === 0) {
20 | if (isStaticStylesOnly(styles)) {
21 | // No function rules, no reaction to prop changes: Use stringified styles as key
22 | return {
23 | key: JSON.stringify(styles),
24 | isStatic: true
25 | }
26 | } else {
27 | // Function rules, but no reaction to prop changes:
28 | // Return a key that is the same for all similarly-styled React elements that use the same theme
29 | return {
30 | key: JSON.stringify(themeID) + ":" + JSON.stringify(getStaticStyles(styles)),
31 | isStatic: false
32 | }
33 | }
34 | } else {
35 | // Update styles on some specific or every prop changes:
36 | // Return always-unique key, since prop changes are unique to every React element
37 | return {
38 | key: randomSheetID,
39 | isStatic: false
40 | }
41 | }
42 | }
43 |
44 | function mutateJssSheet (jssSheet, updatedStyles) {
45 | const prevClassNames = Object.keys(jssSheet.classes)
46 |
47 | // TODO: Update only the rules that changed
48 |
49 | for (const prevClassName of prevClassNames) {
50 | jssSheet.deleteRule(prevClassName)
51 | }
52 |
53 | jssSheet.addRules(updatedStyles)
54 | }
55 |
56 | function createUnifiedSheet (meta, jss, manager, registry, styles, theme, jssSheetOptions) {
57 | const resolvedStyles = resolveStyles(styles, theme)
58 | const jssSheet = manager.get(meta.key) || jss.createStyleSheet(resolvedStyles, jssSheetOptions || {})
59 | const latestStylesFingerprint = JSON.stringify(resolvedStyles)
60 |
61 | manager.add(meta.key, jssSheet)
62 |
63 | if (registry) {
64 | registry.add(jssSheet)
65 | }
66 |
67 | const sheet = {
68 | attached: false,
69 | latestStylesFingerprint,
70 | meta,
71 |
72 | attach () {
73 | manager.manage(meta.key)
74 | sheet.attached = true
75 | },
76 | detach () {
77 | manager.unmanage(meta.key)
78 | sheet.attached = false
79 | },
80 | getClassNames () {
81 | return jssSheet.classes
82 | },
83 | toString () {
84 | return jssSheet.toString()
85 | },
86 | update (currentStyles, currentTheme) {
87 | // TODO: Only when in development:
88 | // Warn if getStaticStyles(currentStyles) does not match getStaticStyles(styles)
89 |
90 | if (sheet.meta.isStatic) return
91 |
92 | const updatedResolvedStyles = resolveStyles(currentStyles, currentTheme)
93 | const updatedStylesFingerprint = JSON.stringify(updatedResolvedStyles)
94 |
95 | if (updatedStylesFingerprint === sheet.latestStylesFingerprint) return
96 |
97 | mutateJssSheet(jssSheet, updatedResolvedStyles)
98 | sheet.latestStylesFingerprint = updatedStylesFingerprint
99 | }
100 | }
101 | return sheet
102 | }
103 |
104 | export function JssProvider (props) {
105 | const [{ jss, manager }] = useState({
106 | jss: create(defaultPreset()),
107 | manager: new SheetsManager()
108 | })
109 |
110 | const createSheet = (styles, themeID, theme, inputs) => {
111 | const sheetMeta = createSheetMeta(styles, themeID, inputs)
112 | return createUnifiedSheet(sheetMeta, jss, manager, props.registry, styles, theme, props.sheetOptions || {})
113 | }
114 |
115 | return (
116 |
117 | {props.children}
118 |
119 | )
120 | }
121 |
--------------------------------------------------------------------------------
/packages/style-api-jss/src/style-utils.js:
--------------------------------------------------------------------------------
1 | export function getStaticStyles (styles) {
2 | const statics = {}
3 |
4 | for (const key of Object.keys(styles)) {
5 | const value = styles[key]
6 | if (typeof value === "object" && value) {
7 | statics[key] = getStaticStyles(value)
8 | } else if (typeof value !== "function") {
9 | statics[key] = value
10 | }
11 | }
12 |
13 | return statics
14 | }
15 |
16 | export function isStaticStylesOnly (styles) {
17 | for (const key of Object.keys(styles)) {
18 | const value = styles[key]
19 | if (typeof value === "object" && value) {
20 | if (!isStaticStylesOnly(value)) return false
21 | } else if (typeof value === "function") {
22 | return false
23 | }
24 | }
25 | return true
26 | }
27 |
28 | export function resolveStyles (styles, theme) {
29 | const resolved = {}
30 |
31 | for (const key of Object.keys(styles)) {
32 | const value = styles[key]
33 | if (typeof value === "object" && value) {
34 | resolved[key] = resolveStyles(value, theme)
35 | } else if (typeof value === "function") {
36 | resolved[key] = value(theme)
37 | } else {
38 | resolved[key] = value
39 | }
40 | }
41 |
42 | return resolved
43 | }
44 |
--------------------------------------------------------------------------------
/packages/style-api/README.md:
--------------------------------------------------------------------------------
1 | # style-api
2 |
3 | This package is for internal use only. It defines the API between the hooks and the styling library providers.
4 |
5 | Check out the [project readme](https://github.com/andywer/react-usestyles) for more information.
6 |
7 |
8 | ## License
9 |
10 | MIT
11 |
--------------------------------------------------------------------------------
/packages/style-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@andywer/style-api",
3 | "version": "0.1.0",
4 | "author": "Andy Wermke (https://github.com/andywer)",
5 | "license": "MIT",
6 | "main": "lib/index.js",
7 | "repository": "andywer/react-usestyles",
8 | "scripts": {
9 | "build": "babel src/ -d lib/ --root-mode upward",
10 | "prepare": "build",
11 | "watch": "yarn build -- --watch"
12 | },
13 | "peerDependencies": {
14 | "react": ">= 16.7.0 || >= 16.7.0-alpha.0"
15 | },
16 | "files": [
17 | "lib/**"
18 | ]
19 | }
20 |
--------------------------------------------------------------------------------
/packages/style-api/src/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState } from "react"
2 |
3 | export const CssInJsContext = createContext()
4 |
5 | export function CssInJsProvider (props) {
6 | const [state] = useState({
7 | createSheet: props.createSheet
8 | })
9 | return (
10 |
11 | {props.children}
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/packages/style-hook/README.md:
--------------------------------------------------------------------------------
1 | # style-hook
2 |
3 | This is the main user-facing package. It contains the hooks: `useStyles()`, `useStyle()`, `useGlobalStyles()`.
4 |
5 | Check out the [project readme](https://github.com/andywer/react-usestyles) for more information.
6 |
7 |
8 | ## Installation
9 |
10 | ```sh
11 | $ npm install react@next @andywer/style-hook
12 | ```
13 |
14 |
15 | ## Usage
16 |
17 | ### `useStyles()`
18 |
19 | ```jsx
20 | // Button.js
21 | import { useStyles } from "@andywer/style-hook"
22 | import React from "react"
23 |
24 | export function Button (props) {
25 | const classNames = useStyles({
26 | button: {
27 | padding: "0.6em 1.2em",
28 | border: "none",
29 | boxShadow: "0 0 0.5em #b0b0b0",
30 | "&:hover": {
31 | boxShadow: "0 0 0.5em #e0e0e0"
32 | }
33 | }
34 | })
35 |
36 | return (
37 |
40 | )
41 | }
42 | ```
43 |
44 | Will yield the following CSS:
45 |
46 | ```css
47 | .button-0-1-2 {
48 | border: none;
49 | padding: 0.6em 1.2em;
50 | background: #3080ff;
51 | box-shadow: 0 0 0.5em #b0b0b0;
52 | }
53 | .button-0-1-2:hover {
54 | box-shadow: 0 0 0.5em #e0e0e0;
55 | }
56 | ```
57 |
58 |
59 | ### `useStyle()`
60 |
61 | This is a convenience function for when you just need to define one CSS class. This is a very frequent use case.
62 |
63 | ```jsx
64 | // Button.js
65 | import { useStyle } from "@andywer/style-hook"
66 | import React from "react"
67 |
68 | export function Button (props) {
69 | const className = useStyle({
70 | padding: "0.6em 1.2em",
71 | border: "none",
72 | boxShadow: "0 0 0.5em #b0b0b0",
73 | "&:hover": {
74 | boxShadow: "0 0 0.5em #e0e0e0"
75 | }
76 | })
77 | return (
78 |
81 | )
82 | }
83 | ```
84 |
85 |
86 | ### Dynamic styles & themes
87 |
88 | ```jsx
89 | // Button.js
90 | import { useStyles } from "@andywer/style-hook"
91 | import React from "react"
92 |
93 | export function Button (props) {
94 | const classNames = useStyles({
95 | button: {
96 | padding: "0.6em 1.2em",
97 | background: theme => theme.button.default.background,
98 | color: theme => theme.button.default.textColor,
99 | border: () => props.border || "none",
100 | boxShadow: "0 0 0.5em #b0b0b0",
101 | "&:hover": {
102 | boxShadow: "0 0 0.5em #e0e0e0"
103 | }
104 | },
105 | buttonPrimary: {
106 | background: theme => theme.button.primary.background,
107 | color: theme => theme.button.primary.textColor
108 | }
109 | }, [props.border])
110 |
111 | const className = [classNames.button, props.primary && classNames.buttonPrimary].join(" ")
112 | return (
113 |
116 | )
117 | }
118 | ```
119 |
120 | All dynamic style rules (that means all rules in a style object that can potentially change during runtime) **MUST** have function values. Those functions receive the current theme (see `App` below) as argument and return the style rule value.
121 |
122 | You might be wondering what the second argument, the array, passed to `useStyles()` is good for. It is an optimization that is common among React hooks: Pass a list of all variables that this style object's dynamic rules depend on.
123 |
124 | If such an array is provided, the expensive CSS update will only be performed if one of those listed variables changed. The styles will always be updated if another theme is selected (see `App` below). Pass an empty array to indicate that a style update is only necessary on theme change.
125 |
126 |
127 | ### `useGlobalStyles()`
128 |
129 | ```jsx
130 | // BodyStyles.js
131 | import { useGlobalStyles } from "@andywer/style-hook"
132 | import React from "react"
133 |
134 | export function BodyStyles (props) {
135 | useGlobalStyles({
136 | "body": {
137 | margin: 0,
138 | padding: 0
139 | },
140 | "#app": {
141 | width: "100%",
142 | height: "100%",
143 | minHeight: "100vh"
144 | }
145 | }, [])
146 | return props.children || null
147 | }
148 | ```
149 |
150 | ### App
151 |
152 | ```jsx
153 | // App.js
154 | import { JssProvider } from "@andywer/style-api-jss"
155 | import { ThemeContext } from "@andywer/style-hook"
156 | import React from "react"
157 | import ReactDOM from "react-dom"
158 | import Button from "./Button"
159 |
160 | const myTheme = {
161 | button: {
162 | default: {
163 | background: "#ffffff",
164 | textColor: "#000000"
165 | },
166 | primary: {
167 | background: "#3080ff",
168 | textColor: "#ffffff"
169 | }
170 | }
171 | }
172 |
173 | const App = () => (
174 |
175 |
176 |
177 |
178 |
179 | )
180 |
181 | ReactDOM.render(, document.getElementById("app"))
182 | ```
183 |
184 | The `JssProvider` that renders the styles using [JSS](https://github.com/cssinjs/react-jss) has been implemented as a first proof-of-concept styling library integration.
185 |
186 |
187 | ### Server-side rendering
188 |
189 | Works the same way as it always did with the styling library (in this case JSS) you use.
190 |
191 | ```jsx
192 | // App.js
193 | import { JssProvider, SheetsRegistry } from "@andywer/style-api-jss"
194 | import React from "react"
195 | import ReactDOM from "react-dom"
196 |
197 | const registry = new SheetsRegistry()
198 |
199 | const App = () => (
200 |
201 | {/* ... */}
202 |
203 | )
204 |
205 | const appHTML = ReactDOM.renderToString()
206 | const appCSS = registry.toString()
207 | ```
208 |
209 |
210 | ## Open questions
211 |
212 | ### CSS injection order
213 |
214 | A big topic in CSS-in-JS is the injection order: The order in which to write component styles to the stylesheets in the DOM. Even with plain old CSS styles you have to think about the order of your styles if multiple selectors might match the same element.
215 |
216 | The default opinion in the community seems to be that an injection order based on component render order will not be stable enough and is likely to lead to styling issues. There are ways to define the injection order based on module resolution order instead, but they imply providing a less friendly API to the user.
217 |
218 | So this project is build around the idea of injecting styles based on render order nevertheless. The same issue seemed to be [one of the earliest issues in styled-components](https://github.com/styled-components/styled-components/issues/1) as well, but at least at that time they came to the conclusion that those edge cases are only hurtful if the user tries to implement styling anti-patterns.
219 |
220 | Only use the CSS class names returned by `useStyles()`, don't try to add selectors that match certain child tags. You should be fine 😉
221 |
222 | Use the styling hook, use it in different scenarios and try to break it. If you manage to break it whithout implementing anti-patterns, please open an issue and share 🐛
223 |
224 |
225 | ## License
226 |
227 | MIT
228 |
--------------------------------------------------------------------------------
/packages/style-hook/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@andywer/style-hook",
3 | "version": "0.1.1",
4 | "author": "Andy Wermke (https://github.com/andywer)",
5 | "main": "lib/index.js",
6 | "license": "MIT",
7 | "repository": "andywer/react-usestyles",
8 | "scripts": {
9 | "build": "babel src/ -d lib/ --root-mode upward",
10 | "prepare": "build",
11 | "watch": "yarn build -- --watch"
12 | },
13 | "peerDependencies": {
14 | "@andywer/style-api": "0.x",
15 | "react": ">= 16.7.0 || >= 16.7.0-alpha.0"
16 | },
17 | "dependencies": {
18 | "theming": "^2.1.2"
19 | },
20 | "files": [
21 | "lib/**"
22 | ]
23 | }
24 |
--------------------------------------------------------------------------------
/packages/style-hook/src/index.js:
--------------------------------------------------------------------------------
1 | import { CssInJsContext } from "@andywer/style-api"
2 | import { useContext, useMemo, useMutationEffect, useState } from "react"
3 | import { ThemeContext } from "theming"
4 |
5 | export { ThemeContext }
6 |
7 | function useStylesInternal (styles, inputs) {
8 | const cssInJs = useContext(CssInJsContext)
9 | const theme = useContext(ThemeContext) || {}
10 |
11 | if (!cssInJs) {
12 | throw new Error("No CSS-in-JS implementation found in context.")
13 | }
14 | if (!theme._id) {
15 | // Hacky! Just give every theme we see an ID, so we can tell them apart easily
16 | theme._id = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
17 | }
18 |
19 | const [sheet] = useState(() => cssInJs.createSheet(styles, theme._id, theme, inputs))
20 |
21 | useMutationEffect(() => {
22 | sheet.attach()
23 | return () => sheet.detach()
24 | }, [])
25 |
26 | // Misusing useMemo here to synchronously sheet.update() only if styles or theme changed
27 | useMemo(() => {
28 | if (sheet.attached) {
29 | sheet.update(styles, theme)
30 | }
31 | }, inputs ? [theme, ...inputs] : [theme, Math.random()])
32 |
33 | return sheet.getClassNames()
34 | }
35 |
36 | function transformIntoGlobalStyles (styles) {
37 | const transformed = {}
38 |
39 | for (const key of Object.keys(styles)) {
40 | transformed[`@global ${key}`] = styles[key]
41 | }
42 |
43 | return transformed
44 | }
45 |
46 | function wrapStyleCallback (styleCallback, transformStyles) {
47 | // Don't just pass-through arbitrary arguments, since we check function.length in useStyles()
48 | if (styleCallback.length === 0) {
49 | return () => transformStyles(styleCallback())
50 | } else if (styleCallback.length === 1) {
51 | return (theme) => transformStyles(styleCallback(theme))
52 | } else if (styleCallback.length === 2) {
53 | return (theme, props) => transformStyles(styleCallback(theme, props))
54 | } else {
55 | return (...args) => transformStyles(styleCallback(...args))
56 | }
57 | }
58 |
59 | export function useStyles (styles, inputs = undefined) {
60 | return useStylesInternal(styles, inputs)
61 | }
62 |
63 | export function useStyle (style, inputs = undefined) {
64 | return useStylesInternal({ style }, inputs).style
65 | }
66 |
67 | export function useGlobalStyles (styles, inputs = undefined) {
68 | const transformedStyles = transformIntoGlobalStyles(styles)
69 | useStylesInternal(transformedStyles, inputs)
70 | }
71 |
--------------------------------------------------------------------------------
/samples/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sample-app",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "parcel serve src/index.html"
7 | },
8 | "dependencies": {
9 | "@andywer/style-api-jss": "^0.1.0",
10 | "@andywer/style-hook": "^0.1.0",
11 | "react": "^16.7.0-alpha.0",
12 | "react-dom": "^16.7.0-alpha.0"
13 | },
14 | "devDependencies": {
15 | "parcel-bundler": "^1.10.3"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/samples/app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/samples/app/src/index.js:
--------------------------------------------------------------------------------
1 | import { useGlobalStyles, useStyle, useStyles, ThemeContext } from "@andywer/style-hook"
2 | import { JssProvider, SheetsRegistry } from "@andywer/style-api-jss"
3 | import React, { useState } from "react"
4 | import ReactDOM from "react-dom"
5 |
6 | const themeDark = {
7 | body: {
8 | background: "#505050"
9 | },
10 | button: {
11 | default: {
12 | background: "#303030",
13 | textColor: "#ffffff"
14 | },
15 | primary: {
16 | background: "#303030",
17 | textColor: "#f65151"
18 | }
19 | }
20 | }
21 |
22 | const themeLight = {
23 | body: {
24 | background: "#ffffff"
25 | },
26 | button: {
27 | default: {
28 | background: "#ffffff",
29 | textColor: "#000000"
30 | },
31 | primary: {
32 | background: "#3080ff",
33 | textColor: "#ffffff"
34 | }
35 | }
36 | }
37 |
38 | function GlobalStyles () {
39 | useGlobalStyles({
40 | "body": {
41 | margin: 0,
42 | padding: 0,
43 | background: theme => theme.body.background,
44 | transition: "background .2s"
45 | },
46 | "#app": {
47 | width: "100%",
48 | height: "100%",
49 | minHeight: "100vh"
50 | }
51 | }, [])
52 | return null
53 | }
54 |
55 | function Button (props) {
56 | const classNames = useStyles({
57 | default: {
58 | padding: "0.6em 1.2em",
59 | background: theme => theme.button.default.background,
60 | color: theme => theme.button.default.textColor,
61 | cursor: "pointer",
62 | border: "none",
63 | boxShadow: "0 0 0.5em #b0b0b0",
64 | fontSize: 14,
65 | "&:hover": {
66 | boxShadow: "0 0 0.5em #e0e0e0"
67 | },
68 | "&:focus": {
69 | boxShadow: "0 0 0.5em #e0e0e0",
70 | outline: "none"
71 | }
72 | },
73 | primary: {
74 | background: theme => theme.button.primary.background,
75 | color: theme => theme.button.primary.textColor
76 | }
77 | }, [])
78 |
79 | const className = [
80 | classNames.default,
81 | props.primary && classNames.primary,
82 | props.className || ""
83 | ].join(" ")
84 | return (
85 |
88 | )
89 | }
90 |
91 | function StrangeButton (props) {
92 | const className = useStyle({ background: "green" })
93 | return (
94 |
97 | )
98 | }
99 |
100 | function Container (props) {
101 | const className = useStyle(() => props.css, [props.css])
102 | return (
103 |
104 | {props.children}
105 |
106 | )
107 | }
108 |
109 | function FullSizeContainer (props) {
110 | return (
111 |
112 | {props.children}
113 |
114 | )
115 | }
116 |
117 | function App () {
118 | const [themeName, setActiveTheme] = useState("light")
119 | const toggleTheme = () => setActiveTheme(themeName === "light" ? "dark" : "light")
120 | return (
121 |
122 |
123 |
124 |
125 |
126 | Strange button
127 |
128 |
131 |
134 |
135 |
136 |
137 | )
138 | }
139 |
140 | window.registry = new SheetsRegistry()
141 |
142 | ReactDOM.render(, document.getElementById("app"))
143 |
144 | console.log("Aggregated styles for SSR:\n", window.registry.toString())
145 |
--------------------------------------------------------------------------------