├── public ├── favicon.ico ├── icon-128.png ├── icon-16.png ├── icon-24.png ├── icon-32.png ├── icon-48.png ├── fonts │ ├── RobotoMono-Light.woff │ ├── RobotoMono-Thin.woff │ ├── RobotoMono-Thin.woff2 │ ├── RobotoMono-Light.woff2 │ ├── RobotoMono-Regular.woff │ └── RobotoMono-Regular.woff2 ├── manifest.json └── index.html ├── src ├── App.test.js ├── reducers │ ├── initialState.js │ └── index.js ├── localStorage.js ├── actions │ └── index.js ├── components │ ├── SwitchButton.js │ ├── styled-components │ │ └── global.js │ ├── button.js │ ├── svg │ │ └── dots.js │ └── topBar.js ├── index.js ├── registerServiceWorker.js └── App.js ├── .gitignore ├── index.html ├── README.md └── package.json /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/icon-128.png -------------------------------------------------------------------------------- /public/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/icon-16.png -------------------------------------------------------------------------------- /public/icon-24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/icon-24.png -------------------------------------------------------------------------------- /public/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/icon-32.png -------------------------------------------------------------------------------- /public/icon-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/icon-48.png -------------------------------------------------------------------------------- /public/fonts/RobotoMono-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/fonts/RobotoMono-Light.woff -------------------------------------------------------------------------------- /public/fonts/RobotoMono-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/fonts/RobotoMono-Thin.woff -------------------------------------------------------------------------------- /public/fonts/RobotoMono-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/fonts/RobotoMono-Thin.woff2 -------------------------------------------------------------------------------- /public/fonts/RobotoMono-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/fonts/RobotoMono-Light.woff2 -------------------------------------------------------------------------------- /public/fonts/RobotoMono-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/fonts/RobotoMono-Regular.woff -------------------------------------------------------------------------------- /public/fonts/RobotoMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edkf/tabipsum/HEAD/public/fonts/RobotoMono-Regular.woff2 -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | build.pem 19 | build.crx 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/reducers/initialState.js: -------------------------------------------------------------------------------- 1 | import loremIpsum from 'lorem-ipsum' 2 | 3 | const content = loremIpsum({ 4 | count: 11, 5 | units: 'words', 6 | sentenceLowerBound: 5, 7 | sentenceUpperBound: 15, 8 | paragraphLowerBound: 3, 9 | paragraphUpperBound: 7, 10 | format: 'plain', 11 | }) 12 | 13 | export const initialState = { 14 | value: 14, 15 | darkmode: false, 16 | contentType: 'words', 17 | content 18 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | React App
-------------------------------------------------------------------------------- /src/localStorage.js: -------------------------------------------------------------------------------- 1 | export const loadState = () => { 2 | try { 3 | const serializedState = localStorage.getItem('state'); 4 | if (serializedState == null) { 5 | return undefined 6 | } 7 | return JSON.parse(serializedState) 8 | } catch (err) { 9 | return undefined 10 | } 11 | } 12 | 13 | export const saveState = (state) => { 14 | try { 15 | const serializedState = JSON.stringify(state) 16 | localStorage.setItem('state', serializedState) 17 | } catch (err) { 18 | 19 | } 20 | } -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | export const CHANGE_UNIT = 'CHANGE_UNIT' 2 | export const UPDATE_VALUE = 'UPDATE_VALUE' 3 | export const SWITCH_UI_MODE = 'SWITCH_UI_MODE' 4 | 5 | export function changeUnit(contentType) { 6 | return { 7 | type: CHANGE_UNIT, 8 | contentType 9 | } 10 | } 11 | 12 | export function updateValue(value) { 13 | return { 14 | type: UPDATE_VALUE, 15 | value 16 | } 17 | } 18 | 19 | export function switchUIMode(darkmode) { 20 | return { 21 | type: SWITCH_UI_MODE, 22 | darkmode: !darkmode 23 | } 24 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Tab Ipsum - Lorem Ipsum Generator 3 |
4 | 5 | ## Get Tab Ipsum 6 | 7 | - **[Mac App](https://assets.edkf.com.br/TabIpsum-1.0.0.dmg.zip)** 8 | - **[Chrome extension](https://chrome.google.com/webstore/detail/tabipsum/kodnpaacnfpgeakliedgocnfoeiajghp)** 9 | 10 | 11 | ## Contribute 12 | 13 | - `yarn` (`npm run install`) Install dependencies. 14 | - `yarn start` (`npm run start`) Runs the app in development mode. 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tab-ipsum-app", 3 | "version": "1.0.0", 4 | "private": true, 5 | "dependencies": { 6 | "lorem-ipsum": "^1.0.4", 7 | "react": "^16.2.0", 8 | "react-clipboard.js": "^1.1.3", 9 | "react-dom": "^16.2.0", 10 | "react-input-range": "^1.3.0", 11 | "react-redux": "^5.0.7", 12 | "react-scripts": "1.1.1", 13 | "redux": "^4.0.0", 14 | "styled-components": "^3.2.3" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "TabIpsum", 4 | "short_name": "TabIpsum - Lorem Ipsum Generator", 5 | "version": "1.0", 6 | "description": "Just a Lorem Ipsum generator.", 7 | "author": "Edgard Kozlowski (http://edkf.com.br)", 8 | "commands": { 9 | "_execute_browser_action": {} 10 | }, 11 | "icons": { 12 | "16": "icon-16.png", 13 | "32": "icon-32.png", 14 | "48": "icon-48.png", 15 | "128": "icon-128.png" 16 | }, 17 | "browser_action": { 18 | "default_icon": { 19 | "16": "icon-16.png", 20 | "24": "icon-24.png", 21 | "32": "icon-32.png" 22 | }, 23 | "default_popup": "index.html" 24 | }, 25 | "permissions": [ 26 | "tabs" 27 | ] 28 | } -------------------------------------------------------------------------------- /src/components/SwitchButton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | width: 35px; 6 | height: 20px; 7 | background: ${props => props.darkmode ? '#2FAADC' : '#EEEEEE'}; 8 | border-radius: 20px; 9 | position: relative; 10 | ` 11 | 12 | const Circle = styled.div` 13 | width: 16px; 14 | height: 16px; 15 | border-radius: 15px; 16 | background: #FFFFFF; 17 | position: relative; 18 | top: 2px; 19 | transition: transform .3s ease; 20 | transform: ${props => props.darkmode ? 'translateX(16px)' : 'translateX(3px)'}; 21 | ` 22 | 23 | const SwitchButton = (props) => { 24 | return ( 25 | 26 | 27 | 28 | ) 29 | } 30 | 31 | export default SwitchButton -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import { Provider } from 'react-redux' 5 | import { createStore } from 'redux' 6 | import { loadState, saveState } from './localStorage.js' 7 | 8 | 9 | // Components 10 | import App from './App' 11 | 12 | // Reducers 13 | import reducers from './reducers' 14 | 15 | // Styled-components 16 | import './components/styled-components/global.js' 17 | 18 | const persistedState = loadState() 19 | 20 | // Create Redux Store 21 | const store = createStore( 22 | reducers, 23 | persistedState, 24 | window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() // Enable Redux Devtools 25 | ) 26 | 27 | store.subscribe(() => { 28 | saveState(store.getState()) 29 | }) 30 | 31 | ReactDOM.render( 32 | 33 | 34 | 35 | , document.getElementById('root')); 36 | -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import loremIpsum from 'lorem-ipsum' 3 | 4 | // Action types 5 | import {CHANGE_UNIT, UPDATE_VALUE, SWITCH_UI_MODE } from '../actions' 6 | 7 | // Initial state 8 | import { initialState } from './initialState' 9 | 10 | export default function reducers(state = initialState, action) { 11 | switch (action.type) { 12 | case CHANGE_UNIT: 13 | const { contentType } = action 14 | return { 15 | ...state, 16 | contentType, 17 | content: loremIpsum({ 18 | ...state.content, 19 | units: contentType, 20 | count: state.value 21 | }), 22 | } 23 | case UPDATE_VALUE: 24 | const { value } = action 25 | return { 26 | ...state, 27 | value: value, 28 | content: loremIpsum({ 29 | ...state.content, 30 | units: state.contentType, 31 | count: value 32 | }) 33 | } 34 | case SWITCH_UI_MODE: 35 | return { 36 | ...state, 37 | darkmode: !state.darkmode 38 | } 39 | default: 40 | return state 41 | } 42 | } -------------------------------------------------------------------------------- /src/components/styled-components/global.js: -------------------------------------------------------------------------------- 1 | import { injectGlobal } from 'styled-components' 2 | 3 | injectGlobal` 4 | *, *:before, *:after { 5 | -webkit-box-sizing: border-box; 6 | -moz-box-sizing: border-box; 7 | box-sizing: border-box; 8 | } 9 | 10 | @font-face { 11 | font-family: 'Roboto Mono'; 12 | src: url('/fonts/RobotoMono-Thin.woff2') format('woff2'), 13 | url('/fonts/RobotoMono-Thin.woff') format('woff'); 14 | font-weight: 100; 15 | font-style: normal; 16 | } 17 | 18 | @font-face { 19 | font-family: 'Roboto Mono'; 20 | src: url('/fonts/RobotoMono-Light.woff2') format('woff2'), 21 | url('/fonts/RobotoMono-Light.woff') format('woff'); 22 | font-weight: 300; 23 | font-style: normal; 24 | } 25 | 26 | @font-face { 27 | font-family: 'Roboto Mono'; 28 | src: url('/fonts/RobotoMono-Regular.woff2') format('woff2'), 29 | url('/fonts/RobotoMono-Regular.woff') format('woff'); 30 | font-weight: normal; 31 | font-style: normal; 32 | } 33 | 34 | body { 35 | font-family: 'Roboto Mono' ,-apple-system, system-ui, 'Helvetica Neue', Arial, sans-serif; 36 | -webkit-text-rendering: optimizeLegibility; 37 | -moz-text-rendering: optimizeLegibility; 38 | -ms-text-rendering: optimizeLegibility; 39 | -o-text-rendering: optimizeLegibility; 40 | text-rendering: optimizeLegibility; 41 | -webkit-font-smoothing: antialiased; 42 | -moz-osx-font-smoothing: grayscale; 43 | margin: 0; 44 | padding: 0; 45 | } 46 | ` -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | React App 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/button.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import React, { Component } from 'react' 3 | import styled from 'styled-components' 4 | import Clipboard from 'react-clipboard.js' 5 | 6 | // Styled Components 7 | const ClipboardButton = styled(Clipboard)` 8 | font-family: 'Roboto Mono'; 9 | color: #FFF; 10 | background-color: ${props => props.buttonColor}; 11 | font-size: 24px; 12 | line-height: 1; 13 | font-weight: bold; 14 | padding: 30px; 15 | width: 100%; 16 | border: none; 17 | cursor: pointer; 18 | transition: .2s ease; 19 | ` 20 | 21 | class Button extends Component { 22 | 23 | constructor (props) { 24 | super(props) 25 | this.onSuccess = this.onSuccess.bind(this) 26 | this.getRandomEmoji = this.getRandomEmoji.bind(this) 27 | 28 | this.state = { 29 | buttonLabel: 'Copy', 30 | buttonColor: '#000000' 31 | } 32 | } 33 | 34 | getRandomEmoji () { 35 | const emojis = ['😎', '🚀', '😀', '🙃', '😛', '🙏', '👍', '🙅', '💩', '📝'] 36 | return emojis[Math.floor(Math.random()*emojis.length)] 37 | } 38 | 39 | onSuccess() { 40 | this.setState ({ 41 | buttonLabel: 'Copied ' + this.getRandomEmoji(), 42 | buttonColor: '#00C492' 43 | }) 44 | setTimeout(() => { 45 | this.setState({ 46 | buttonLabel: 'Copy', 47 | buttonColor: '#000000' 48 | }) 49 | }, 1000) 50 | } 51 | 52 | render () { 53 | 54 | const { buttonColor, buttonLabel } = this.state 55 | const { content } = this.props 56 | 57 | return ( 58 | 63 | {buttonLabel} 64 | 65 | ) 66 | } 67 | } 68 | 69 | export default Button -------------------------------------------------------------------------------- /src/components/svg/dots.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Container = styled.div` 5 | padding: 5px; 6 | 7 | path { 8 | transition: .3s ease; 9 | } 10 | 11 | &:hover { 12 | path { 13 | fill: ${props => props.darkmode ? '#666666' : '#AAAAAA'}; 14 | } 15 | } 16 | ` 17 | 18 | const Dots = (props) => ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | 29 | export default Dots -------------------------------------------------------------------------------- /src/components/topBar.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import React, { Component } from 'react' 3 | import styled from 'styled-components' 4 | 5 | // Assets 6 | import Dots from './svg/dots.js' 7 | 8 | import SwitchButton from './SwitchButton' 9 | 10 | const Container = styled.div` 11 | width: 100%; 12 | padding: 15px 25px; 13 | display: flex; 14 | justify-content: flex-end; 15 | ` 16 | 17 | const List = styled.ul` 18 | list-style: none; 19 | margin: 0; 20 | padding: 10px; 21 | background-color: ${props => props.darkmode ? '#353638' : '#FFFFFF'}; 22 | position: absolute; 23 | box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.1); 24 | pointer-events: none; 25 | right: 0; 26 | opacity: 0; 27 | ` 28 | 29 | const Dropdown = styled.div` 30 | position: relative; 31 | &:hover { 32 | ${List} { 33 | pointer-events: auto; 34 | opacity: 1; 35 | } 36 | } 37 | ` 38 | 39 | const Item = styled.li` 40 | width: 180px; 41 | font-family: 'Roboto Mono'; 42 | cursor: pointer; 43 | padding: 10px 5px; 44 | font-size: 10px; 45 | list-style: none; 46 | color: #AAA; 47 | display: flex; 48 | justify-content: space-between; 49 | align-items: center; 50 | 51 | a { 52 | color: #AAA; 53 | text-decoration: none; 54 | } 55 | ` 56 | 57 | const items = [ 58 | { 59 | text: 'Visit Website', 60 | url: 'http://tabipsum.com' 61 | } 62 | ] 63 | 64 | class TopBar extends Component { 65 | render () { 66 | 67 | const { darkmode } = this.props 68 | 69 | return ( 70 | 71 | 72 | 73 | 74 | 77 | Enable dark mode 78 | 79 | 80 | { 81 | items.map((item) => ( 82 | 83 | {item.text} 84 | 85 | )) 86 | } 87 | 88 | 89 | 90 | ) 91 | } 92 | } 93 | 94 | export default TopBar -------------------------------------------------------------------------------- /src/registerServiceWorker.js: -------------------------------------------------------------------------------- 1 | // In production, we register a service worker to serve assets from local cache. 2 | 3 | // This lets the app load faster on subsequent visits in production, and gives 4 | // it offline capabilities. However, it also means that developers (and users) 5 | // will only see deployed updates on the "N+1" visit to a page, since previously 6 | // cached resources are updated in the background. 7 | 8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy. 9 | // This link also includes instructions on opting out of this behavior. 10 | 11 | const isLocalhost = Boolean( 12 | window.location.hostname === 'localhost' || 13 | // [::1] is the IPv6 localhost address. 14 | window.location.hostname === '[::1]' || 15 | // 127.0.0.1/8 is considered localhost for IPv4. 16 | window.location.hostname.match( 17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 18 | ) 19 | ); 20 | 21 | export default function register() { 22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 23 | // The URL constructor is available in all browsers that support SW. 24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 25 | if (publicUrl.origin !== window.location.origin) { 26 | // Our service worker won't work if PUBLIC_URL is on a different origin 27 | // from what our page is served on. This might happen if a CDN is used to 28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 29 | return; 30 | } 31 | 32 | window.addEventListener('load', () => { 33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 34 | 35 | if (isLocalhost) { 36 | // This is running on localhost. Lets check if a service worker still exists or not. 37 | checkValidServiceWorker(swUrl); 38 | 39 | // Add some additional logging to localhost, pointing developers to the 40 | // service worker/PWA documentation. 41 | navigator.serviceWorker.ready.then(() => { 42 | console.log( 43 | 'This web app is being served cache-first by a service ' + 44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ' 45 | ); 46 | }); 47 | } else { 48 | // Is not local host. Just register service worker 49 | registerValidSW(swUrl); 50 | } 51 | }); 52 | } 53 | } 54 | 55 | function registerValidSW(swUrl) { 56 | navigator.serviceWorker 57 | .register(swUrl) 58 | .then(registration => { 59 | registration.onupdatefound = () => { 60 | const installingWorker = registration.installing; 61 | installingWorker.onstatechange = () => { 62 | if (installingWorker.state === 'installed') { 63 | if (navigator.serviceWorker.controller) { 64 | // At this point, the old content will have been purged and 65 | // the fresh content will have been added to the cache. 66 | // It's the perfect time to display a "New content is 67 | // available; please refresh." message in your web app. 68 | console.log('New content is available; please refresh.'); 69 | } else { 70 | // At this point, everything has been precached. 71 | // It's the perfect time to display a 72 | // "Content is cached for offline use." message. 73 | console.log('Content is cached for offline use.'); 74 | } 75 | } 76 | }; 77 | }; 78 | }) 79 | .catch(error => { 80 | console.error('Error during service worker registration:', error); 81 | }); 82 | } 83 | 84 | function checkValidServiceWorker(swUrl) { 85 | // Check if the service worker can be found. If it can't reload the page. 86 | fetch(swUrl) 87 | .then(response => { 88 | // Ensure service worker exists, and that we really are getting a JS file. 89 | if ( 90 | response.status === 404 || 91 | response.headers.get('content-type').indexOf('javascript') === -1 92 | ) { 93 | // No service worker found. Probably a different app. Reload the page. 94 | navigator.serviceWorker.ready.then(registration => { 95 | registration.unregister().then(() => { 96 | window.location.reload(); 97 | }); 98 | }); 99 | } else { 100 | // Service worker found. Proceed as normal. 101 | registerValidSW(swUrl); 102 | } 103 | }) 104 | .catch(() => { 105 | console.log( 106 | 'No internet connection found. App is running in offline mode.' 107 | ); 108 | }); 109 | } 110 | 111 | export function unregister() { 112 | if ('serviceWorker' in navigator) { 113 | navigator.serviceWorker.ready.then(registration => { 114 | registration.unregister(); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | // Dependencies 2 | import React, { Component } from 'react' 3 | import styled from 'styled-components' 4 | import InputRange from 'react-input-range' 5 | import { connect } from 'react-redux' 6 | 7 | // Actions 8 | import { changeUnit, updateValue, switchUIMode } from './actions' 9 | 10 | // Components 11 | import TopBar from './components/topBar' 12 | import Button from './components/button' 13 | 14 | // Styled Components 15 | const Container = styled.div` 16 | display: flex; 17 | flex-direction: column; 18 | justify-content: space-between; 19 | width: 320px; 20 | height: 420px; 21 | background: ${props => props.darkmode ? '#2E2F30' : '#FFFFFF'} 22 | 23 | input::-webkit-outer-spin-button, 24 | input::-webkit-inner-spin-button { 25 | /* display: none; <- Crashes Chrome on hover */ 26 | -webkit-appearance: none; 27 | margin: 0; /* <-- Apparently some margin are still there even though it's hidden */ 28 | } 29 | 30 | input[type='number'] { 31 | -moz-appearance:textfield; 32 | } 33 | 34 | input::-webkit-outer-spin-button, 35 | input::-webkit-inner-spin-button { 36 | -webkit-appearance: none; 37 | } 38 | 39 | .input-range__slider { 40 | -webkit-appearance: none; 41 | -moz-appearance: none; 42 | appearance: none; 43 | background: ${props => props.darkmode ? '#DADADA' : '#000000'}; 44 | border: 1px solid ${props => props.darkmode ? '#DADADA' : '#000000'}; 45 | border-radius: 100%; 46 | cursor: pointer; 47 | display: block; 48 | height: 1rem; 49 | margin-left: -0.5rem; 50 | margin-top: -0.65rem; 51 | outline: none; 52 | position: absolute; 53 | top: 50%; 54 | -webkit-transition: box-shadow 0.3s ease-out, -webkit-transform 0.3s ease-out; 55 | transition: box-shadow 0.3s ease-out, -webkit-transform 0.3s ease-out; 56 | transition: transform 0.3s ease-out, box-shadow 0.3s ease-out; 57 | transition: transform 0.3s ease-out, box-shadow 0.3s ease-out, -webkit-transform 0.3s ease-out; 58 | width: 1rem; } 59 | .input-range__slider:active { 60 | -webkit-transform: scale(1.3); 61 | transform: scale(1.3); } 62 | .input-range__slider:focus { 63 | box-shadow: 0 0 0 5px rgba(63, 81, 181, 0.2); } 64 | .input-range--disabled .input-range__slider { 65 | background: #cccccc; 66 | border: 1px solid #cccccc; 67 | box-shadow: none; 68 | -webkit-transform: none; 69 | transform: none; } 70 | 71 | .input-range__slider-container { 72 | -webkit-transition: left 0.3s ease-out; 73 | transition: left 0.3s ease-out; } 74 | 75 | .input-range__label { 76 | color: #aaaaaa; 77 | font-family: "Helvetica Neue", san-serif; 78 | font-size: 0.8rem; 79 | -webkit-transform: translateZ(0); 80 | transform: translateZ(0); 81 | white-space: nowrap; } 82 | 83 | .input-range__label--min, 84 | .input-range__label--max { 85 | bottom: -1.4rem; 86 | position: absolute; } 87 | 88 | .input-range__label--min { 89 | left: 0; } 90 | 91 | .input-range__label--max { 92 | right: 0; } 93 | 94 | .input-range__label--value { 95 | position: absolute; 96 | top: -1.8rem; } 97 | 98 | .input-range__label-container { 99 | display: none; 100 | left: -50%; 101 | position: relative; } 102 | .input-range__label--max .input-range__label-container { 103 | left: 50%; } 104 | 105 | .input-range__track { 106 | background: ${props => props.darkmode ? '#222222' : '#EEEEEE'}; 107 | border-radius: 0.3rem; 108 | cursor: pointer; 109 | display: block; 110 | height: 0.3rem; 111 | position: relative; 112 | -webkit-transition: left 0.3s ease-out, width 0.3s ease-out; 113 | transition: left 0.3s ease-out, width 0.3s ease-out; } 114 | .input-range--disabled .input-range__track { 115 | background: #EEEEEE; } 116 | 117 | .input-range__track--background { 118 | left: 0; 119 | margin-top: -0.15rem; 120 | position: absolute; 121 | right: 0; 122 | top: 50%; } 123 | 124 | .input-range__track--active { 125 | background: ${ props => props.darkmode ? '#DDDDDD' : '#333333'}; } 126 | 127 | .input-range { 128 | height: 1rem; 129 | position: relative; 130 | width: 100%; } 131 | ` 132 | 133 | const MainContainer = styled.div` 134 | width: 80%; 135 | margin: 0 auto; 136 | text-align: center; 137 | ` 138 | 139 | const Number = styled.input` 140 | width: 100%; 141 | font-family: 'Roboto Mono'; 142 | font-size: 120px; 143 | font-weight: 100; 144 | text-align: center; 145 | letter-spacing: -10px; 146 | border: none; 147 | background-color: transparent; 148 | color: ${props => props.darkmode ? '#DADADA' : '#000000'} 149 | 150 | &:focus { 151 | outline: none; 152 | } 153 | ` 154 | 155 | const List = styled.ul` 156 | padding: 0; 157 | margin: 15px 0; 158 | display: flex; 159 | justify-content: center; 160 | ` 161 | 162 | const Item = styled.li` 163 | font-family: 'Roboto Mono'; 164 | font-weight: 300; 165 | text-transform: capitalize; 166 | letter-spacing: -0.5px; 167 | cursor: pointer; 168 | padding: 10px; 169 | font-size: 14px; 170 | list-style: none; 171 | padding: 10px 5px; 172 | transition: .3s ease; 173 | color: ${props => props.darkmode ? (props.isSelected ? '#DADADA' : '#666666' ) : (props.isSelected ? '#000000' : '#AAAAAA')}; 174 | 175 | &.is-selected { 176 | color: #000; 177 | font-weight: bold; 178 | } 179 | ` 180 | 181 | const units = [ 'words', 'sentences', 'paragraphs'] 182 | 183 | class App extends Component { 184 | 185 | constructor (props) { 186 | super(props) 187 | this.switchTheme = this.switchTheme.bind(this) 188 | } 189 | 190 | componentDidMount () { 191 | console.log(localStorage.getItem("darkmode")) 192 | } 193 | 194 | switchTheme () { 195 | localStorage.setItem('darkmode', !localStorage.getItem("darkmode")) 196 | } 197 | 198 | render() { 199 | 200 | const { content, value, contentType, darkmode } = this.props.state 201 | const { changeUnit, updateValue, updateFromNumber, switchUIMode } = this.props 202 | 203 | return ( 204 | 205 | 206 | 207 | 215 | 216 | {units.map((unit, index) => ( 217 | {unit} 218 | ))} 219 | 220 | 227 | 228 |