├── .npmignore ├── .gitignore ├── src ├── index.tsx ├── flashless.ts └── FlashlessScript.tsx ├── tsconfig.json ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './FlashlessScript'; 2 | export * from './flashless'; 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "es2017" 6 | ], 7 | "jsx": "react", 8 | "esModuleInterop": true, 9 | "declaration": true, 10 | "outDir": "./lib" 11 | }, 12 | "files": [ 13 | "src/index.tsx" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chakra-ui-flashless", 3 | "version": "0.0.8", 4 | "description": "Tools to implement flashless color modes in Chakra UI", 5 | "author": "Trevor Blades ", 6 | "repository": "trevorblades/chakra-ui-flashless", 7 | "license": "MIT", 8 | "main": "lib/index.js", 9 | "keywords": [ 10 | "chakra-ui", 11 | "color", 12 | "chakra", 13 | "darkmode", 14 | "fouc", 15 | "flash" 16 | ], 17 | "eslintConfig": { 18 | "extends": "@trevorblades/eslint-config/typescript" 19 | }, 20 | "scripts": { 21 | "pretest": "eslint src/*", 22 | "test": "exit", 23 | "prepare": "tsc" 24 | }, 25 | "devDependencies": { 26 | "@chakra-ui/react": "^1.1.6", 27 | "@emotion/react": "^11.1.4", 28 | "@emotion/styled": "^11.0.0", 29 | "@trevorblades/eslint-config": "^7.2.1", 30 | "@types/lodash.merge": "^4.6.6", 31 | "@types/react": "^17.0.0", 32 | "eslint": "^7.18.0", 33 | "react": "^17.0.1", 34 | "typescript": "^4.1.3" 35 | }, 36 | "dependencies": { 37 | "lodash.merge": "^4.6.2", 38 | "outdent": "^0.8.0" 39 | }, 40 | "peerDependencies": { 41 | "@chakra-ui/react": "^1.1.6" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/flashless.ts: -------------------------------------------------------------------------------- 1 | import defaultTheme from '@chakra-ui/theme'; 2 | import merge from 'lodash.merge'; 3 | import {Dict} from '@chakra-ui/utils'; 4 | 5 | const Badge = { 6 | variants: { 7 | solid(props: Dict) { 8 | const {colorScheme: c} = props; 9 | return { 10 | bg: `var(--badge-solid-${c})`, 11 | color: 'var(--badge-text)' 12 | }; 13 | }, 14 | subtle(props: Dict) { 15 | const {colorScheme: c} = props; 16 | return { 17 | bg: `var(--badge-subtle-${c})`, 18 | color: `var(--badge-subtle-${c}-text)` 19 | }; 20 | }, 21 | outline(props: Dict) { 22 | const {colorScheme: c} = props; 23 | return { 24 | color: `var(--badge-outline-${c})`, 25 | boxShadow: `inset 0 0 0px 1px var(--badge-outline-${c})` 26 | }; 27 | } 28 | } 29 | }; 30 | 31 | function ghost(props: Dict) { 32 | const {colorScheme: c} = props; 33 | return { 34 | color: `var(--button-ghost-${c})`, 35 | _hover: {bg: `var(--button-ghost-${c}-hover)`}, 36 | _active: {bg: `var(--button-ghost-${c}-active)`} 37 | }; 38 | } 39 | 40 | export function flashless(theme: Dict = defaultTheme): Dict { 41 | return merge( 42 | { 43 | config: { 44 | useSystemColorMode: true 45 | }, 46 | styles: { 47 | global: { 48 | body: { 49 | bg: 'var(--bg)', 50 | color: 'var(--text)' 51 | }, 52 | '*::placeholder': { 53 | color: 'var(--placeholder-text)' 54 | }, 55 | '*, *::before, &::after': { 56 | borderColor: 'var(--border)' 57 | } 58 | } 59 | }, 60 | components: { 61 | Badge, 62 | Button: { 63 | variants: { 64 | ghost, 65 | solid: ({colorScheme: c}) => ({ 66 | color: c !== 'gray' && 'var(--bg)', 67 | bg: `var(--button-solid-${c})`, 68 | _hover: {bg: `var(--button-solid-${c}-hover)`}, 69 | _active: {bg: `var(--button-solid-${c}-active)`} 70 | }), 71 | outline: (props: Dict) => ({ 72 | borderColor: 73 | props.colorScheme === 'gray' ? 'var(--border)' : 'currentColor', 74 | ...ghost(props) 75 | }) 76 | } 77 | }, 78 | Tag: { 79 | variants: { 80 | subtle: (props: Dict) => ({ 81 | container: Badge.variants.subtle(props) 82 | }), 83 | solid: (props: Dict) => ({ 84 | container: Badge.variants.solid(props) 85 | }), 86 | outline: (props: Dict) => ({ 87 | container: Badge.variants.outline(props) 88 | }) 89 | } 90 | } 91 | } 92 | }, 93 | theme 94 | ); 95 | } 96 | -------------------------------------------------------------------------------- /src/FlashlessScript.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import {Dict} from '@chakra-ui/utils'; 3 | import {getColor, transparentize} from '@chakra-ui/theme-tools'; 4 | import {outdent} from 'outdent'; 5 | 6 | const baseVariables = { 7 | '--bg': ['white', 'gray.800'], 8 | '--text': ['gray.800', 'whiteAlpha.900'], 9 | '--placeholder-text': ['gray.400', 'whiteAlpha.400'], 10 | '--border': ['gray.200', 'whiteAlpha.300'], 11 | '--badge-text': ['white', 'whiteAlpha.800'] 12 | }; 13 | 14 | type Color = string | [string, number]; 15 | type Variables = Record; 16 | 17 | function createVariables(theme: Dict, customVariables?: Variables): string { 18 | function colorValue(color: Color) { 19 | return Array.isArray(color) 20 | ? transparentize(...color)(theme) 21 | : getColor(theme, color); 22 | } 23 | 24 | const defaultVariables = Object.entries(theme.colors) 25 | .filter(entries => typeof entries[1] === 'object') 26 | .reduce((acc, [c]) => { 27 | const isGray = c === 'gray'; 28 | return { 29 | ...acc, 30 | [`--badge-solid-${c}`]: [`${c}.500`, [`${c}.500`, 0.6]], 31 | [`--badge-subtle-${c}`]: [`${c}.100`, [`${c}.200`, 0.16]], 32 | [`--badge-subtle-${c}-text`]: [`${c}.800`, `${c}.200`], 33 | [`--badge-outline-${c}`]: [`${c}.500`, [`${c}.200`, 0.8]], 34 | [`--button-ghost-${c}`]: [ 35 | isGray ? 'inherit' : `${c}.600`, 36 | isGray ? 'whiteAlpha.900' : `${c}.200` 37 | ], 38 | [`--button-ghost-${c}-hover`]: [ 39 | `${c}.${isGray ? 100 : 50}`, 40 | isGray ? 'whiteAlpha.200' : [`${c}.200`, 0.12] 41 | ], 42 | [`--button-ghost-${c}-active`]: [ 43 | `${c}.${isGray ? 200 : 100}`, 44 | isGray ? 'whiteAlpha.300' : [`${c}.200`, 0.24] 45 | ], 46 | [`--button-solid-${c}`]: [ 47 | `${c}.${isGray ? 100 : 500}`, 48 | isGray ? 'whiteAlpha.200' : `${c}.200` 49 | ], 50 | [`--button-solid-${c}-hover`]: [ 51 | `${c}.${isGray ? 200 : 600}`, 52 | isGray ? 'whiteAlpha.300' : `${c}.300` 53 | ], 54 | [`--button-solid-${c}-active`]: [ 55 | `${c}.${isGray ? 300 : 700}`, 56 | isGray ? 'whiteAlpha.400' : `${c}.400` 57 | ] 58 | }; 59 | }, baseVariables); 60 | 61 | return outdent` 62 | const mql = window.matchMedia('(prefers-color-scheme: dark)'); 63 | const root = document.documentElement; 64 | ${Object.entries({ 65 | ...defaultVariables, 66 | ...customVariables 67 | }) 68 | .map( 69 | ([name, values]) => 70 | outdent` 71 | root.style.setProperty( 72 | '${name}', 73 | mql.matches 74 | ? '${colorValue(values[1])}' 75 | : '${colorValue(values[0])}' 76 | ); 77 | ` 78 | ) 79 | .join('\n')} 80 | `; 81 | } 82 | 83 | interface FlashlessScriptProps { 84 | theme: Dict; 85 | customVariables?: Variables; 86 | } 87 | 88 | export function FlashlessScript({ 89 | theme, 90 | customVariables 91 | }: FlashlessScriptProps): JSX.Element { 92 | return ( 93 |