├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── cypress.json ├── cypress ├── fixtures │ ├── cheeseburger.jpg │ └── example.json ├── integration │ ├── Playground.Colors.spec.js │ ├── Playground.Edit.spec.js │ ├── Playground.Fonts.spec.js │ ├── Playground.Load.spec.js │ ├── Playground.Nav.spec.js │ ├── Playground.Random.spec.js │ └── Playground.Save.spec.js ├── plugins │ └── index.js └── support │ ├── commands.js │ └── index.js ├── gatsby-browser.js ├── gatsby-config.js ├── package-lock.json ├── package.json ├── public └── index.html ├── src ├── animate │ ├── useTweenLite.js │ └── useTweenMax.js ├── components │ ├── Footer.js │ ├── Header.js │ ├── Main.js │ ├── Nav.js │ ├── Wrapper.js │ ├── head │ │ ├── GlobalCSS.js │ │ └── SEO.js │ ├── playground │ │ ├── Choose.js │ │ ├── ChooseColorScheme.js │ │ ├── ChooseColors.js │ │ ├── ChooseFonts.js │ │ ├── ColorAdd.js │ │ ├── ColorPicker.js │ │ ├── ColorSchemeFromColor.js │ │ ├── ColorSchemeFromImage.js │ │ ├── ColorSchemeFromPreset.js │ │ ├── ColorSchemeSwatch.js │ │ ├── Container.js │ │ ├── FontSelectBrowser.js │ │ ├── FontSelectWeb.js │ │ ├── Header.js │ │ ├── LoadTheme.js │ │ ├── Main.js │ │ ├── NextButton.js │ │ ├── SaveTheme.js │ │ ├── Welcome.js │ │ ├── WelcomeBack.js │ │ ├── Wrapper.js │ │ └── index.js │ ├── sections │ │ ├── Section.js │ │ ├── SubSection.js │ │ ├── color │ │ │ ├── Color.js │ │ │ ├── Palette.js │ │ │ └── index.js │ │ ├── index.js │ │ ├── theme.js │ │ ├── typography │ │ │ ├── Font.js │ │ │ ├── FontWeights.js │ │ │ ├── TypeScale.js │ │ │ └── index.js │ │ └── ui │ │ │ ├── Buttons.js │ │ │ ├── Cards.js │ │ │ ├── Intro.js │ │ │ ├── MediaObject.js │ │ │ ├── NavBar.js │ │ │ └── index.js │ └── ui │ │ ├── Button.js │ │ ├── Card.js │ │ ├── Figure.js │ │ ├── Flex.js │ │ ├── GithubLink.js │ │ ├── HeaderButton.js │ │ ├── Heading.js │ │ ├── Link.js │ │ └── Logo.js ├── context │ ├── NavContext.js │ └── ThemeContext.js ├── images │ └── icon.png ├── pages │ └── index.js └── themes │ ├── code.js │ ├── dark.js │ ├── index.js │ ├── indigo.js │ ├── orchid.js │ ├── peru.js │ ├── roboto.js │ ├── royal.js │ ├── salmon.js │ ├── sky.js │ ├── slate.js │ ├── swiss.js │ ├── tailwind.js │ └── tomato.js └── static └── img ├── chakra.png ├── design-system-playground.png └── rebass.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # Cypress 72 | cypress/screenshots 73 | cypress/videos 74 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | package.json 3 | package-lock.json 4 | public 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": false, 4 | "singleQuote": false, 5 | "tabWidth": 2, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 John Polacek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design System Playground 2 | 3 | #### Play with typography and colors to generate a design system theme you can use in your projects. 4 | 5 | [![Netlify Status](https://api.netlify.com/api/v1/badges/ca9cacda-5677-43e8-928d-c1970af41cfe/deploy-status)](https://app.netlify.com/sites/design-system-playground/deploys) 6 | 7 | --- 8 | 9 | With a [Theme Object](https://theme-ui.com/theme-spec) that follows the [System UI Theme Specification](https://system-ui.com/) for style values, scales, and/or design tokens, you can create [interoperable](https://jxnblk.com/blog/interoperability/) components that share common underlying style properties across projects, websites, applications and environments. 10 | 11 | --- 12 | 13 | #### Related Projects 14 | 15 | Check out these other cool projects: 16 | 17 | - [System UI](https://system-ui.com/) 18 | - [Theme UI](https://theme-ui.com/) 19 | - [Rebass](https://rebassjs.org/) 20 | - [Chakra UI](https://chakra-ui.com/) 21 | - [Components AI](https://components.ai/) 22 | - [ColorBox](https://www.colorbox.io/) 23 | - [Color Thief](https://lokeshdhakar.com/projects/color-thief/) 24 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8000", 3 | "chromeWebSecurity": false, 4 | "video": false 5 | } 6 | -------------------------------------------------------------------------------- /cypress/fixtures/cheeseburger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpolacek/design-system-playground/b46fc02f8c80a7f051c89e53e1014cef1934537c/cypress/fixtures/cheeseburger.jpg -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/integration/Playground.Colors.spec.js: -------------------------------------------------------------------------------- 1 | describe("Playground", function() { 2 | beforeEach(function() { 3 | cy.visit("/") 4 | cy.get("header").should("have.css", "background-color", "rgb(65, 105, 225)") 5 | cy.wait(10000) 6 | cy.contains("What body font do you like?").should("be.visible") 7 | cy.contains("Next").click() 8 | cy.wait(1000) 9 | cy.contains("What heading font do you like?").should("be.visible") 10 | cy.contains("Next").click() 11 | cy.wait(1000) 12 | cy.contains("Get a color scheme...").should("be.visible") 13 | }) 14 | 15 | it("can set color scheme from preset, image, preset or go random", function() { 16 | const defaultPrimaryColor = 17 | Cypress.browser.name === "electron" 18 | ? "rgb(222, 162, 94)" 19 | : "rgb(222, 162, 95)" 20 | 21 | cy.get("#colorsPreset").select("tomato") 22 | cy.get("header").should("have.css", "background-color", "rgb(255, 99, 71)") 23 | 24 | cy.get("#colorsFromColor").type("abc123") 25 | cy.get("header").should("have.css", "background-color", "rgb(171, 193, 35)") 26 | 27 | // see uploadFile in support/commands, cheeseburger.js in fixtures 28 | cy.uploadFile("cheeseburger.jpg", "#colorsFromImage") 29 | cy.get("#colorsFromImage").trigger("change") 30 | cy.get("header").should("have.css", "background-color", defaultPrimaryColor) 31 | 32 | cy.contains("go random").click() 33 | cy.get("header").should( 34 | "not.have.css", 35 | "background-color", 36 | defaultPrimaryColor 37 | ) 38 | 39 | cy.contains("Next").click() 40 | cy.wait(1000) 41 | cy.contains("Here are your colors...").should("be.visible") 42 | cy.contains("primary").should("be.visible") 43 | cy.contains("secondary").should("be.visible") 44 | cy.contains("background").should("be.visible") 45 | cy.contains("text").should("be.visible") 46 | }) 47 | 48 | it("can change, add or delete colors", function() { 49 | cy.contains("Next").click() 50 | cy.wait(1000) 51 | cy.contains("Here are your colors...").should("be.visible") 52 | cy.contains("primary").should("be.visible") 53 | cy.contains("secondary").should("be.visible") 54 | cy.contains("background").should("be.visible") 55 | cy.contains("text").should("be.visible") 56 | 57 | // change primary color 58 | cy.get("input") 59 | .first() 60 | .clear() 61 | .type("#abc123") 62 | cy.get("header").should("have.css", "background-color", "rgb(171, 193, 35)") 63 | 64 | // add color 65 | cy.get("#addColorPreview").should( 66 | "have.css", 67 | "background-color", 68 | "rgba(0, 0, 0, 0)" 69 | ) 70 | cy.get("input[name=newColorName]").type("forest") 71 | cy.get("input[name=newColorValue]").type("228b22") 72 | cy.get("#addColor").click() 73 | 74 | cy.wait(1000) 75 | cy.contains("forest").should("be.visible") 76 | cy.contains("#228b22").should("be.visible") 77 | cy.contains("#228b22") 78 | .parent() 79 | .find("div") 80 | .should("have.css", "background-color", "rgb(34, 139, 34)") 81 | 82 | cy.get("input[name=newColorName]").should("have.value", "") 83 | cy.get("input[name=newColorValue]").should("have.value", "#") 84 | 85 | // delete color 86 | cy.contains("forest") 87 | .parent() 88 | .contains("×") 89 | .click() 90 | cy.contains("forest").should("not.exist") 91 | cy.contains("#228b22").should("not.exist") 92 | }) 93 | 94 | it("can detect dark mode", function() { 95 | cy.get("#colorsFromColor").type("#fafafa") 96 | cy.get("header").should( 97 | "have.css", 98 | "background-color", 99 | "rgb(250, 250, 250)" 100 | ) 101 | cy.get("main").should("have.css", "background-color", "rgb(0, 0, 0)") 102 | cy.get("main").should("have.css", "color", "rgb(255, 255, 255)") 103 | cy.get("footer").should("have.css", "background-color", "rgb(26, 26, 26)") 104 | cy.get("nav li").should("have.css", "background-color", "rgb(0, 0, 0)") 105 | cy.get("nav li span").should("have.css", "color", "rgb(250, 250, 250)") 106 | cy.get("#playground").should( 107 | "have.css", 108 | "background-color", 109 | "rgb(26, 26, 26)" 110 | ) 111 | 112 | cy.wait(1000) 113 | cy.get("h3") 114 | .first() 115 | .should("have.css", "color", "rgb(255, 255, 255)") 116 | cy.get("input").should("have.css", "color", "rgb(255, 255, 255)") 117 | cy.get("input").should("have.css", "background-color", "rgb(0, 0, 0)") 118 | cy.get("select").should("have.css", "color", "rgb(255, 255, 255)") 119 | cy.get("select").should("have.css", "background-color", "rgb(0, 0, 0)") 120 | }) 121 | }) 122 | -------------------------------------------------------------------------------- /cypress/integration/Playground.Edit.spec.js: -------------------------------------------------------------------------------- 1 | describe("Playground", function() { 2 | it("can edit fonts and colors", function() { 3 | cy.visit("/") 4 | cy.wait(10000) 5 | cy.get("header").should("have.css", "background-color", "rgb(65, 105, 225)") 6 | cy.contains("What body font do you like?").should("be.visible") 7 | cy.contains("Next").click() 8 | cy.wait(1000) 9 | cy.contains("What heading font do you like?").should("be.visible") 10 | cy.contains("Next").click() 11 | cy.wait(1000) 12 | cy.contains("Get a color scheme...").should("be.visible") 13 | cy.contains("Next").click() 14 | cy.wait(1000) 15 | cy.contains("Here are your colors...").should("be.visible") 16 | cy.contains("Next").click() 17 | cy.wait(1000) 18 | 19 | cy.wait(1000) 20 | cy.get("#playground").should("have.css", "height", "0px") 21 | cy.get("header") 22 | .contains("play") 23 | .click() 24 | 25 | cy.contains("What body font do you like?").should("be.visible") 26 | cy.get("#selectBrowserFont").select("Georgia, serif") 27 | cy.contains("Next").click() 28 | cy.wait(1000) 29 | 30 | cy.contains("What heading font do you like?").should("be.visible") 31 | cy.get("#selectBrowserFont").select("Arial, sans-serif") 32 | cy.contains("Next").click() 33 | cy.wait(1000) 34 | 35 | cy.contains("Get a color scheme...").should("be.visible") 36 | cy.get("#colorsPreset").select("tomato") 37 | cy.contains("Next").click() 38 | cy.wait(1000) 39 | 40 | cy.contains("Here are your colors...").should("be.visible") 41 | cy.contains("Next").click() 42 | cy.wait(1000) 43 | 44 | cy.wait(1000) 45 | cy.get("#playground").should("have.css", "height", "0px") 46 | cy.get("p#bodyText").should("have.css", "font-family", "Georgia, serif") 47 | cy.get("main section h2").should( 48 | "have.css", 49 | "font-family", 50 | "Arial, sans-serif" 51 | ) 52 | cy.get("header").should("have.css", "background-color", "rgb(255, 99, 71)") 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /cypress/integration/Playground.Fonts.spec.js: -------------------------------------------------------------------------------- 1 | describe("Playground", function() { 2 | beforeEach(function() { 3 | cy.visit("/") 4 | }) 5 | 6 | it("can set browser fonts", function() { 7 | cy.wait(9000) 8 | cy.contains("What body font do you like?").should("be.visible") 9 | cy.get("#selectBrowserFont").select("Georgia, serif") 10 | cy.contains("Next").click() 11 | cy.wait(500) 12 | 13 | cy.contains("What heading font do you like?").should("be.visible") 14 | cy.get("#selectBrowserFont").select("Arial, sans-serif") 15 | cy.contains("Next").click() 16 | cy.wait(500) 17 | 18 | cy.contains("Get a color scheme...").should("be.visible") 19 | cy.contains("Next").click() 20 | cy.wait(500) 21 | 22 | cy.contains("Here are your colors...").should("be.visible") 23 | cy.contains("Next").click() 24 | cy.wait(500) 25 | 26 | cy.wait(1000) 27 | cy.get("main section h2").should( 28 | "have.css", 29 | "font-family", 30 | "Arial, sans-serif" 31 | ) 32 | cy.get("main section p#bodyText").should( 33 | "have.css", 34 | "font-family", 35 | "Georgia, serif" 36 | ) 37 | }) 38 | 39 | it("can set web fonts", function() { 40 | cy.wait(9000) 41 | cy.contains("What body font do you like?").should("be.visible") 42 | cy.get("#selectWebFont").select("Vollkorn") 43 | cy.contains("Next").click() 44 | cy.wait(500) 45 | 46 | cy.contains("What heading font do you like?").should("be.visible") 47 | cy.get("#selectWebFont").select("Yantramanav") 48 | cy.contains("Next").click() 49 | cy.wait(500) 50 | 51 | cy.contains("Get a color scheme...").should("be.visible") 52 | cy.contains("Next").click() 53 | cy.wait(500) 54 | 55 | cy.contains("Here are your colors...").should("be.visible") 56 | cy.contains("Next").click() 57 | cy.wait(500) 58 | 59 | cy.wait(1000) 60 | 61 | cy.get("main section p#bodyText").should( 62 | "have.css", 63 | "font-family", 64 | "Vollkorn, serif" 65 | ) 66 | cy.get("main section h2").should( 67 | "have.css", 68 | "font-family", 69 | "Yantramanav, sans-serif" 70 | ) 71 | }) 72 | 73 | it("can set random fonts", function() { 74 | cy.wait(9000) 75 | cy.get("header h1").should( 76 | "have.css", 77 | "font-family", 78 | '"avenir next", avenir, helvetica, arial, sans-serif' 79 | ) 80 | cy.contains("What body font do you like?").should("be.visible") 81 | cy.contains("go random").click() 82 | cy.get("header h1").should( 83 | "not.have.css", 84 | "font-family", 85 | '"avenir next", avenir, helvetica, arial, sans-serif' 86 | ) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /cypress/integration/Playground.Load.spec.js: -------------------------------------------------------------------------------- 1 | describe("Playground", function() { 2 | beforeEach(function() { 3 | cy.visit("/") 4 | }) 5 | 6 | it("can load preset theme", function() { 7 | cy.wait(10000) 8 | cy.get("header") 9 | .contains("load") 10 | .click() 11 | cy.contains("Load Theme").should("be.visible") 12 | cy.contains("salmon").click() 13 | cy.get("header").should( 14 | "have.css", 15 | "background-color", 16 | "rgb(255, 140, 105)" 17 | ) 18 | cy.get("main section p#bodyText").should( 19 | "have.css", 20 | "font-family", 21 | "Lora, serif" 22 | ) 23 | cy.get("main section h2").should( 24 | "have.css", 25 | "font-family", 26 | '"Istok Web", sans-serif' 27 | ) 28 | }) 29 | 30 | it("can save, load and delete themes", function() { 31 | cy.wait(10000) 32 | cy.contains("What body font do you like?").should("be.visible") 33 | cy.get("#selectWebFont").select("Arimo") 34 | cy.contains("Next").click() 35 | cy.wait(1000) 36 | 37 | cy.contains("What heading font do you like?").should("be.visible") 38 | cy.get("#selectWebFont").select("Tinos") 39 | cy.contains("Next").click() 40 | cy.wait(1000) 41 | 42 | cy.contains("Get a color scheme...").should("be.visible") 43 | cy.get("#colorsPreset").select("indigo") 44 | cy.contains("Next").click() 45 | cy.wait(1000) 46 | 47 | cy.contains("Here are your colors...").should("be.visible") 48 | cy.contains("Next").click() 49 | cy.wait(1000) 50 | 51 | cy.wait(1000) 52 | 53 | cy.get("header") 54 | .contains("save") 55 | .click() 56 | 57 | cy.wait(1000) 58 | 59 | cy.contains("Save Theme").should("be.visible") 60 | cy.get("#themeName").type("Beep Boop Bop") 61 | cy.get("button") 62 | .contains("Save") 63 | .click() 64 | 65 | cy.reload() 66 | cy.wait(10000) 67 | 68 | cy.get("header") 69 | .contains("load") 70 | .click() 71 | 72 | cy.contains("Beep Boop Bop").click() 73 | cy.get("header").should("have.css", "background-color", "rgb(75, 0, 130)") 74 | cy.get("main section p#bodyText").should( 75 | "have.css", 76 | "font-family", 77 | "Arimo, sans-serif" 78 | ) 79 | cy.get("main section h2").should("have.css", "font-family", "Tinos, serif") 80 | }) 81 | 82 | it("can cancel load theme", function() { 83 | cy.wait(10000) 84 | cy.get("header") 85 | .contains("load") 86 | .click() 87 | cy.contains("Load Theme").should("be.visible") 88 | cy.get("button") 89 | .contains("Cancel") 90 | .click() 91 | cy.contains("Load Theme").should("not.exist") 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /cypress/integration/Playground.Nav.spec.js: -------------------------------------------------------------------------------- 1 | describe("Playground", function() { 2 | beforeEach(function() { 3 | cy.visit("/") 4 | }) 5 | 6 | it("can navigate to font, color, theme and ui", function() { 7 | cy.get("h2") 8 | .contains("Font Families") 9 | .should("be.visible") 10 | cy.get("h2") 11 | .contains("Type Scale") 12 | .should("be.visible") 13 | cy.get("nav") 14 | .contains("COLOR") 15 | .click() 16 | cy.get("h2") 17 | .contains("Font Families") 18 | .should("not.exist") 19 | cy.get("h2") 20 | .contains("Type Scale") 21 | .should("not.exist") 22 | cy.get("h2") 23 | .contains("Color Palette") 24 | .should("be.visible") 25 | cy.get("nav") 26 | .contains("THEME") 27 | .click() 28 | cy.get("h2") 29 | .contains("Font Families") 30 | .should("not.exist") 31 | cy.get("h2") 32 | .contains("Type Scale") 33 | .should("not.exist") 34 | cy.get("h2") 35 | .contains("Color Palette") 36 | .should("not.exist") 37 | cy.get("h2") 38 | .contains("Your Theme Object") 39 | .should("be.visible") 40 | cy.get("nav") 41 | .contains("UI") 42 | .click() 43 | cy.get("h2") 44 | .contains("Font Families") 45 | .should("not.exist") 46 | cy.get("h2") 47 | .contains("Type Scale") 48 | .should("not.exist") 49 | cy.get("h2") 50 | .contains("Color Palette") 51 | .should("not.exist") 52 | cy.get("h2") 53 | .contains("Your Theme Object") 54 | .should("not.exist") 55 | cy.get("a") 56 | .contains("Theme UI Docs") 57 | .should("be.visible") 58 | cy.get("nav") 59 | .contains("FONT") 60 | .click() 61 | cy.get("h2") 62 | .contains("Font Families") 63 | .should("be.visible") 64 | cy.get("h2") 65 | .contains("Type Scale") 66 | .should("be.visible") 67 | cy.get("h2") 68 | .contains("Color Palette") 69 | .should("not.exist") 70 | cy.get("h2") 71 | .contains("Your Theme Object") 72 | .should("not.exist") 73 | cy.get("a") 74 | .contains("Theme UI Docs") 75 | .should("not.exist") 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /cypress/integration/Playground.Random.spec.js: -------------------------------------------------------------------------------- 1 | describe("Playground", function() { 2 | beforeEach(function() { 3 | cy.visit("/") 4 | }) 5 | 6 | it("can generate random theme", function() { 7 | cy.wait(10000) 8 | cy.get("header h1").should( 9 | "have.css", 10 | "font-family", 11 | '"avenir next", avenir, helvetica, arial, sans-serif' 12 | ) 13 | cy.get("main p#bodyText").should( 14 | "have.css", 15 | "font-family", 16 | '"avenir next", avenir, helvetica, arial, sans-serif' 17 | ) 18 | cy.get("header").should("have.css", "background-color", "rgb(65, 105, 225)") 19 | cy.get("header") 20 | .contains("random") 21 | .click() 22 | cy.get("header h1").should( 23 | "not.have.css", 24 | "font-family", 25 | '"avenir next", avenir, helvetica, arial, sans-serif' 26 | ) 27 | cy.get("main p#bodyText").should( 28 | "not.have.css", 29 | "font-family", 30 | '"avenir next", avenir, helvetica, arial, sans-serif' 31 | ) 32 | cy.get("header").should( 33 | "not.have.css", 34 | "background-color", 35 | "rgb(65, 105, 225)" 36 | ) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /cypress/integration/Playground.Save.spec.js: -------------------------------------------------------------------------------- 1 | describe("Playground", function() { 2 | beforeEach(function() { 3 | cy.visit("/") 4 | }) 5 | 6 | it("requires name to save theme", function() { 7 | cy.wait(10000) 8 | cy.get("header") 9 | .contains("save") 10 | .click() 11 | 12 | cy.contains("Save Theme").should("be.visible") 13 | cy.get("button") 14 | .contains("Save") 15 | .should("be.disabled") 16 | cy.get("#themeName").type("a") 17 | cy.get("button") 18 | .contains("Save") 19 | .should("not.be.disabled") 20 | cy.get("button") 21 | .contains("Save") 22 | .click() 23 | 24 | cy.get("header") 25 | .contains("load") 26 | .click() 27 | 28 | cy.contains("Load Theme").should("be.visible") 29 | cy.get("button") 30 | .contains("a") 31 | .should("be.visible") 32 | }) 33 | 34 | it("can cancel save theme", function() { 35 | cy.wait(10000) 36 | cy.get("header") 37 | .contains("save") 38 | .click() 39 | 40 | cy.contains("Save Theme").should("be.visible") 41 | cy.get("button") 42 | .contains("Cancel") 43 | .click() 44 | cy.contains("Save Theme").should("not.exist") 45 | }) 46 | 47 | it("can suggest theme name", function() { 48 | cy.wait(10000) 49 | cy.get("header") 50 | .contains("save") 51 | .click() 52 | 53 | cy.contains("Save Theme").should("be.visible") 54 | cy.get("button") 55 | .contains("Yes") 56 | .click() 57 | cy.get("input#themeName").should("have.value", "Royal Blue") 58 | cy.get("button") 59 | .contains("Save") 60 | .click() 61 | 62 | cy.get("header") 63 | .contains("load") 64 | .click() 65 | 66 | cy.contains("Load Theme").should("be.visible") 67 | cy.get("button") 68 | .contains("Royal Blue") 69 | .should("be.visible") 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | module.exports = (on, config) => { 15 | // `on` is used to hook into various events Cypress emits 16 | // `config` is the resolved Cypress config 17 | } 18 | -------------------------------------------------------------------------------- /cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | Cypress.Commands.add("uploadFile", (fileName, selector) => { 2 | cy.get(selector).then(subject => { 3 | cy.fixture(fileName, "base64").then(content => { 4 | const el = subject[0] 5 | const blob = b64toBlob(content) 6 | const testFile = new File([blob], fileName) 7 | const dataTransfer = new DataTransfer() 8 | 9 | dataTransfer.items.add(testFile) 10 | el.files = dataTransfer.files 11 | }) 12 | }) 13 | }) 14 | 15 | function b64toBlob(b64Data, contentType, sliceSize) { 16 | contentType = contentType || "" 17 | sliceSize = sliceSize || 512 18 | 19 | var byteCharacters = atob(b64Data) 20 | var byteArrays = [] 21 | 22 | for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) { 23 | var slice = byteCharacters.slice(offset, offset + sliceSize) 24 | 25 | var byteNumbers = new Array(slice.length) 26 | for (var i = 0; i < slice.length; i++) { 27 | byteNumbers[i] = slice.charCodeAt(i) 28 | } 29 | 30 | var byteArray = new Uint8Array(byteNumbers) 31 | 32 | byteArrays.push(byteArray) 33 | } 34 | 35 | var blob = new Blob(byteArrays, { type: contentType }) 36 | blob.lastModifiedDate = new Date() 37 | return blob 38 | } 39 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import "./commands" 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: `Design System Playground`, 4 | description: `Play with typography, colors and themeable components in your design system.`, 5 | author: `@johnpolacek`, 6 | }, 7 | plugins: [ 8 | `gatsby-plugin-react-helmet`, 9 | { 10 | resolve: `gatsby-source-filesystem`, 11 | options: { 12 | name: `images`, 13 | path: `${__dirname}/src/images`, 14 | }, 15 | }, 16 | `gatsby-transformer-sharp`, 17 | `gatsby-plugin-sharp`, 18 | { 19 | resolve: `gatsby-plugin-manifest`, 20 | options: { 21 | name: `design-system-playground`, 22 | short_name: `designplay`, 23 | start_url: `/`, 24 | background_color: `#fff`, 25 | theme_color: `#663399`, 26 | display: `minimal-ui`, 27 | icon: `src/images/icon.png`, // This path is relative to the root of the site. 28 | }, 29 | }, 30 | // this (optional) plugin enables Progressive Web App + Offline functionality 31 | // To learn more, visit: https://gatsby.dev/offline 32 | // `gatsby-plugin-offline`, 33 | ], 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "design-system-playground", 3 | "description": "Experiment with design systems by playing with typography, colors and components to create a theme.", 4 | "version": "1.0.0", 5 | "author": "John Polacek (https://johnpolacek.com/)", 6 | "dependencies": { 7 | "@emotion/core": "^10.1.1", 8 | "@emotion/styled": "^10.0.27", 9 | "@mdx-js/react": "^1.6.22", 10 | "@theme-ui/color": "^0.2.53", 11 | "@theme-ui/presets": "^0.2.44", 12 | "@theme-ui/prism": "^0.2.50", 13 | "color-scheme": "^1.0.1", 14 | "colorthief": "^2.3.2", 15 | "emotion-theming": "^10.0.27", 16 | "gatsby": "^2.31.1", 17 | "gatsby-image": "^2.10.0", 18 | "gatsby-plugin-manifest": "^2.11.0", 19 | "gatsby-plugin-offline": "^3.9.0", 20 | "gatsby-plugin-react-helmet": "^3.9.0", 21 | "gatsby-plugin-sharp": "^2.13.3", 22 | "gatsby-source-filesystem": "^2.10.0", 23 | "gatsby-transformer-sharp": "^2.11.0", 24 | "gsap": "^3.6.0", 25 | "js-file-download": "^0.4.12", 26 | "ntcjs": "^1.1.2", 27 | "polished": "^3.6.7", 28 | "prism-react-renderer": "^0.1.7", 29 | "prop-types": "^15.7.2", 30 | "react": "^16.14.0", 31 | "react-dom": "^16.14.0", 32 | "react-ga": "^2.7.0", 33 | "react-helmet": "^5.2.1", 34 | "react-icons": "^3.11.0", 35 | "react-syntax-highlighter": "^11.0.2", 36 | "rgb-hex": "^3.0.0", 37 | "theme-ui": "^0.2.52" 38 | }, 39 | "devDependencies": { 40 | "chai-colors": "^1.0.1", 41 | "cypress": "^6.3.0", 42 | "prettier": "^1.19.1", 43 | "start-server-and-test": "^1.11.7" 44 | }, 45 | "keywords": [ 46 | "gatsby" 47 | ], 48 | "license": "MIT", 49 | "scripts": { 50 | "build": "gatsby build", 51 | "cypress": "cypress open", 52 | "develop": "gatsby develop", 53 | "dev": "gatsby develop -o", 54 | "format": "prettier --write \"**/*.{js,jsx,json,md}\"", 55 | "format-watch": "onchange \"**/*.{js,jsx,json,md}\" -- prettier --write {{changed}}", 56 | "start": "npm run develop", 57 | "serve": "gatsby serve", 58 | "test": "cypress run --env mode=headless" 59 | }, 60 | "repository": { 61 | "type": "git", 62 | "url": "https://github.com/gatsbyjs/gatsby-starter-default" 63 | }, 64 | "bugs": { 65 | "url": "https://github.com/gatsbyjs/gatsby/issues" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /src/animate/useTweenLite.js: -------------------------------------------------------------------------------- 1 | // based on: https://github.com/johnlindquist/use-gsap 2 | 3 | import { useRef, useEffect, useCallback } from "react" 4 | import { TweenLite } from "gsap" 5 | 6 | export default (duration, options, onMount = true) => { 7 | let ref = useRef(null) 8 | let go = useCallback(() => { 9 | TweenLite.to(ref.current, duration, options) 10 | }, [duration, options]) 11 | 12 | useEffect(() => { 13 | if (onMount) go() 14 | }, [ref, go, options, onMount]) 15 | 16 | return [ref, go] 17 | } 18 | -------------------------------------------------------------------------------- /src/animate/useTweenMax.js: -------------------------------------------------------------------------------- 1 | // based on: https://github.com/johnlindquist/use-gsap 2 | 3 | import { useRef, useEffect, useCallback } from "react" 4 | import { TweenMax } from "gsap" 5 | 6 | export default (duration, options, onMount = true) => { 7 | let ref = useRef(null) 8 | let go = useCallback(() => { 9 | TweenMax.to(ref.current, duration, options) 10 | }, [duration, options]) 11 | 12 | useEffect(() => { 13 | if (onMount) go() 14 | }, [ref, go, options, onMount]) 15 | 16 | return [ref, go] 17 | } 18 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { lightness } from "@theme-ui/color" 4 | // eslint-disable-next-line 5 | import React, { useContext } from "react" 6 | import Flex from "./ui/Flex" 7 | import { ThemeContext } from "../context/ThemeContext" 8 | 9 | export default props => { 10 | const { theme, isDarkMode } = useContext(ThemeContext) 11 | 12 | return ( 13 | <> 14 | 141 | 142 | ) 143 | } 144 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { lightness } from "@theme-ui/color" 4 | // eslint-disable-next-line 5 | import React, { useContext, useState, useEffect } from "react" 6 | import fileDownload from "js-file-download" 7 | import { 8 | ThemeContext, 9 | getColorScheme, 10 | getRandomFont, 11 | } from "../context/ThemeContext" 12 | import { NavContext, PlaygroundViews } from "../context/NavContext" 13 | import Logo from "./ui/Logo" 14 | import GithubLink from "./ui/GithubLink" 15 | import HeaderButton from "./ui/HeaderButton" 16 | import Button from "./ui/Button" 17 | 18 | export default props => { 19 | const { playgroundView, setPlaygroundView, setPlaygroundHeight } = useContext( 20 | NavContext 21 | ) 22 | 23 | const { theme, setTheme, isDarkMode } = useContext(ThemeContext) 24 | 25 | const editTheme = () => { 26 | setPlaygroundView(PlaygroundViews.FONT_BODY) 27 | setPlaygroundHeight("800px") 28 | } 29 | 30 | const downloadTheme = () => { 31 | fileDownload("export default " + JSON.stringify(theme, null, 2), "theme.js") 32 | } 33 | 34 | const randomTheme = () => { 35 | let newTheme = { ...theme } 36 | newTheme.colors = getColorScheme() 37 | 38 | const randomBodyFont = getRandomFont() 39 | newTheme.fonts.body = randomBodyFont.family 40 | 41 | const randomHeadingFont = getRandomFont() 42 | newTheme.fonts.heading = randomHeadingFont.family 43 | setTheme({ ...newTheme }) 44 | } 45 | 46 | const loadTheme = () => { 47 | setPlaygroundView(PlaygroundViews.LOAD) 48 | setPlaygroundHeight("100vh") 49 | } 50 | 51 | const saveTheme = () => { 52 | setPlaygroundView(PlaygroundViews.SAVE) 53 | setPlaygroundHeight("800px") 54 | } 55 | 56 | return ( 57 |
71 |
72 | 73 |
74 |
83 | {playgroundView !== PlaygroundViews.WELCOME ? ( 84 | <> 85 | 86 | load 87 | 88 | 89 | save 90 | 91 | 92 | play 93 | 94 | 95 | download 96 | 97 | 98 | random 99 | 100 | 101 | ) : ( 102 | 117 | )} 118 | 119 |
120 |
121 | ) 122 | } 123 | -------------------------------------------------------------------------------- /src/components/Main.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useContext } from "react" 4 | import useTweenMax from "../animate/useTweenMax" 5 | import { NavContext } from "../context/NavContext" 6 | import { ThemeContext } from "../context/ThemeContext" 7 | import { Typography, Color, UI, Theme } from "./sections" 8 | 9 | export default props => { 10 | const { currSection } = useContext(NavContext) 11 | const { theme } = useContext(ThemeContext) 12 | 13 | const [tweenRef] = useTweenMax(1, { 14 | opacity: 1, 15 | delay: 1, 16 | immediateRender: true, 17 | }) 18 | 19 | return ( 20 |
32 | { 33 | { 34 | FONT: , 35 | COLOR: , 36 | UI: , 37 | THEME: , 38 | }[currSection] 39 | } 40 |
41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/components/Nav.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useState, useEffect } from "react" 4 | import { lightness } from "@theme-ui/color" 5 | import useTweenMax from "../animate/useTweenMax" 6 | import { Expo } from "gsap" 7 | import { useContext } from "react" 8 | import { NavContext, NavSections } from "../context/NavContext" 9 | import { ThemeContext } from "../context/ThemeContext" 10 | 11 | const NavButton = props => { 12 | const [tweenRef] = useTweenMax( 13 | 1, 14 | props.hasPlayed 15 | ? {} 16 | : { 17 | startAt: { y: 10, opacity: 0 }, 18 | opacity: 1, 19 | y: 0, 20 | delay: 2 + props.index * 0.2, 21 | ease: Expo.easeOut, 22 | immediateRender: true, 23 | } 24 | ) 25 | 26 | // do not need this once theme-ui bug is fixed - https://github.com/system-ui/theme-ui/issues/360 27 | const { theme, isDarkMode } = useContext(ThemeContext) 28 | 29 | const { currSection, setCurrSection } = useContext(NavContext) 30 | const isCurrent = currSection === props.section 31 | 32 | const styleOuter = { 33 | width: "25%", 34 | textAlign: "center", 35 | bg: isCurrent 36 | ? theme.colors.background 37 | : lightness(theme.colors.primary, isDarkMode() ? 0.1 : 0.95), 38 | } 39 | 40 | const styleInner = { 41 | display: "block", 42 | fontSize: ["4vw", "3vw", "2.5vw", "2vw"], 43 | textDecoration: "none", 44 | py: "4vh", 45 | opacity: props.hasPlayed ? 1 : 0, 46 | color: isCurrent ? "primary" : lightness(theme.colors.primary, 0.6), 47 | ":hover": { 48 | bg: isCurrent 49 | ? theme.colors.background 50 | : lightness(theme.colors.primary, isDarkMode() ? 0.08 : 0.925), 51 | color: isCurrent ? "primary" : lightness(theme.colors.primary, 0.4), 52 | }, 53 | } 54 | 55 | return ( 56 |
  • 57 | {isCurrent ? ( 58 | 59 | {props.section} 60 | 61 | ) : ( 62 | { 67 | e.preventDefault() 68 | setCurrSection(e.target.textContent) 69 | }} 70 | > 71 | {props.section} 72 | 73 | )} 74 |
  • 75 | ) 76 | } 77 | 78 | export default props => { 79 | const [hasPlayed, setHasPlayed] = useState(false) 80 | useEffect(() => { 81 | setHasPlayed(true) 82 | }, []) 83 | 84 | return ( 85 | 108 | ) 109 | } 110 | -------------------------------------------------------------------------------- /src/components/Wrapper.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useContext } from "react" 5 | import { ThemeContext } from "../context/ThemeContext" 6 | 7 | export default props => { 8 | const { theme } = useContext(ThemeContext) 9 | 10 | const webfonts = {} 11 | Object.keys(theme.fonts) 12 | .map(font => { 13 | return theme.fonts[font] 14 | }) 15 | .filter(font => { 16 | return ( 17 | font.split(",").length === 2 && font.split(",")[0].indexOf("'") === 0 18 | ) 19 | }) 20 | .map(font => { 21 | return font.split(",")[0].split("'")[1] 22 | }) 23 | .forEach(font => { 24 | webfonts[font] = 25 | "https://fonts.googleapis.com/css?family=" + 26 | font.replace(/ /g, "+") + 27 | ":100,200,300,400,500,600,700,800,900" 28 | }) 29 | 30 | return ( 31 | <> 32 |
    {props.children}
    33 | {Object.keys(webfonts).map(webfont => { 34 | return 35 | })} 36 | 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/components/head/GlobalCSS.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { Global, css } from "@emotion/core" 3 | 4 | const GlobalCSS = props => ( 5 | 225 | ) 226 | 227 | export default GlobalCSS 228 | -------------------------------------------------------------------------------- /src/components/head/SEO.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that queries for data with 3 | * Gatsby's useStaticQuery React hook 4 | * 5 | * See: https://www.gatsbyjs.org/docs/use-static-query/ 6 | */ 7 | 8 | import React from "react" 9 | import PropTypes from "prop-types" 10 | import Helmet from "react-helmet" 11 | import { useStaticQuery, graphql } from "gatsby" 12 | 13 | function SEO({ description, lang, meta, title }) { 14 | const { site } = useStaticQuery( 15 | graphql` 16 | query { 17 | site { 18 | siteMetadata { 19 | title 20 | description 21 | author 22 | } 23 | } 24 | } 25 | ` 26 | ) 27 | 28 | const metaDescription = description || site.siteMetadata.description 29 | 30 | return ( 31 | 76 | ) 77 | } 78 | 79 | SEO.defaultProps = { 80 | lang: `en`, 81 | meta: [], 82 | description: ``, 83 | } 84 | 85 | SEO.propTypes = { 86 | description: PropTypes.string, 87 | lang: PropTypes.string, 88 | meta: PropTypes.arrayOf(PropTypes.object), 89 | title: PropTypes.string.isRequired, 90 | } 91 | 92 | export default SEO 93 | -------------------------------------------------------------------------------- /src/components/playground/Choose.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useState, useEffect } from "react" 4 | import useTweenMax from "../../animate/useTweenMax" 5 | import { Expo } from "gsap" 6 | import Container from "./Container" 7 | import Header from "./Header" 8 | import Main from "./Main" 9 | import NextButton from "./NextButton" 10 | 11 | export default props => { 12 | const [hasPlayed, setHasPlayed] = useState(false) 13 | useEffect(() => { 14 | setHasPlayed(true) 15 | }, []) 16 | 17 | const [tweenOutRef, tweenOut] = useTweenMax( 18 | 0.5, 19 | { 20 | opacity: 0, 21 | x: "-100vw", 22 | ease: Expo.easeInOut, 23 | onComplete: props.onComplete, 24 | }, 25 | false 26 | ) 27 | 28 | return ( 29 | 30 |
    31 | {props.title} 32 |
    33 |
    34 | {props.children} 35 |
    36 | tweenOut()} /> 37 |
    38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/components/playground/ChooseColorScheme.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useState, useContext } from "react" 5 | import { ThemeContext, getColorScheme } from "../../context/ThemeContext" 6 | import Choose from "./Choose" 7 | import Flex from "../ui/Flex" 8 | import Button from "../ui/Button" 9 | import ColorSchemeSwatch from "./ColorSchemeSwatch" 10 | import ColorSchemeFromColor from "./ColorSchemeFromColor" 11 | import ColorSchemeFromImage from "./ColorSchemeFromImage" 12 | import ColorSchemeFromPreset from "./ColorSchemeFromPreset" 13 | 14 | export default props => { 15 | const { theme, setTheme } = useContext(ThemeContext) 16 | const [colors, setColors] = useState(theme.colors) 17 | const [schemeMode, setSchemeMode] = useState(null) 18 | 19 | const FROM_PRESET = "FROM_PRESET" 20 | const FROM_COLOR = "FROM_COLOR" 21 | const FROM_IMAGE = "FROM_IMAGE" 22 | 23 | const onSelectColors = (colors, mode) => { 24 | let newTheme = { ...theme } 25 | newTheme.colors = colors 26 | setTheme({ ...newTheme }) 27 | setColors(colors) 28 | setSchemeMode(mode) 29 | } 30 | 31 | const goRandom = () => { 32 | let newTheme = { ...theme } 33 | newTheme.colors = getColorScheme() 34 | setTheme({ ...newTheme }) 35 | setColors(newTheme.colors) 36 | setSchemeMode(null) 37 | } 38 | 39 | return ( 40 | 45 |
    46 | {Object.values(colors) 47 | .filter( 48 | color => 49 | typeof color === "string" && 50 | color.toLowerCase() !== "#fff" && 51 | color.toLowerCase() !== "#ffffff" && 52 | color !== "#000" && 53 | color !== "#000000" && 54 | color !== "transparent" 55 | ) 56 | .map((color, i) => ( 57 | 58 | ))} 59 |
    60 |

    61 | 74 | or... 75 |

    76 | 77 |
    78 |

    From Preset

    79 | onSelectColors(colors, FROM_PRESET)} 82 | /> 83 |
    84 |
    85 |

    From Color

    86 | onSelectColors(colors, FROM_COLOR)} 89 | /> 90 |
    91 |
    92 |

    From Image

    93 | onSelectColors(colors, FROM_IMAGE)} 96 | /> 97 |
    98 |
    99 |
    100 | ) 101 | } 102 | -------------------------------------------------------------------------------- /src/components/playground/ChooseColors.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useContext } from "react" 4 | import { 5 | ThemeContext, 6 | getMainColors, 7 | getOtherColors, 8 | } from "../../context/ThemeContext" 9 | import Choose from "./Choose" 10 | import ColorAdd from "./ColorAdd" 11 | import Flex from "../ui/Flex" 12 | import ColorPicker from "./ColorPicker" 13 | 14 | export default props => { 15 | const { theme } = useContext(ThemeContext) 16 | 17 | return ( 18 | 19 | 20 | {getMainColors().map((color, i) => ( 21 | 22 | ))} 23 | 24 | 25 | {getOtherColors(theme).map((color, i) => ( 26 | 32 | ))} 33 | 34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/components/playground/ChooseFonts.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useContext, useState } from "react" 5 | import { ThemeContext, getRandomFont } from "../../context/ThemeContext" 6 | import FontSelectBrowser from "./FontSelectBrowser" 7 | import FontSelectWeb from "./FontSelectWeb" 8 | import FontWeights from "../sections/typography/FontWeights" 9 | import Choose from "./Choose" 10 | import Button from "../ui/Button" 11 | 12 | export default props => { 13 | const [currFont, setCurrFont] = useState(null) 14 | const { theme, setTheme } = useContext(ThemeContext) 15 | const onFontSelect = (fontName, fontFamily) => { 16 | let newTheme = { ...theme } 17 | newTheme.fonts[props.fontKey] = fontFamily 18 | setTheme({ ...newTheme }) 19 | setCurrFont(fontName) 20 | } 21 | 22 | const onWebFontSelect = (fontName, fontType) => { 23 | let newTheme = { ...theme } 24 | newTheme.fonts[props.fontKey] = "'" + fontName + "', " + fontType 25 | setTheme({ ...newTheme }) 26 | setCurrFont(fontName) 27 | } 28 | 29 | const goRandom = () => { 30 | const randomFont = getRandomFont() 31 | let newTheme = { ...theme } 32 | newTheme.fonts[props.fontKey] = randomFont.family 33 | setTheme({ ...newTheme }) 34 | setCurrFont(randomFont.name) 35 | } 36 | 37 | const selectBody = () => { 38 | let newTheme = { ...theme } 39 | newTheme.fonts[props.fontKey] = theme.fonts.body 40 | setTheme({ ...newTheme }) 41 | setCurrFont(theme.fonts.body.replace(/'/g, "").split(",")[0]) 42 | } 43 | 44 | return ( 45 | 50 |
    51 | 52 |
    53 |
    54 | 55 | 56 | 63 | {" "} 64 | or{" "} 65 | 66 | 80 | {props.fontKey !== "body" && ( 81 | <> 82 | 83 | {" "} 84 | or{" "} 85 | 86 | 99 | 100 | )} 101 |
    102 |
    103 | ) 104 | } 105 | -------------------------------------------------------------------------------- /src/components/playground/ColorAdd.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import { useContext, useState } from "react" 6 | import { ThemeContext } from "../../context/ThemeContext" 7 | import Button from "../ui/Button" 8 | 9 | export default props => { 10 | const [colorName, setColorName] = useState("") 11 | const [colorValue, setColorValue] = useState("#") 12 | 13 | const { theme, setTheme } = useContext(ThemeContext) 14 | 15 | const onNameChange = e => { 16 | setColorName(e.target.value) 17 | } 18 | 19 | const onColorChange = e => { 20 | let newColor = e.target.value.toUpperCase() 21 | if (newColor.indexOf("#") !== 0) { 22 | newColor = "#" + newColor 23 | } 24 | setColorValue(e.target.value) 25 | } 26 | 27 | const onColorAddConfirm = e => { 28 | let newTheme = { ...theme } 29 | newTheme.colors[colorName.toLowerCase().replace(/\s/g, "")] = colorValue 30 | setTheme({ ...newTheme }) 31 | setColorName("") 32 | setColorValue("#") 33 | } 34 | 35 | return ( 36 |
    37 |

    Add Color

    38 |
    50 |
    59 | 62 | 76 |
    77 |
    86 | 89 | 97 |
    98 | 121 |
    122 | ) 123 | } 124 | -------------------------------------------------------------------------------- /src/components/playground/ColorPicker.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useState, useContext, useEffect } from "react" 5 | import { ThemeContext } from "../../context/ThemeContext" 6 | import useTweenMax from "../../animate/useTweenMax" 7 | import { Expo } from "gsap" 8 | 9 | export default props => { 10 | const { theme, setTheme } = useContext(ThemeContext) 11 | 12 | const [hasPlayed, setHasPlayed] = useState(false) 13 | useEffect(() => { 14 | setHasPlayed(true) 15 | }, []) 16 | 17 | const [tweenRef] = useTweenMax( 18 | 1, 19 | hasPlayed 20 | ? {} 21 | : { 22 | startAt: { 23 | scale: 0.5, 24 | opacity: 0, 25 | }, 26 | opacity: 1, 27 | scale: 1, 28 | delay: 0.25 + Math.random(), 29 | ease: Expo.easeOut, 30 | } 31 | ) 32 | 33 | const onColorChange = e => { 34 | const newColor = e.target.value 35 | if (/^#([0-9A-F]{3}){1,2}$/i.test(newColor)) { 36 | let newTheme = { ...theme } 37 | newTheme.colors[props.color] = e.target.value 38 | setTheme({ ...newTheme }) 39 | } 40 | } 41 | 42 | const onDelete = e => { 43 | let newTheme = { ...theme } 44 | delete newTheme.colors[props.color] 45 | setTheme({ ...newTheme }) 46 | } 47 | 48 | return ( 49 |
    54 |
    64 | {props.canDelete && ( 65 |
    85 | × 86 |
    87 | )} 88 |
    89 |

    97 | {props.color} 98 |

    99 | {props.canEdit ? ( 100 | 106 | ) : ( 107 | 108 | {theme.colors[props.color]} 109 | 110 | )} 111 |
    112 | ) 113 | } 114 | -------------------------------------------------------------------------------- /src/components/playground/ColorSchemeFromColor.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useState } from "react" 5 | import Flex from "../ui/Flex" 6 | import { getColorScheme } from "../../context/ThemeContext" 7 | 8 | export default props => { 9 | const [selected, setSelected] = useState(null) 10 | 11 | const onColorChange = e => { 12 | let newColor = e.target.value 13 | if (newColor.indexOf("#") !== 0) { 14 | newColor = "#" + newColor 15 | } 16 | if (newColor.length === 7 && /^#([0-9A-F]{3}){1,2}$/i.test(newColor)) { 17 | const newColors = getColorScheme(newColor) 18 | props.onSelect(newColors) 19 | setSelected(newColor) 20 | e.target.value = "" 21 | } 22 | } 23 | 24 | return ( 25 |
    26 | 34 | {props.isActive && ( 35 | 46 | {selected} 47 | 48 | )} 49 |
    50 | ) 51 | } 52 | -------------------------------------------------------------------------------- /src/components/playground/ColorSchemeFromImage.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useState } from "react" 5 | import { isDark } from "../../context/ThemeContext" 6 | import ColorThief from "colorthief" 7 | import rgbHex from "rgb-hex" 8 | import ntc from "ntcjs" 9 | 10 | export default props => { 11 | const [image, setImage] = useState(null) 12 | const colorThief = new ColorThief() 13 | 14 | const onFileSelect = e => { 15 | const reader = new FileReader() 16 | reader.readAsDataURL(e.target.files[0]) 17 | reader.onload = event => { 18 | setImage(event.target.result) 19 | } 20 | } 21 | 22 | const onImageLoad = e => { 23 | const colors = colorThief.getPalette(e.target) 24 | let newColors = {} 25 | colors.forEach((color, index) => { 26 | const colorVal = "#" + rgbHex("rgb(" + color.toString() + ")") 27 | if (index === 0) { 28 | newColors.primary = colorVal 29 | } else if (index === 1) { 30 | newColors.secondary = colorVal 31 | } else { 32 | const colorName = ntc 33 | .name(colorVal)[1] 34 | .toLowerCase() 35 | .split(" ") 36 | .pop() 37 | newColors[colorName] = colorVal 38 | } 39 | }) 40 | newColors.background = isDark(newColors.primary) ? "#fff" : "#000" 41 | newColors.text = isDark(newColors.primary) ? "#000" : "#fff" 42 | props.onSelect(newColors) 43 | } 44 | 45 | return ( 46 | <> 47 | 54 | {image ? ( 55 | Color Scheme Generated 66 | ) : ( 67 |
    68 | )} 69 | 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /src/components/playground/ColorSchemeFromPreset.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useState } from "react" 5 | import { Themes } from "../../context/ThemeContext" 6 | import Flex from "../ui/Flex" 7 | 8 | export default props => { 9 | const [preset, setPreset] = useState(null) 10 | 11 | return ( 12 | <> 13 | 33 | {props.isActive && ( 34 | 45 | {preset} 46 | 47 | )} 48 | 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/components/playground/ColorSchemeSwatch.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useState, useEffect } from "react" 5 | import useTweenMax from "../../animate/useTweenMax" 6 | import { Expo } from "gsap" 7 | 8 | export default props => { 9 | const [hasPlayed, setHasPlayed] = useState(false) 10 | useEffect(() => { 11 | setHasPlayed(true) 12 | }, []) 13 | 14 | const [tweenRef] = useTweenMax( 15 | 1, 16 | hasPlayed 17 | ? {} 18 | : { 19 | startAt: { 20 | x: Math.random() * 40 - 20, 21 | y: Math.random() * 40 - 20, 22 | opacity: 0, 23 | }, 24 | opacity: 1, 25 | x: 0, 26 | y: 0, 27 | delay: 0.25 + props.i * 0.05, 28 | ease: Expo.easeOut, 29 | } 30 | ) 31 | return ( 32 |
    46 | ) 47 | } 48 | -------------------------------------------------------------------------------- /src/components/playground/Container.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import React, { useContext } from "react" 4 | import { NavContext } from "../../context/NavContext" 5 | 6 | export default React.forwardRef((props, ref) => { 7 | const { playgroundHeight } = useContext(NavContext) 8 | 9 | return ( 10 |
    23 | {props.children} 24 |
    25 | ) 26 | }) 27 | -------------------------------------------------------------------------------- /src/components/playground/FontSelectBrowser.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { BrowserFonts } from "../../context/ThemeContext" 4 | 5 | export default props => { 6 | const onFontSelect = e => { 7 | if (e.target.value !== "") { 8 | props.onSelect( 9 | e.target.options[e.target.selectedIndex].text, 10 | e.target.value 11 | ) 12 | e.target.value = "" 13 | } 14 | } 15 | 16 | return ( 17 | 43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/components/playground/FontSelectWeb.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { WebFonts } from "../../context/ThemeContext" 4 | 5 | export default props => { 6 | const onSelect = e => { 7 | if (e.target.value !== "") { 8 | const fontName = e.target.value.split(",")[0] 9 | const fontType = e.target.value.split(",")[1] 10 | props.onSelect(fontName, fontType) 11 | e.target.value = "" 12 | } 13 | } 14 | 15 | return ( 16 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/components/playground/Header.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import Heading from "../ui/Heading" 6 | import useTweenMax from "../../animate/useTweenMax" 7 | import { Expo } from "gsap" 8 | 9 | export default props => { 10 | const [tweenDownRef] = useTweenMax( 11 | 1, 12 | props.animate 13 | ? { 14 | startAt: { y: "-25vh", opacity: 0 }, 15 | opacity: 1, 16 | y: 0, 17 | ease: Expo.easeOut, 18 | immediateRender: true, 19 | } 20 | : {} 21 | ) 22 | 23 | return ( 24 |
    29 | 40 | {props.children} 41 | 42 |
    43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /src/components/playground/LoadTheme.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import React, { useState, useEffect, useContext } from "react" 4 | import { ThemeContext } from "../../context/ThemeContext" 5 | import Container from "./Container" 6 | import Header from "./Header" 7 | import Main from "./Main" 8 | import Button from "../ui/Button" 9 | import { NavContext, PlaygroundViews } from "../../context/NavContext" 10 | import presets from "../../themes/index" 11 | 12 | export default props => { 13 | const [myThemes, setMyThemes] = React.useState( 14 | localStorage.getItem("themes") 15 | ? JSON.parse(localStorage.getItem("themes")) 16 | : null 17 | ) 18 | const { setPlaygroundView, setPlaygroundHeight } = useContext(NavContext) 19 | 20 | const { setTheme } = useContext(ThemeContext) 21 | 22 | const modes = { 23 | LOAD: "LOAD", 24 | DELETE: "DELETE", 25 | } 26 | 27 | const [mode, setMode] = useState(modes.LOAD) 28 | 29 | const [hasPlayed, setHasPlayed] = useState(false) 30 | useEffect(() => { 31 | setHasPlayed(true) 32 | }, []) 33 | 34 | const cancel = () => { 35 | setPlaygroundView(PlaygroundViews.COMPLETE) 36 | setPlaygroundHeight(0) 37 | } 38 | 39 | const load = themeIndex => { 40 | setPlaygroundView(PlaygroundViews.COMPLETE) 41 | setPlaygroundHeight(0) 42 | setTheme(myThemes[themeIndex]) 43 | } 44 | 45 | const remove = themeIndex => { 46 | console.log("remove") 47 | if (myThemes.length === 1) { 48 | localStorage.clear() 49 | setMyThemes(null) 50 | } else { 51 | const newMyThemes = myThemes.slice(0) 52 | newMyThemes.splice(themeIndex) 53 | localStorage.setItem("themes", JSON.stringify(newMyThemes)) 54 | setMyThemes(newMyThemes) 55 | } 56 | } 57 | 58 | const loadPreset = preset => { 59 | setPlaygroundView(PlaygroundViews.COMPLETE) 60 | setPlaygroundHeight(0) 61 | setTheme(presets[preset]) 62 | } 63 | 64 | return ( 65 | 66 |
    67 | {mode === modes.LOAD ? "Load" : "Delete"} Theme 68 |
    69 |
    70 | {mode === modes.LOAD && ( 71 |

    72 | {myThemes && myThemes.length > 0 ? ( 73 | <>Select one of your themes to load... 74 | ) : ( 75 | <> 76 | You don’t have any themes saved yet. Click that save button up 77 | there 78 | 79 | )} 80 |

    81 | )} 82 | {mode === modes.DELETE && ( 83 |

    84 | {myThemes && myThemes.length > 0 85 | ? "Choose themes you would like to remove." 86 | : "All saved themes have been removed."} 87 |

    88 | )} 89 | {myThemes && 90 | myThemes.length > 0 && 91 | myThemes.map((myTheme, i) => ( 92 | 101 | ))} 102 | {mode === modes.LOAD && ( 103 | <> 104 |

    Or choose a preset theme...

    105 |
    106 | {Object.keys(presets).map((preset, i) => ( 107 | 116 | ))} 117 |
    118 | 119 | )} 120 |
    121 | 127 | {myThemes && myThemes.length > 0 && mode === modes.LOAD && ( 128 | 136 | )} 137 |
    138 |
    139 |
    140 | ) 141 | } 142 | -------------------------------------------------------------------------------- /src/components/playground/Main.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import useTweenMax from "../../animate/useTweenMax" 4 | 5 | export default props => { 6 | const [tweenFadeInRef] = useTweenMax( 7 | 1, 8 | props.animate 9 | ? { 10 | opacity: 1, 11 | delay: 0.5, 12 | immediateRender: true, 13 | } 14 | : {} 15 | ) 16 | 17 | return ( 18 |
    23 | {props.children} 24 |
    25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/components/playground/NextButton.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import useTweenMax from "../../animate/useTweenMax" 4 | import { Back } from "gsap" 5 | import Button from "../ui/Button" 6 | 7 | export default props => { 8 | const [tweenUpRef] = useTweenMax( 9 | 0.5, 10 | props.animate 11 | ? { 12 | startAt: { y: "10vh", opacity: 0 }, 13 | opacity: 1, 14 | delay: 1, 15 | y: 0, 16 | ease: Back.easeOut, 17 | immediateRender: true, 18 | } 19 | : {} 20 | ) 21 | 22 | return ( 23 |
    24 | 27 |
    28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /src/components/playground/SaveTheme.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import React, { useState, useEffect, useContext } from "react" 4 | import { ThemeContext } from "../../context/ThemeContext" 5 | import Container from "./Container" 6 | import Header from "./Header" 7 | import Main from "./Main" 8 | import Flex from "../ui/Flex" 9 | import Button from "../ui/Button" 10 | import ntc from "ntcjs" 11 | import { NavContext, PlaygroundViews } from "../../context/NavContext" 12 | 13 | export default props => { 14 | const { setPlaygroundView, setPlaygroundHeight } = useContext(NavContext) 15 | 16 | const { theme } = useContext(ThemeContext) 17 | const [myThemes, setMyThemes] = React.useState( 18 | localStorage.getItem("themes") 19 | ? JSON.parse(localStorage.getItem("themes")) 20 | : [] 21 | ) 22 | 23 | const [hasPlayed, setHasPlayed] = useState(false) 24 | useEffect(() => { 25 | setHasPlayed(true) 26 | }, []) 27 | 28 | const [themeName, setThemeName] = useState("") 29 | const suggestedName = ntc.name(theme.colors.primary)[1] 30 | 31 | const cancel = () => { 32 | setPlaygroundView(PlaygroundViews.COMPLETE) 33 | setPlaygroundHeight(0) 34 | } 35 | 36 | const save = () => { 37 | if (themeName !== "") { 38 | const newMyThemes = myThemes.concat([{ name: themeName, ...theme }]) 39 | localStorage.setItem("themes", JSON.stringify(newMyThemes)) 40 | setMyThemes(newMyThemes) 41 | setPlaygroundView(PlaygroundViews.COMPLETE) 42 | setPlaygroundHeight(0) 43 | } 44 | } 45 | 46 | return ( 47 | 48 |
    Save Theme
    49 |
    50 |

    Save this theme by giving it a name.

    51 | 57 |
    58 | { 61 | setThemeName(e.target.value) 62 | }} 63 | maxLength="20" 64 | name="themeName" 65 | id="themeName" 66 | type="text" 67 | value={themeName} 68 | /> 69 | 70 |
    71 | 85 |
    86 |
    87 | 100 |
    101 |
    102 |
    103 | 104 |

    105 | How about {suggestedName}?{" "} 106 | 114 |

    115 |

    125 | Note: Themes are saved in your browser’s local storage and will not be 126 | available on a different browser or device. Make sure you download 127 | your themes for safe keeping. 128 |

    129 |
    130 |
    131 | ) 132 | } 133 | -------------------------------------------------------------------------------- /src/components/playground/Welcome.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { PlaygroundViews, NavContext } from "../../context/NavContext" 4 | import { useState, useEffect, useContext } from "react" 5 | import useTweenMax from "../../animate/useTweenMax" 6 | import { Power4 } from "gsap" 7 | 8 | const getRandomPos = base => { 9 | return Math.random() * base - base / 2 10 | } 11 | 12 | export default props => { 13 | const { setPlaygroundView, setPlaygroundHeight } = useContext(NavContext) 14 | 15 | const [hasPlayed, setHasPlayed] = useState(false) 16 | useEffect(() => { 17 | setHasPlayed(true) 18 | }, []) 19 | 20 | const [tweenOutRef] = useTweenMax( 21 | 0.5, 22 | hasPlayed 23 | ? {} 24 | : { 25 | scale: 0.01, 26 | opacity: 0, 27 | delay: 4, 28 | ease: Power4.easeIn, 29 | onComplete: () => { 30 | setPlaygroundView(PlaygroundViews.FONT_BODY) 31 | setPlaygroundHeight("800px") 32 | }, 33 | } 34 | ) 35 | 36 | return ( 37 |

    41 | {"Welcome to the Playground" 42 | .replace(/ /g, "\u00a0") 43 | .split("") 44 | .map((letter, i) => { 45 | const [tweenRef] = useTweenMax(1.5, { 46 | startAt: { 47 | x: getRandomPos(2000), 48 | y: getRandomPos(1000), 49 | z: getRandomPos(100), 50 | opacity: 0, 51 | rotation: getRandomPos(720), 52 | rotationX: getRandomPos(360), 53 | rotationY: getRandomPos(360), 54 | }, 55 | x: 0, 56 | y: 0, 57 | z: 0, 58 | rotation: 0, 59 | rotationX: 0, 60 | rotationY: 0, 61 | opacity: 1, 62 | delay: 0.5 + Math.random() * 0.5, 63 | immediateRender: true, 64 | ease: Power4.easeOut, 65 | }) 66 | 67 | return ( 68 | 74 | {letter} 75 | 76 | ) 77 | })} 78 |

    79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /src/components/playground/WelcomeBack.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { PlaygroundViews, NavContext } from "../../context/NavContext" 4 | import { useState, useEffect, useContext } from "react" 5 | import useTweenMax from "../../animate/useTweenMax" 6 | import { Power4 } from "gsap" 7 | 8 | const getRandomPos = base => { 9 | return Math.random() * base - base / 2 10 | } 11 | 12 | export default props => { 13 | const { setPlaygroundView, setPlaygroundHeight } = useContext(NavContext) 14 | 15 | const [hasPlayed, setHasPlayed] = useState(false) 16 | useEffect(() => { 17 | setHasPlayed(true) 18 | }, []) 19 | 20 | const [tweenOutRef] = useTweenMax( 21 | 0.25, 22 | hasPlayed 23 | ? {} 24 | : { 25 | scale: 0.01, 26 | opacity: 0, 27 | delay: 3, 28 | ease: Power4.easeIn, 29 | onComplete: () => { 30 | setPlaygroundView(PlaygroundViews.COMPLETE) 31 | setPlaygroundHeight(0) 32 | }, 33 | } 34 | ) 35 | 36 | return ( 37 |

    41 | {"Welcome Back!" 42 | .replace(/ /g, "\u00a0") 43 | .split("") 44 | .map((letter, i) => { 45 | const [tweenRef] = useTweenMax(0.75, { 46 | startAt: { 47 | x: getRandomPos(2000), 48 | y: getRandomPos(1000), 49 | z: getRandomPos(100), 50 | opacity: 0, 51 | rotation: getRandomPos(720), 52 | rotationX: getRandomPos(360), 53 | rotationY: getRandomPos(360), 54 | }, 55 | x: 0, 56 | y: 0, 57 | z: 0, 58 | rotation: 0, 59 | rotationX: 0, 60 | rotationY: 0, 61 | opacity: 1, 62 | delay: 1 + Math.random() * 0.25, 63 | immediateRender: true, 64 | ease: Power4.easeOut, 65 | }) 66 | 67 | return ( 68 | 74 | {letter} 75 | 76 | ) 77 | })} 78 |

    79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /src/components/playground/Wrapper.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useContext, useState, useEffect } from "react" 4 | import { NavContext } from "../../context/NavContext" 5 | import { ThemeContext } from "../../context/ThemeContext" 6 | import { lightness } from "@theme-ui/color" 7 | import useTweenMax from "../../animate/useTweenMax" 8 | import { Expo } from "gsap" 9 | 10 | export default props => { 11 | const [hasPlayed, setHasPlayed] = useState(false) 12 | useEffect(() => { 13 | setHasPlayed(true) 14 | }, []) 15 | 16 | const { playgroundHeight } = useContext(NavContext) 17 | 18 | const { theme, isDarkMode } = useContext(ThemeContext) 19 | const [tweenRef] = useTweenMax( 20 | 1, 21 | hasPlayed 22 | ? {} 23 | : { 24 | startAt: { paddingBottom: "20vh" }, 25 | paddingBottom: "0", 26 | delay: 2, 27 | ease: Expo.easeOut, 28 | immediateRender: true, 29 | } 30 | ) 31 | 32 | return ( 33 |
    42 |
    55 | {props.children} 56 |
    57 |
    58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /src/components/playground/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import React, { useContext } from "react" 4 | import { NavContext, PlaygroundViews } from "../../context/NavContext" 5 | import Wrapper from "./Wrapper" 6 | import Welcome from "./Welcome" 7 | import WelcomeBack from "./WelcomeBack" 8 | import ChooseFonts from "./ChooseFonts" 9 | import ChooseColors from "./ChooseColors" 10 | import ChooseColorScheme from "./ChooseColorScheme" 11 | import LoadTheme from "./LoadTheme" 12 | import SaveTheme from "./SaveTheme" 13 | 14 | export default props => { 15 | const { setPlaygroundHeight, playgroundView, setPlaygroundView } = useContext( 16 | NavContext 17 | ) 18 | 19 | const [myThemes] = React.useState( 20 | typeof window !== "undefined" && window.localStorage.getItem("themes") 21 | ? window.localStorage.getItem("themes") 22 | : null 23 | ) 24 | 25 | const onStepsComplete = () => { 26 | setPlaygroundHeight(0) 27 | setPlaygroundView(PlaygroundViews.COMPLETE) 28 | } 29 | 30 | return ( 31 | 32 | {myThemes ? : } 33 | {playgroundView === PlaygroundViews.FONT_BODY && ( 34 | { 37 | setPlaygroundView(PlaygroundViews.FONT_HEADING) 38 | setPlaygroundHeight(["1400px", "140vh"]) 39 | }} 40 | /> 41 | )} 42 | {playgroundView === PlaygroundViews.FONT_HEADING && ( 43 | { 46 | setPlaygroundView(PlaygroundViews.COLOR_SCHEME) 47 | }} 48 | /> 49 | )} 50 | {playgroundView === PlaygroundViews.COLOR_SCHEME && ( 51 | { 53 | setPlaygroundView(PlaygroundViews.COLOR_REVIEW) 54 | }} 55 | /> 56 | )} 57 | {playgroundView === PlaygroundViews.COLOR_REVIEW && ( 58 | 59 | )} 60 | {playgroundView === PlaygroundViews.LOAD && } 61 | {playgroundView === PlaygroundViews.SAVE && } 62 | 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /src/components/sections/Section.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import useTweenMax from "../../animate/useTweenMax" 4 | 5 | export default props => { 6 | const [tweenRef] = useTweenMax(0.5, { opacity: 1 }) 7 | return
    8 | } 9 | -------------------------------------------------------------------------------- /src/components/sections/SubSection.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import Heading from "../ui/Heading" 4 | 5 | export default props => ( 6 |
    7 | {props.title} 8 | {props.children} 9 |
    10 | ) 11 | -------------------------------------------------------------------------------- /src/components/sections/color/Color.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { lighten, darken, shade, tint } from "@theme-ui/color" 4 | import Flex from "../../ui/Flex" 5 | 6 | export default props => ( 7 |
    8 | {props.colorName && ( 9 |

    {props.colorName}

    10 | )} 11 |
    {props.colorVal}
    12 | 13 |
    16 |
    17 |

    Lighten / Darken

    18 | 19 | {[...Array(6).keys()] 20 | .splice(1) 21 | .reverse() 22 | .map(i => ( 23 |
    31 | ))} 32 | {[...Array(6).keys()].splice(1).map(i => ( 33 |
    41 | ))} 42 | 43 |

    44 | Tint / Shade 45 |

    46 | 47 | {[...Array(6).keys()] 48 | .reverse() 49 | .splice(1) 50 | .map(i => ( 51 |
    59 | ))} 60 | {[...Array(6).keys()].splice(1).map(i => ( 61 |
    69 | ))} 70 | 71 |
    72 | 73 |
    74 | ) 75 | -------------------------------------------------------------------------------- /src/components/sections/color/Palette.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import Color from "./Color" 6 | 7 | export default props => ( 8 |
    9 | {Object.entries(props.colors) 10 | .filter( 11 | color => 12 | typeof color[1] === "string" && color[0].indexOf("primary") === 0 13 | ) 14 | .map(color => ( 15 | 16 | ))} 17 | {Object.entries(props.colors) 18 | .filter( 19 | color => 20 | typeof color[1] === "string" && color[0].indexOf("secondary") === 0 21 | ) 22 | .map(color => ( 23 | 24 | ))} 25 | {Object.entries(props.colors) 26 | .filter( 27 | color => 28 | typeof color[1] === "string" && 29 | color[0].indexOf("text") === 0 && 30 | color[1] !== "#fff" && 31 | color[1] !== "#ffffff" && 32 | color[1] !== "#000" && 33 | color[1] !== "#000000" 34 | ) 35 | .map(color => ( 36 | 37 | ))} 38 | {Object.entries(props.colors) 39 | .filter( 40 | color => 41 | typeof color[1] === "string" && 42 | color[0].indexOf("background") === 0 && 43 | color[1] !== "#fff" && 44 | color[1] !== "#ffffff" && 45 | color[1] !== "#000" && 46 | color[1] !== "#000000" 47 | ) 48 | .map(color => ( 49 | 50 | ))} 51 | {Object.entries(props.colors) 52 | .filter( 53 | color => 54 | typeof color[1] === "string" && 55 | color[0].indexOf("primary") !== 0 && 56 | color[0].indexOf("secondary") !== 0 && 57 | color[0].indexOf("text") !== 0 && 58 | color[0].indexOf("background") !== 0 && 59 | color[0] !== "white" && 60 | color[1] !== "transparent" && 61 | color[1] !== "#fff" && 62 | color[1] !== "#ffffff" && 63 | color[1] !== "#000" && 64 | color[1] !== "#000000" 65 | ) 66 | .map(color => ( 67 | 68 | ))} 69 | {Object.entries(props.colors) 70 | .filter(color => typeof color[1] === "object") 71 | .map(color => { 72 | const colorName = color[0] 73 | return ( 74 | <> 75 |

    {colorName}

    76 |
    77 | {color[1] 78 | .filter(color => color) 79 | .map((color, i) => ( 80 |
    81 |
    {color}
    82 |
    83 |
    84 | ))} 85 |
    86 | 87 | ) 88 | })} 89 |
    90 | ) 91 | -------------------------------------------------------------------------------- /src/components/sections/color/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useContext } from "react" 4 | import { ThemeContext } from "../../../context/ThemeContext" 5 | import Section from "../Section" 6 | import SubSection from "../SubSection" 7 | import Palette from "./Palette" 8 | 9 | export default props => { 10 | const { theme } = useContext(ThemeContext) 11 | 12 | return ( 13 |
    14 | 15 | 16 | 17 |
    18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /src/components/sections/index.js: -------------------------------------------------------------------------------- 1 | export { default as Typography } from "./typography" 2 | export { default as Color } from "./color" 3 | export { default as UI } from "./ui" 4 | export { default as Theme } from "./theme" 5 | -------------------------------------------------------------------------------- /src/components/sections/theme.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useContext } from "react" 4 | import { ThemeContext } from "../../context/ThemeContext" 5 | import fileDownload from "js-file-download" 6 | import SyntaxHighlighter from "react-syntax-highlighter" 7 | import getCodeTheme from "../../themes/code" 8 | import Section from "./Section" 9 | import Heading from "../ui/Heading" 10 | import Button from "../ui/Button" 11 | import Link from "../ui/Link" 12 | 13 | export default props => { 14 | const { theme } = useContext(ThemeContext) 15 | 16 | const themeSource = "export default " + JSON.stringify(theme, null, 2) 17 | 18 | const downloadTheme = () => { 19 | fileDownload(themeSource, "theme.js") 20 | } 21 | 22 | return ( 23 |
    24 |
    25 | 26 | Your Theme Object{" "} 27 | 42 | 43 |

    44 | The object below represents all the settings that have been applied to 45 | the active theme in this app. You can make adjustments to fonts and 46 | colors, then use the generated theme in your projects. For more info, 47 | check out System UI or{" "} 48 | Theme UI. 49 |

    50 | 51 | {themeSource} 52 | 53 |
    54 |
    55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/components/sections/typography/Font.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import FontWeights from "./FontWeights" 6 | 7 | export default props => ( 8 | <> 9 | {Object.keys(props.fonts) 10 | .reverse() 11 | .filter(fontName => { 12 | return props.fonts[fontName] !== "inherit" 13 | }) 14 | .map(fontName => ( 15 |
    19 | 20 |

    28 | {props.fonts[fontName]} 29 |

    30 |
    31 | ))} 32 | 33 | ) 34 | -------------------------------------------------------------------------------- /src/components/sections/typography/FontWeights.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useState, useEffect } from "react" 5 | import useTweenMax from "../../../animate/useTweenMax" 6 | import { Back } from "gsap" 7 | 8 | export default props => { 9 | const [hasPlayed, setHasPlayed] = useState(false) 10 | useEffect(() => { 11 | setHasPlayed(true) 12 | }, []) 13 | 14 | return ( 15 | <> 16 |

    17 | {props.fontName} 18 |

    19 |

    20 | The quick brown fox jumps over the lazy dog. 21 |

    22 |
    30 | {[100, 200, 300, 400, 500, 600, 700, 800, 900].map((weight, i) => { 31 | const [tweenRef] = useTweenMax( 32 | 0.5, 33 | hasPlayed 34 | ? {} 35 | : { 36 | startAt: { y: "20px", opacity: 0, scale: 0.5 }, 37 | scale: 1, 38 | opacity: 1, 39 | y: 0, 40 | delay: 0.5 + i * 0.05, 41 | ease: Back.easeOut, 42 | immediateRender: true, 43 | } 44 | ) 45 | return ( 46 |
    60 |
    61 | Aa 62 |
    63 |
    74 | {weight} 75 |
    76 |
    77 | ) 78 | })} 79 |
    80 | 81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /src/components/sections/typography/TypeScale.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import { useContext } from "react" 6 | import { ThemeContext } from "../../../context/ThemeContext" 7 | import Heading from "../../ui/Heading" 8 | 9 | export default props => { 10 | const { theme } = useContext(ThemeContext) 11 | return ( 12 | <> 13 |
    14 | {props.fontSizes.map(size => ( 15 |
    24 |
    27 |
    35 |
    {size}
    36 |
    37 |
    49 |
    Aa
    50 |
    51 |
    52 |
    53 | ))} 54 |
    55 |
    56 | 57 | Heading 1 58 | H1 59 | 60 | 61 | Heading 2 62 | H2 63 | 64 | 65 | Heading 3 66 | H3 67 | 68 | 69 | Heading 4 70 | H4 71 | 72 | 73 | Heading 5 74 | H5 75 | 76 | 77 | Heading 6 78 | H6 79 | 80 |

    81 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do 82 | eiusmod tempor incididunt ut labore et dolore magna aliqua. Maecenas 83 | accumsan lacus vel facilisis. Massa tincidunt dui ut ornare. Amet nisl 84 | purus in mollis nunc sed id. Auctor augue mauris augue neque gravida 85 | in fermentum. Ut morbi tincidunt augue interdum velit euismod in 86 | pellentesque massa. Mattis molestie a iaculis at erat pellentesque 87 | adipiscing commodo.{" "} 88 |

    89 |
    90 | 91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /src/components/sections/typography/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useContext } from "react" 4 | import { ThemeContext } from "../../../context/ThemeContext" 5 | import Section from "../Section" 6 | import SubSection from "../SubSection" 7 | import Font from "./Font" 8 | import TypeScale from "./TypeScale" 9 | 10 | export default props => { 11 | const { theme } = useContext(ThemeContext) 12 | 13 | return ( 14 |
    15 | 16 | 17 | 18 | 19 | 20 | 21 |
    22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/sections/ui/Buttons.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import { useContext } from "react" 6 | import { ThemeContext } from "../../../context/ThemeContext" 7 | import Button from "../../ui/Button" 8 | 9 | export default props => { 10 | const { theme } = useContext(ThemeContext) 11 | 12 | const buttonColors = Object.keys(theme.colors).filter( 13 | color => 14 | color !== "white" && 15 | color !== "text" && 16 | color !== "black" && 17 | color !== "background" 18 | ) 19 | 20 | return ( 21 |
    22 |
    23 | {buttonColors.map(color => ( 24 | 37 | ))} 38 |
    39 |
    40 | {buttonColors.map(color => ( 41 | 54 | ))} 55 |
    56 |
    57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /src/components/sections/ui/Cards.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import { useContext } from "react" 6 | import { ThemeContext } from "../../../context/ThemeContext" 7 | import Card from "../../ui/Card" 8 | import Flex from "../../ui/Flex" 9 | 10 | export default props => { 11 | const { theme } = useContext(ThemeContext) 12 | 13 | const cardColors = Object.keys(theme.colors).filter( 14 | color => 15 | color !== "white" && 16 | color !== "text" && 17 | color !== "black" && 18 | color !== "background" 19 | ) 20 | 21 | return ( 22 | 23 | {cardColors.slice(0, 6).map(color => ( 24 |
    25 | 32 | This is a card. 33 | 34 |
    35 | ))} 36 |
    37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /src/components/sections/ui/Intro.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import Link from "../../ui/Link" 6 | import Figure from "../../ui/Figure" 7 | 8 | export default props => { 9 | return ( 10 | <> 11 |

    12 | Theme UI is a framework for 13 | building UI components with a{" "} 14 | style props API that 15 | follows the{" "} 16 | 17 | System UI Theme Specification 18 | {" "} 19 | for defining{" "} 20 | theme objects and{" "} 21 | 22 | design tokens 23 | 24 | . 25 |

    26 |

    35 | Looking for a good starting point? Check out the{" "} 36 | Theme UI Docs{" "} 37 | and try these compononent libraries that are compatible with 38 | Theme UI. 39 |

    40 |
    45 | 49 | Rebass 50 | 51 |
    52 |
    57 | 61 | Chakra UI 62 | 63 |
    64 | 65 | ) 66 | } 67 | -------------------------------------------------------------------------------- /src/components/sections/ui/MediaObject.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | import Flex from "../../ui/Flex" 6 | import Link from "../../ui/Link" 7 | 8 | export default props => ( 9 | 10 | placeholder for media object component 17 |

    18 | The{" "} 19 | 20 | media object 21 | {" "} 22 | is an image to the left, with descriptive content to the right 23 |

    24 |
    25 | ) 26 | -------------------------------------------------------------------------------- /src/components/sections/ui/NavBar.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | 6 | export default props => ( 7 |
    8 | 20 | 32 | 44 |
    45 | ) 46 | -------------------------------------------------------------------------------- /src/components/sections/ui/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import Section from "../Section" 4 | import Intro from "./Intro" 5 | import Buttons from "./Buttons" 6 | import Cards from "./Cards" 7 | import MediaObject from "./MediaObject" 8 | import NavBar from "./NavBar" 9 | import Flex from "../../ui/Flex" 10 | 11 | export default props => ( 12 |
    13 | 14 |
    15 | 16 |
    17 |
    24 | 25 | 26 |
    27 | 28 |
    29 |
    30 |
    31 | 32 |
    33 |
    34 |
    35 | ) 36 | -------------------------------------------------------------------------------- /src/components/ui/Button.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { useContext } from "react" 4 | import { ThemeContext } from "../../context/ThemeContext" 5 | 6 | export default props => { 7 | const { isDarkMode } = useContext(ThemeContext) 8 | 9 | return ( 10 | 37 |
    38 | )} 39 |
    40 | ) 41 | -------------------------------------------------------------------------------- /src/components/ui/Figure.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | 6 | export default props => ( 7 |
    8 | 9 | {props.alt} 19 | 20 |
    {props.children}
    21 |
    22 | ) 23 | -------------------------------------------------------------------------------- /src/components/ui/Flex.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | 4 | export default props => ( 5 |
    9 | ) 10 | -------------------------------------------------------------------------------- /src/components/ui/GithubLink.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React from "react" 5 | 6 | export default props => ( 7 | 19 | 32 | 33 | ) 34 | -------------------------------------------------------------------------------- /src/components/ui/HeaderButton.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | import { lightness } from "@theme-ui/color" 4 | // eslint-disable-next-line 5 | import React, { useContext, useEffect, useState } from "react" 6 | import useTweenMax from "../../animate/useTweenMax" 7 | import { Expo } from "gsap" 8 | import { ThemeContext } from "../../context/ThemeContext" 9 | import Button from "./Button" 10 | 11 | export default props => { 12 | const [hasPlayed, setHasPlayed] = useState(false) 13 | useEffect(() => { 14 | setHasPlayed(true) 15 | }, []) 16 | 17 | const [tweenRef] = useTweenMax( 18 | 0.5, 19 | hasPlayed 20 | ? {} 21 | : { 22 | startAt: { x: "50vw", opacity: 0 }, 23 | opacity: 1, 24 | x: 0, 25 | delay: 0.5 + props.index / 10, 26 | ease: Expo.easeOut, 27 | immediateRender: true, 28 | onComplete: props.onCompleteAnimate || null, 29 | } 30 | ) 31 | 32 | const { theme } = useContext(ThemeContext) 33 | 34 | return ( 35 |
    36 | 52 |
    53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /src/components/ui/Heading.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx, Styled } from "theme-ui" 3 | 4 | export default props => 5 | -------------------------------------------------------------------------------- /src/components/ui/Link.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | 4 | export default props => ( 5 | // eslint-disable-next-line 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /src/components/ui/Logo.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useContext } from "react" 5 | import { ThemeContext } from "../../context/ThemeContext" 6 | import Heading from "./Heading" 7 | import { lightness } from "@theme-ui/color" 8 | 9 | export default props => { 10 | const { theme, isDarkMode } = useContext(ThemeContext) 11 | 12 | return ( 13 | <> 14 |
    24 |
    33 |
    34 |
    35 |
    36 | 48 | Design System Playground{" "} 49 | 62 | ...powered by{" "} 63 |
    71 | Theme UI 72 | 73 | 74 | 75 | 76 | ) 77 | } 78 | -------------------------------------------------------------------------------- /src/context/NavContext.js: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext } from "react" 2 | 3 | const NavContext = createContext({ currSection: "typography" }) 4 | const NavSections = ["FONT", "COLOR", "THEME", "UI"] 5 | const PlaygroundViews = { 6 | WELCOME: "WELCOME", 7 | FONT_BODY: "FONT_BODY", 8 | FONT_HEADING: "FONT_HEADING", 9 | COLOR_SCHEME: "COLOR_SCHEME", 10 | COLOR_REVIEW: "COLOR_REVIEW", 11 | COMPLETE: "COMPLETE", 12 | SAVE: "SAVE", 13 | LOAD: "LOAD", 14 | } 15 | 16 | const NavProvider = ({ children }) => { 17 | const [currSection, setCurrSection] = useState(NavSections[0]) 18 | const [playgroundHeight, setPlaygroundHeight] = useState(["60vh", "70vh"]) 19 | const [playgroundView, setPlaygroundView] = useState(PlaygroundViews.WELCOME) 20 | 21 | return ( 22 | 32 | {children} 33 | 34 | ) 35 | } 36 | 37 | export { NavContext, NavProvider, NavSections, PlaygroundViews } 38 | -------------------------------------------------------------------------------- /src/context/ThemeContext.js: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext } from "react" 2 | import { ThemeProvider as ThemeUIProvider } from "theme-ui" 3 | import themes from "../themes" 4 | import ColorScheme from "color-scheme" 5 | import ntc from "ntcjs" 6 | 7 | const Themes = themes 8 | const ThemeContext = createContext({ theme: themes.royal }) 9 | 10 | const ThemeProvider = ({ children }) => { 11 | const [theme, setTheme] = useState(themes.royal) 12 | 13 | const isDarkMode = () => { 14 | return !isDark(theme.colors.primary) 15 | } 16 | 17 | return ( 18 | 19 | {children} 20 | 21 | ) 22 | } 23 | 24 | const isDark = color => { 25 | return getLuminance(color) <= 0.7 26 | } 27 | 28 | const mainColors = ["primary", "secondary", "background", "text"] 29 | 30 | const getMainColors = () => { 31 | return mainColors 32 | } 33 | 34 | const getOtherColors = theme => { 35 | const otherColors = Object.keys(theme.colors).filter(color => { 36 | return ( 37 | typeof theme.colors[color] === "string" && 38 | mainColors.indexOf(color) === -1 && 39 | color !== "transparent" && 40 | color !== "white" && 41 | theme.colors[color].toLowerCase() !== "#fff" && 42 | theme.colors[color] !== "#000" && 43 | theme.colors[color].toLowerCase() !== "#ffffff" && 44 | theme.colors[color] !== "#000000" 45 | ) 46 | }) 47 | return otherColors 48 | } 49 | 50 | const BrowserFonts = { 51 | sans: [ 52 | { family: "Arial, sans-serif", name: "Arial" }, 53 | { 54 | family: "'avenir next', avenir, helvetica, arial, sans-serif", 55 | name: "Avenir", 56 | }, 57 | { 58 | family: '"Helvetica Neue", Helvetica, Arial, sans-serif', 59 | name: "Helvetica", 60 | }, 61 | { 62 | family: 63 | 'Frutiger, "Frutiger Linotype", Univers, Calibri, "Gill Sans", "Gill Sans MT", "Myriad Pro", Myriad, "DejaVu Sans Condensed", "Liberation Sans", "Nimbus Sans L", Tahoma, Geneva, "Helvetica Neue", Helvetica, Arial, sans-serif', 64 | name: "Helvetica Based", 65 | }, 66 | { 67 | family: 68 | "-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji", 69 | name: "System", 70 | }, 71 | { 72 | family: 'Tahoma, Geneva, "Helvetica Neue", Helvetica, Arial, sans-serif', 73 | name: "Tahoma", 74 | }, 75 | { 76 | family: '"Trebuchet MS", Verdana, "Verdana Ref", sans-serif', 77 | name: "Trebuchet", 78 | }, 79 | { 80 | family: 81 | '"Segoe UI", Candara, "Bitstream Vera Sans", "DejaVu Sans", "Bitstream Vera Sans", "Trebuchet MS", Verdana, "Verdana Ref", sans-serif', 82 | name: "Trebuchet Based", 83 | }, 84 | { family: 'Verdana, "Verdana Ref", sans-serif', name: "Verdana" }, 85 | { 86 | family: 87 | 'Corbel, "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", "DejaVu Sans", "Bitstream Vera Sans", "Liberation Sans", Verdana, "Verdana Ref", sans-serif', 88 | name: "Verdana Based", 89 | }, 90 | ], 91 | serif: [ 92 | { 93 | family: 94 | 'Baskerville, "Bookman Old Style", "Bitstream Charter", "Nimbus Roman No9 L", Garamond, "Apple Garamond", "ITC Garamond Narrow", "New Century Schoolbook", "Century Schoolbook", "Century Schoolbook L", Georgia, serif', 95 | name: "Baskerville", 96 | }, 97 | { 98 | family: 99 | '"Courier New", Courier, "Lucida Sans Typewriter", "Lucida Typewriter", monospace', 100 | name: "Courier", 101 | }, 102 | { 103 | family: 104 | 'Garamond, "Apple Garamond", "ITC Garamond Narrow", "New Century Schoolbook", "Century Schoolbook", "Century Schoolbook L", Georgia, serif', 105 | name: "Garamond", 106 | }, 107 | { family: "Georgia, serif", name: "Georgia" }, 108 | { 109 | family: 110 | 'Constantia, "Lucida Bright", Lucidabright, "Lucida Serif", Lucida, "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif', 111 | name: "Georgia Based", 112 | }, 113 | { 114 | family: 115 | '"Palatino Linotype", Palatino, Palladio, "URW Palladio L", "Book Antiqua", Baskerville, "Bookman Old Style", "Bitstream Charter", "Nimbus Roman No9 L", Garamond, "Apple Garamond", "ITC Garamond Narrow", "New Century Schoolbook", "Century Schoolbook", "Century Schoolbook L", Georgia, serif', 116 | name: "Palatino", 117 | }, 118 | { family: 'Times, "Times New Roman", serif', name: "Times" }, 119 | { 120 | family: 121 | 'Cambria, "Hoefler Text", Utopia, "Liberation Serif", "Nimbus Roman No9 L Regular", Times, "Times New Roman", serif', 122 | name: "Times Based", 123 | }, 124 | ], 125 | } 126 | 127 | const WebFonts = { 128 | sans: [ 129 | "ABeeZee", 130 | "Abel", 131 | "Alegreya Sans", 132 | "Amaranth", 133 | "Archivo Narrow", 134 | "Arimo", 135 | "Armata", 136 | "Asap", 137 | "Assistant", 138 | "Barlow Condensed", 139 | "Cabin", 140 | "Cairo", 141 | "Catamaran", 142 | "Cuprum", 143 | "Didact Gothic", 144 | "Dosis", 145 | "Economica", 146 | "Exo", 147 | "Fira Sans", 148 | "Fjalla One", 149 | "Francois One", 150 | "Gudea", 151 | "Heebo", 152 | "Hind Siliguri", 153 | "Istok Web", 154 | "Josefin Sans", 155 | "Karla", 156 | "Lato", 157 | "Libre Franklin", 158 | "Maven Pro", 159 | "Merriweather Sans", 160 | "Monda", 161 | "Montserrat", 162 | "Muli", 163 | "Nanum Gothic", 164 | "News Cycle", 165 | "Noto Sans", 166 | "Nunito", 167 | "Nunito Sans", 168 | "Open Sans", 169 | "Open Sans Condensed", 170 | "Orbitron", 171 | "Oswald", 172 | "Oxygen", 173 | "PT Sans", 174 | "Pathway Gothic One", 175 | "Philosopher", 176 | "Play", 177 | "Pontano Sans", 178 | "Poppins", 179 | "Quattrocento Sans", 180 | "Questrial", 181 | "Quicksand", 182 | "Rajdhani", 183 | "Raleway", 184 | "Roboto", 185 | "Roboto Condensed", 186 | "Ropa Sans", 187 | "Rubik", 188 | "Russo One", 189 | "Signika", 190 | "Source Sans Pro", 191 | "Teko", 192 | "Titillium Web", 193 | "Ubuntu", 194 | "Varela Round", 195 | "Work Sans", 196 | "Yanone Kaffeesatz", 197 | "Yantramanav", 198 | ], 199 | serif: [ 200 | "Adamina", 201 | "Alegreya", 202 | "Amiri", 203 | "Arapey", 204 | "Arvo", 205 | "Bitter", 206 | "Bree Serif", 207 | "Cardo", 208 | "Cinzel", 209 | "Cormorant Garamond", 210 | "Crete Round", 211 | "Crimson Text", 212 | "Domine", 213 | "EB Garamond", 214 | "Josefin Slab", 215 | "Libre Baskerville", 216 | "Lora", 217 | "Merriweather", 218 | "Neuton", 219 | "Noticia Text", 220 | "Noto Serif", 221 | "Old Standard TT", 222 | "PT Serif", 223 | "Playfair Display", 224 | "Playfair Display SC", 225 | "Quattrocento", 226 | "Roboto Slab", 227 | "Rokkitt", 228 | "Sanchez", 229 | "Slabo 27px", 230 | "Source Serif Pro", 231 | "Tinos", 232 | "Vidaloka", 233 | "Volkhov", 234 | "Vollkorn", 235 | ], 236 | } 237 | 238 | const getRandomFont = () => { 239 | const allFonts = [] 240 | .concat(BrowserFonts.sans) 241 | .concat(BrowserFonts.serif) 242 | .concat( 243 | WebFonts.sans.map(font => { 244 | return { name: font, family: "'" + font + "', sans-serif" } 245 | }) 246 | ) 247 | .concat( 248 | WebFonts.serif.map(font => { 249 | return { name: font, family: "'" + font + "', serif" } 250 | }) 251 | ) 252 | return allFonts[Math.floor(Math.random() * allFonts.length)] 253 | } 254 | 255 | const hexToH = H => { 256 | let r = 0, 257 | g = 0, 258 | b = 0 259 | if (H.length === 4) { 260 | r = "0x" + H[1] + H[1] 261 | g = "0x" + H[2] + H[2] 262 | b = "0x" + H[3] + H[3] 263 | } else if (H.length === 7) { 264 | r = "0x" + H[1] + H[2] 265 | g = "0x" + H[3] + H[4] 266 | b = "0x" + H[5] + H[6] 267 | } 268 | r /= 255 269 | g /= 255 270 | b /= 255 271 | let cmin = Math.min(r, g, b), 272 | cmax = Math.max(r, g, b), 273 | delta = cmax - cmin, 274 | h = 0 275 | 276 | if (delta === 0) h = 0 277 | else if (cmax === r) h = ((g - b) / delta) % 6 278 | else if (cmax === g) h = (b - r) / delta + 2 279 | else h = (r - g) / delta + 4 280 | h = Math.round(h * 60) 281 | 282 | if (h < 0) h += 360 283 | 284 | return h 285 | } 286 | 287 | const getLuminance = H => { 288 | let r = 0, 289 | g = 0, 290 | b = 0 291 | if (H.length === 4) { 292 | r = "0x" + H[1] + H[1] 293 | g = "0x" + H[2] + H[2] 294 | b = "0x" + H[3] + H[3] 295 | } else if (H.length === 7) { 296 | r = "0x" + H[1] + H[2] 297 | g = "0x" + H[3] + H[4] 298 | b = "0x" + H[5] + H[6] 299 | } 300 | r /= 255 301 | g /= 255 302 | b /= 255 303 | 304 | return r * 0.299 + g * 0.587 + b * 0.114 305 | } 306 | 307 | const getColorScheme = baseColor => { 308 | if (!baseColor) { 309 | baseColor = "#000000".replace(/0/g, function() { 310 | return (~~(Math.random() * 16)).toString(16) 311 | }) 312 | } 313 | 314 | const scm = new ColorScheme() 315 | 316 | const isDarkMode = getLuminance(baseColor) > 0.7 317 | 318 | const colors = scm 319 | .from_hue(hexToH(baseColor)) 320 | .scheme("triade") 321 | .colors() 322 | 323 | let colorPalette = { 324 | primary: baseColor, 325 | secondary: "#" + colors[0], 326 | text: isDarkMode ? "#FFFFFF" : "#000000", 327 | background: isDarkMode ? "#000000" : "#FFFFFF", 328 | } 329 | 330 | colors.splice(1).forEach(color => { 331 | const colorName = ntc 332 | .name(color)[1] 333 | .toLowerCase() 334 | .split(" ") 335 | .pop() 336 | colorPalette[colorName] = "#" + color 337 | }) 338 | 339 | if (typeof colorPalette.white !== "undefined") { 340 | colorPalette.offwhite = colorPalette.white 341 | colorPalette.white = "#ffffff" 342 | } 343 | 344 | return colorPalette 345 | } 346 | 347 | export { 348 | ThemeContext, 349 | ThemeProvider, 350 | Themes, 351 | BrowserFonts, 352 | WebFonts, 353 | getRandomFont, 354 | getMainColors, 355 | getOtherColors, 356 | getColorScheme, 357 | isDark, 358 | } 359 | -------------------------------------------------------------------------------- /src/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnpolacek/design-system-playground/b46fc02f8c80a7f051c89e53e1014cef1934537c/src/images/icon.png -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from "theme-ui" 3 | // eslint-disable-next-line 4 | import React, { useContext } from "react" 5 | import Header from "../components/Header" 6 | import Nav from "../components/Nav" 7 | import Main from "../components/Main" 8 | import Playground from "../components/playground" 9 | import Wrapper from "../components/Wrapper" 10 | import Footer from "../components/Footer" 11 | import { NavProvider } from "../context/NavContext" 12 | import { ThemeContext, ThemeProvider } from "../context/ThemeContext" 13 | import SEO from "../components/head/SEO" 14 | import GlobalCSS from "../components/head/GlobalCSS" 15 | import ReactGA from "react-ga" 16 | 17 | const IndexPage = () => { 18 | const { theme } = useContext(ThemeContext) 19 | 20 | ReactGA.initialize("UA-2821890-15") 21 | if (typeof window !== "undefined") { 22 | ReactGA.pageview(window.location.pathname + window.location.search) 23 | } 24 | 25 | return ( 26 | 27 | 28 | 29 | 30 | 31 |
    32 | 33 |