├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── lerna.json ├── netlify.toml ├── package.json ├── packages ├── components-extra │ ├── .gitignore │ ├── jest.config.js │ ├── jest.setup.js │ ├── package.json │ ├── scripts │ │ └── afterBuild.js │ ├── src │ │ ├── components │ │ │ ├── BackToTop │ │ │ │ ├── BackToTop.tsx │ │ │ │ ├── components │ │ │ │ │ ├── ArrowUpward.tsx │ │ │ │ │ └── Button.tsx │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ └── BackToTop.test.tsx │ │ │ ├── Card │ │ │ │ ├── Card.tsx │ │ │ │ ├── components │ │ │ │ │ ├── CardButton.tsx │ │ │ │ │ └── CardContentWrapper.tsx │ │ │ │ ├── hooks │ │ │ │ │ └── Context.tsx │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ └── Card.test.tsx │ │ │ ├── CookiesBanner │ │ │ │ ├── CookiesBanner.tsx │ │ │ │ ├── components │ │ │ │ │ ├── Container.tsx │ │ │ │ │ ├── CookieIcon.tsx │ │ │ │ │ └── CookiesButton.tsx │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ └── CookiesBanner.test.tsx │ │ │ ├── CookiesConsent │ │ │ │ ├── CookiesConsent.tsx │ │ │ │ ├── components │ │ │ │ │ ├── ArrowIcon.tsx │ │ │ │ │ ├── Category.tsx │ │ │ │ │ ├── CategoryTitle.tsx │ │ │ │ │ ├── ConsentButton.tsx │ │ │ │ │ ├── Container.tsx │ │ │ │ │ ├── Controls.tsx │ │ │ │ │ └── MainTitle.tsx │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ └── CookiesConsent.test.tsx │ │ │ ├── CreditCardNumber │ │ │ │ ├── CreditCardNumber.tsx │ │ │ │ ├── components │ │ │ │ │ ├── CardIcon.tsx │ │ │ │ │ └── StartAdornment.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── tests │ │ │ │ │ └── CreditCardNumber.test.tsx │ │ │ │ └── utils │ │ │ │ │ ├── addWhitespaces.ts │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── endsWithWhitespace.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── isAddingNewGroup.ts │ │ │ │ │ ├── removeWhitespaces.ts │ │ │ │ │ ├── setCaretToPosition.ts │ │ │ │ │ └── tests │ │ │ │ │ ├── addWhitespaces.test.ts │ │ │ │ │ ├── endsWithWhitespace.test.ts │ │ │ │ │ ├── isAddingNewGroup.test.ts │ │ │ │ │ ├── removeWhitespaces.test.ts │ │ │ │ │ └── setCaretToPosition.test.tsx │ │ │ ├── Footer │ │ │ │ ├── Footer.tsx │ │ │ │ ├── components │ │ │ │ │ ├── BottomBanner.tsx │ │ │ │ │ ├── Column.tsx │ │ │ │ │ ├── FooterContainer.tsx │ │ │ │ │ ├── Item.tsx │ │ │ │ │ ├── ItemsContainer.tsx │ │ │ │ │ ├── Title.tsx │ │ │ │ │ └── TopContainer.tsx │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ └── Footer.test.tsx │ │ │ ├── Navbar │ │ │ │ ├── Navbar.tsx │ │ │ │ ├── components │ │ │ │ │ ├── Brand.tsx │ │ │ │ │ ├── DownArrowIcon.tsx │ │ │ │ │ ├── Language.tsx │ │ │ │ │ ├── LanguageItem.tsx │ │ │ │ │ ├── Menu.tsx │ │ │ │ │ ├── MenuIcon.tsx │ │ │ │ │ ├── MenuItem.tsx │ │ │ │ │ └── TranslateIcon.tsx │ │ │ │ ├── hooks │ │ │ │ │ └── index.tsx │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ └── Navbar.test.tsx │ │ │ ├── Newsletter │ │ │ │ ├── Newsletter.tsx │ │ │ │ ├── components │ │ │ │ │ ├── Button.tsx │ │ │ │ │ ├── Container.tsx │ │ │ │ │ ├── Form.tsx │ │ │ │ │ ├── Input.tsx │ │ │ │ │ └── Typographies.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── tests │ │ │ │ │ └── Newsletter.test.tsx │ │ │ ├── Paragraph │ │ │ │ ├── Paragraph.tsx │ │ │ │ ├── index.ts │ │ │ │ └── tests │ │ │ │ │ └── Paragraph.test.tsx │ │ │ ├── StyledProvider │ │ │ │ ├── StyledProvider.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── hooks │ │ │ ├── index.ts │ │ │ ├── useIsDesktop.ts │ │ │ └── useIsSmallScreen.ts │ │ ├── index.ts │ │ ├── theme │ │ │ ├── createMixins.ts │ │ │ ├── createPalette.ts │ │ │ ├── createTheme.ts │ │ │ ├── createTypography.ts │ │ │ ├── index.ts │ │ │ ├── styled.d.ts │ │ │ └── zIndex.ts │ │ ├── types │ │ │ └── index.ts │ │ ├── utils-test │ │ │ ├── index.ts │ │ │ └── renderWithTheme.tsx │ │ └── utils │ │ │ ├── index.ts │ │ │ ├── isBoolean.ts │ │ │ ├── isEmpty.ts │ │ │ ├── isServerSide.ts │ │ │ ├── isString.ts │ │ │ ├── merge.ts │ │ │ ├── resetCSS.ts │ │ │ ├── serverDocument.ts │ │ │ └── tests │ │ │ ├── isBoolean.test.ts │ │ │ ├── isEmpty.test.ts │ │ │ ├── isString.test.ts │ │ │ ├── merge.test.ts │ │ │ └── resetCSS.test.tsx │ └── tsconfig.json └── docs │ ├── .eslintignore │ ├── .gitignore │ ├── README.md │ ├── gatsby-browser.js │ ├── gatsby-config.js │ ├── gatsby-ssr.js │ ├── package.json │ ├── src │ ├── components │ │ ├── Blockquote.tsx │ │ ├── ColorBoxes.tsx │ │ ├── EditLink.tsx │ │ ├── Header.tsx │ │ ├── InlineCode.tsx │ │ ├── Introduction.tsx │ │ ├── Layout.tsx │ │ ├── Link.tsx │ │ ├── List.tsx │ │ ├── ListItem.tsx │ │ ├── MDXProvider.tsx │ │ ├── Navigation.tsx │ │ ├── Paragraph.tsx │ │ ├── Playground.tsx │ │ ├── Props.tsx │ │ ├── PropsProvider.tsx │ │ ├── Seo.tsx │ │ ├── ThemeProvider.tsx │ │ ├── ThemeView.tsx │ │ ├── Titles.tsx │ │ └── docs │ │ │ ├── BackToTop.tsx │ │ │ ├── Card.tsx │ │ │ ├── CookiesBanner.tsx │ │ │ ├── CookiesConsent.tsx │ │ │ ├── CreditCardNumber.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Navbar.tsx │ │ │ ├── Newsletter.tsx │ │ │ ├── Paragraph.tsx │ │ │ └── index.ts │ ├── hooks │ │ ├── index.ts │ │ └── useGithubEditLink.ts │ ├── icons │ │ ├── Code.tsx │ │ ├── Copy.tsx │ │ ├── Dark.tsx │ │ ├── ExpandLess.tsx │ │ ├── ExpandMore.tsx │ │ ├── Github.tsx │ │ ├── Light.tsx │ │ ├── Menu.tsx │ │ ├── Pencil.tsx │ │ └── index.ts │ ├── images │ │ ├── favicon.ico │ │ ├── gatsby-astronaut.png │ │ ├── gatsby-icon.png │ │ ├── logo-introduction.png │ │ ├── logo.png │ │ └── logo.webp │ ├── pages │ │ ├── 404.mdx │ │ ├── components │ │ │ ├── back-to-top.mdx │ │ │ ├── card.mdx │ │ │ ├── cookies-banner.mdx │ │ │ ├── cookies-consent.mdx │ │ │ ├── credit-card-number.mdx │ │ │ ├── footer.mdx │ │ │ ├── navbar.mdx │ │ │ ├── newsletter.mdx │ │ │ └── paragraph.mdx │ │ ├── examples.mdx │ │ ├── guides │ │ │ ├── bundling.mdx │ │ │ ├── ssr.mdx │ │ │ ├── trouble-shooting.mdx │ │ │ └── typescript.mdx │ │ ├── index.mdx │ │ ├── theming │ │ │ ├── customization.mdx │ │ │ └── palette.mdx │ │ └── utils │ │ │ ├── reset-css.mdx │ │ │ └── styled-provider.mdx │ ├── styles │ │ └── prismTheme.css │ ├── theme │ │ ├── ModeProvider.tsx │ │ ├── index.ts │ │ ├── styled.d.ts │ │ ├── theme.d.ts │ │ └── theme.ts │ └── utils │ │ ├── index.ts │ │ ├── isCSR.ts │ │ ├── isInternalLink.ts │ │ └── slugify.ts │ └── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | **/*.test.js 2 | jest.*.js 3 | build 4 | node_modules 5 | *d.ts 6 | build-tsc 7 | gatsby-* 8 | scripts 9 | public -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "eslint:recommended", 4 | "plugin:react/recommended", 5 | "plugin:@typescript-eslint/eslint-recommended", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | "plugins": [ 9 | "react-hooks", 10 | "@typescript-eslint" 11 | ], 12 | "parser": "@typescript-eslint/parser", 13 | "env": { 14 | "browser": true, 15 | "node": true, 16 | "es6": true 17 | }, 18 | "rules": { 19 | "react/prop-types": [ 20 | 2, { "ignore": ["theme", "children", "className", "ref", "forwardedRef"] } 21 | ], 22 | "react/display-name": 0, 23 | "react/no-unescaped-entities": 0, 24 | "react-hooks/rules-of-hooks": "error", 25 | "react-hooks/exhaustive-deps": "warn", 26 | "@typescript-eslint/ban-ts-ignore": 0, 27 | "@typescript-eslint/member-delimiter-style": 0, 28 | "@typescript-eslint/no-explicit-any": 0, 29 | "@typescript-eslint/ban-ts-comment": 0, 30 | "@typescript-eslint/no-var-requires": 0, // TODO: Remove this when react-json-view is ssr-friendly. 31 | "@typescript-eslint/explicit-module-boundary-types": 0 32 | }, 33 | "overrides": [ 34 | { 35 | "files": ["*.js"], 36 | "rules": { 37 | "@typescript-eslint/explicit-function-return-type": "off" 38 | } 39 | } 40 | ], 41 | "settings": { 42 | "react": { 43 | "version": "detect" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | build 4 | .eslintcache -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | .cache 4 | public 5 | build-tsc -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "trailingComma": "all", 4 | "tabWidth": 2, 5 | "semi": false, 6 | "singleQuote": true 7 | } 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'lts/*' 4 | cache: 5 | yarn: true 6 | directories: 7 | - node_modules 8 | install: yarn 9 | script: 10 | - yarn bootstrap 11 | - yarn test 12 | - yarn lint 13 | - yarn check-types -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for contributing to **components-extra**! 4 | 5 | ## Conventions 6 | 7 | ### Branch name 8 | 9 | No specific rule here. However, always nice to follow the `feature/[name]` practice. 10 | 11 | ### PR name 12 | 13 | No specific rule here either. But if you can, you can prefix the name with the scope of your change. 14 | Example: `[CookiesBanner]: fix broken button` 15 | 16 | ### Commit 17 | 18 | Please follow the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) standards. 19 | 20 | ## Development 21 | 22 | ### Run the dev server 23 | 24 | If your are making a fix to a component or creating a new one, you can visualize your changes live by using 25 | the local doc server: 26 | 27 | ```shell 28 | yarn start 29 | ``` 30 | 31 | The stories are located under the package `docs/`. 32 | 33 | > **Note**: if you are using a linux distribution, you might need to increase the system file watch limit. If you encounter an error like _Error: UNHANDLED REJECTION ENOSPC: System limit for number of file watchers reached_, just run the following command and try again: `echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p`. 34 | 35 | ### Test pipeline 36 | 37 | Once you're done with your work, you can validate all the test pipelines with: 38 | 39 | ``` 40 | yarn validate 41 | ``` 42 | 43 | If all checks are OK, then you're all good to submit a PR! :) 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alexandre Le Lain 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 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["packages/*"], 3 | "npmClient": "yarn", 4 | "version": "independant", 5 | "useWorkspaces": true 6 | } 7 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | command = "yarn bootstrap && yarn build:docs" 3 | publish = "packages/docs/public" 4 | environment = { NODE_VERSION = "14.17.0" } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "bootstrap": "lerna bootstrap && lerna run build:bootstrap --stream", 5 | "start": "lerna run start --stream --parallel --npm-client=yarn", 6 | "serve": "lerna run serve --stream", 7 | "build": "lerna run build --scope components-extra --stream", 8 | "build:docs": "lerna run build --scope docs --stream", 9 | "test": "lerna run test --stream", 10 | "check-types": "lerna run check-types --stream", 11 | "lint": "eslint --cache packages/**/*.{ts,tsx,js}", 12 | "prettier": "prettier --write packages/**/*.{ts,tsx,js}", 13 | "validate": "yarn lint && yarn test && yarn build && yarn check-types" 14 | }, 15 | "devDependencies": { 16 | "@material-ui/core": "^4.12.3", 17 | "@material-ui/types": "^5.1.0", 18 | "@types/react-dom": "^16.9.8", 19 | "@types/styled-components": "^5.1.0", 20 | "@typescript-eslint/eslint-plugin": "^3.7.0", 21 | "@typescript-eslint/parser": "^3.7.0", 22 | "cross-env": "^7.0.2", 23 | "eslint": "^7.5.0", 24 | "eslint-plugin-react": "^7.20.0", 25 | "eslint-plugin-react-hooks": "^4.0.4", 26 | "husky": "^4.2.5", 27 | "lerna": "^4.0.0", 28 | "prettier": "^2.0.5", 29 | "pretty-quick": "^2.0.1", 30 | "react": "^17.0.2", 31 | "react-dom": "^17.0.2", 32 | "styled-components": "^5.3.0", 33 | "typescript": "^3.9.7" 34 | }, 35 | "husky": { 36 | "hooks": { 37 | "pre-commit": "pretty-quick --staged --pattern \"packages/**/*.*(ts|tsx|js)\"" 38 | } 39 | }, 40 | "workspaces": [ 41 | "packages/*" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /packages/components-extra/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | build 4 | build-tsc 5 | .eslintcache -------------------------------------------------------------------------------- /packages/components-extra/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | setupFilesAfterEnv: ['./jest.setup.js'], 4 | modulePathIgnorePatterns: ['/build/', '/build-tsc/'], 5 | moduleNameMapper: { 6 | '^utils-test(.*)$': '/src/utils-test$1', 7 | }, 8 | } 9 | -------------------------------------------------------------------------------- /packages/components-extra/jest.setup.js: -------------------------------------------------------------------------------- 1 | require('@testing-library/jest-dom/extend-expect') 2 | require('./src/theme') 3 | -------------------------------------------------------------------------------- /packages/components-extra/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "components-extra", 3 | "version": "3.2.1", 4 | "description": "React UI molecule components based on styled-components & material-ui.", 5 | "homepage": "https://components-extra.netlify.app/", 6 | "author": "Alexandre Le Lain ", 7 | "license": "MIT", 8 | "module": "./build/esm/index.js", 9 | "browser": "./build/esm/index.js", 10 | "main": "./build/index.js", 11 | "sideEffects": false, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/alexandre-lelain/components-extra.git" 15 | }, 16 | "keywords": [ 17 | "react", 18 | "react-components", 19 | "material design", 20 | "material-ui", 21 | "blocks", 22 | "molecule components" 23 | ], 24 | "scripts": { 25 | "start": "yarn build:esm --watch", 26 | "build": "rimraf build && yarn build:esm && yarn build:cjs && yarn build:es && yarn build:amd && yarn build:after", 27 | "build:amd": "cross-env NODE_ENV=production tsc -m amd --outFile build/amd/components-extra.js --d false && yarn build:minify", 28 | "build:minify": "yarn terser build/amd/components-extra.js -c -o build/amd/components-extra.min.js", 29 | "build:cjs": "cross-env NODE_ENV=production tsc -m commonjs --outDir build", 30 | "build:esm": "cross-env NODE_ENV=production tsc -m es6 --outDir build/esm", 31 | "build:es": "cross-env NODE_ENV=production tsc -m esnext --outDir build/es", 32 | "build:after": "node ./scripts/afterBuild.js", 33 | "build:bootstrap": "yarn build:cjs && yarn build:esm && yarn build:after", 34 | "test": "jest", 35 | "postversion": "git push && git push --tags", 36 | "version": "git add -vA", 37 | "release": "yarn build && yarn test && yarn publish build --tag latest" 38 | }, 39 | "dependencies": { 40 | "prop-types": "^15.7.2" 41 | }, 42 | "devDependencies": { 43 | "@testing-library/jest-dom": "^5.11.0", 44 | "@testing-library/react": "^10.4.3", 45 | "@types/jest": "^26.0.3", 46 | "@types/testing-library__jest-dom": "^5.9.1", 47 | "cross-env": "^7.0.2", 48 | "fs-extra": "^9.0.1", 49 | "jest": "^26.1.0", 50 | "rimraf": "^3.0.2", 51 | "terser": "^4.8.0", 52 | "ts-jest": "^26.1.1" 53 | }, 54 | "peerDependencies": { 55 | "@material-ui/core": "^4.12.3", 56 | "react": "^17.0.0", 57 | "react-dom": "^17.0.0", 58 | "styled-components": "^5.0.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/components-extra/scripts/afterBuild.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Highly inspired from material-ui's build scripts: https://github.com/mui-org/material-ui/blob/master/scripts/copy-files.js 3 | * Credits go to them :) 4 | */ 5 | 6 | const fs = require('fs-extra') 7 | const path = require('path') 8 | 9 | const PACKAGE_PATH = process.cwd() 10 | const BUILD_PATH = path.join(PACKAGE_PATH, './build') 11 | 12 | async function includeFileInBuild(file) { 13 | const sourcePath = path.resolve(PACKAGE_PATH, file) 14 | const targetPath = path.resolve(BUILD_PATH, path.basename(file)) 15 | await fs.copy(sourcePath, targetPath) 16 | console.log(`Copied ${sourcePath} to ${targetPath}`) 17 | } 18 | 19 | async function createPackageFile() { 20 | const packageData = await fs.readFile(path.resolve(PACKAGE_PATH, './package.json'), 'utf8') 21 | // eslint-disable-next-line no-unused-vars 22 | const { scripts, devDependencies, ...packageDataOther } = JSON.parse(packageData) 23 | const newPackageData = { 24 | ...packageDataOther, 25 | main: './index.js', 26 | module: './esm/index.js', 27 | typings: './index.d.ts', 28 | } 29 | const targetPath = path.resolve(BUILD_PATH, './package.json') 30 | await fs.writeFile(targetPath, JSON.stringify(newPackageData, null, 2), 'utf8') 31 | console.log(`package.json created in build folder.`) 32 | 33 | return newPackageData 34 | } 35 | 36 | async function copyMetaFiles() { 37 | const filesToCopy = ['../../CHANGELOG.md', '../../LICENSE', '../../README.md'] 38 | await Promise.all(filesToCopy.map(includeFileInBuild)) 39 | } 40 | 41 | async function main() { 42 | await createPackageFile() 43 | await copyMetaFiles() 44 | } 45 | 46 | main() 47 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/BackToTop/BackToTop.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef, useEffect, useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import { Zoom, FabProps } from '@material-ui/core' 5 | import { useTheme } from '@material-ui/core/styles' 6 | 7 | import ArrowUpward from './components/ArrowUpward' 8 | import Button from './components/Button' 9 | 10 | import { isServerSide, serverDocument } from '../../utils' 11 | import { ComponentExtra } from '../../types' 12 | 13 | /** 14 | * 15 | * API: 16 | * 17 | * - [BackToTop API](https://components-extra.netlify.app/components/back-to-top) 18 | * - inherits [Fab API](https://material-ui.com/api/fab/) 19 | */ 20 | const BackToTop: React.FC = ({ 21 | forwardedRef, 22 | keepHash = false, 23 | ...rest 24 | }: BackToTopProps) => { 25 | const { 26 | mixins: { backToTop }, 27 | transitions, 28 | } = useTheme() 29 | const [display, setDisplay] = useState(false) 30 | const { body, documentElement } = isServerSide() ? serverDocument : document 31 | 32 | const transitionDuration = { 33 | enter: transitions.duration.enteringScreen, 34 | exit: transitions.duration.leavingScreen, 35 | } 36 | 37 | useEffect(() => { 38 | const { startHeight } = backToTop 39 | 40 | const onScroll = (): void => { 41 | if (body.scrollTop > startHeight || documentElement.scrollTop > startHeight) { 42 | setDisplay(true) 43 | } else { 44 | setDisplay(false) 45 | } 46 | } 47 | 48 | document.addEventListener('scroll', onScroll) 49 | return (): void => document.removeEventListener('scroll', onScroll) 50 | }, [body.scrollTop, documentElement.scrollTop, backToTop, keepHash]) 51 | 52 | const scrollToTop = (): void => { 53 | if (!keepHash && !isServerSide()) { 54 | history.replaceState({}, '', '#') 55 | } 56 | documentElement && documentElement.scrollIntoView({ behavior: 'smooth' }) 57 | } 58 | 59 | return ( 60 | 67 | 77 | 78 | ) 79 | } 80 | 81 | BackToTop.displayName = 'BackToTop' 82 | 83 | export interface BackToTopProps extends Omit { 84 | /** 85 | * Optional. There already is the arrow icon as a child. However, you can still 86 | * bring any node you would like to render inside the button. 87 | */ 88 | children?: React.ReactNode 89 | /** 90 | * Ref forwarded to the HTML Root element. 91 | */ 92 | forwardedRef?: React.Ref 93 | /** 94 | * By default, the component will clear any existing hash from the URL when clicked, since it goes 95 | * to the top of the page. Set this prop to true if you don't want this behavior. 96 | */ 97 | keepHash?: boolean 98 | } 99 | 100 | BackToTop.propTypes = { 101 | children: PropTypes.node, 102 | keepHash: PropTypes.bool, 103 | } 104 | 105 | export type BackToTopType = ComponentExtra, 'button'> 106 | 107 | const BackToTopExtra = styled( 108 | forwardRef((props: BackToTopProps, ref: React.Ref) => ( 109 | 110 | )), 111 | )`` as BackToTopType 112 | 113 | export default BackToTopExtra 114 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/BackToTop/components/ArrowUpward.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { SvgIcon } from '@material-ui/core' 3 | import styled from 'styled-components' 4 | 5 | const StyledIcon = styled(SvgIcon)` 6 | ${({ theme: { palette } }) => ` 7 | fill: ${palette.primary.contrastText}; 8 | `} 9 | ` 10 | 11 | const ArrowUpward: React.FC = () => { 12 | return ( 13 | 14 | 15 | 16 | ) 17 | } 18 | 19 | export default ArrowUpward 20 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/BackToTop/components/Button.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Fab } from '@material-ui/core' 3 | 4 | const Button = styled(Fab)` 5 | position: fixed; 6 | bottom: 25px; 7 | right: 25px; 8 | ${({ theme }): string => ` 9 | z-index: ${theme.zIndex.backToTop}; 10 | `} 11 | ` 12 | 13 | Button.displayName = 'BackToTop-Button' 14 | 15 | export default Button 16 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/BackToTop/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './BackToTop' 2 | export * from './BackToTop' 3 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/BackToTop/tests/BackToTop.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { fireEvent } from '@testing-library/react' 3 | import { renderWithTheme } from 'utils-test' 4 | import BackToTop from '..' 5 | 6 | const START_HEIGHT_TEST = 42 7 | 8 | describe('', () => { 9 | beforeAll(() => { 10 | document.documentElement.scrollIntoView = jest.fn(() => { 11 | document.documentElement.scrollTop = 0 12 | }) 13 | }) 14 | 15 | test('it forwards the given ref correctly', () => { 16 | const ref = React.createRef() 17 | renderWithTheme() 18 | expect(ref.current instanceof HTMLButtonElement).toBeTruthy() 19 | }) 20 | 21 | test('it renders correctly', () => { 22 | const { getByTestId } = renderWithTheme() 23 | const backToTop = getByTestId('back-to-top-button') 24 | expect(backToTop).toBeInTheDocument() 25 | }) 26 | 27 | test('it scrolls document to top when clicked', () => { 28 | const { getByTestId } = renderWithTheme() 29 | const backToTop = getByTestId('back-to-top-button') 30 | document.documentElement.scrollTop = START_HEIGHT_TEST 31 | fireEvent.click(backToTop) 32 | expect(document.documentElement.scrollTop).toEqual(0) 33 | }) 34 | 35 | test('it removes any url hash by default when scroll is on top', () => { 36 | const { getByTestId } = renderWithTheme() 37 | const backToTop = getByTestId('back-to-top-button') 38 | document.documentElement.scrollTop = START_HEIGHT_TEST 39 | window.location.hash = '#test' 40 | fireEvent.click(backToTop) 41 | fireEvent.scroll(document) 42 | expect(window.location.hash).toEqual('') 43 | }) 44 | 45 | test('it does not remove any url hash when scroll is on top if keepHash prop is set', () => { 46 | const { getByTestId } = renderWithTheme() 47 | const backToTop = getByTestId('back-to-top-button') 48 | document.documentElement.scrollTop = START_HEIGHT_TEST 49 | window.location.hash = '#test' 50 | fireEvent.click(backToTop) 51 | fireEvent.scroll(document) 52 | expect(window.location.hash).toEqual('#test') 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/Card/components/CardButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { makeStyles } from '@material-ui/core/styles' 4 | import { Button, ButtonProps } from '@material-ui/core' 5 | 6 | import { useBig } from '../hooks/Context' 7 | import { ComponentExtra } from '../../../types' 8 | 9 | const useStyles = makeStyles((theme) => ({ 10 | button: { 11 | color: theme.palette.links, 12 | }, 13 | })) 14 | 15 | const CardButton: React.FC = ({ className, ...rest }: ButtonProps) => { 16 | const classes = useStyles() 17 | const big = useBig() 18 | 19 | return ( 20 | 44 | 45 | ) 46 | } 47 | 48 | Item.displayName = 'Footer.Item' 49 | 50 | Item.propTypes = { 51 | icon: PropTypes.node, 52 | } 53 | 54 | export interface FooterItemProps extends ButtonProps { 55 | /** 56 | * The item's icon. 57 | */ 58 | icon?: React.ReactNode 59 | } 60 | 61 | export type FooterItemType = ComponentExtra, 'a'> 62 | 63 | const ItemExtra = styled(Item)`` as FooterItemType 64 | 65 | export default ItemExtra 66 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/Footer/components/ItemsContainer.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export default styled.div` 4 | display: flex; 5 | flex-wrap: wrap; 6 | justify-content: space-evenly; 7 | ` 8 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/Footer/components/Title.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import { Typography } from '@material-ui/core' 5 | import { makeStyles } from '@material-ui/core/styles' 6 | 7 | const useStyles = makeStyles(({ palette }) => ({ 8 | title: { 9 | fontWeight: 500, 10 | color: palette.primary.contrastText, 11 | }, 12 | })) 13 | 14 | const Separator = styled.hr` 15 | width: 30%; 16 | margin-bottom: 48px; 17 | ${({ theme }): string => ` 18 | border-color: ${theme.palette.primary.contrastText}; 19 | `} 20 | ` 21 | 22 | const Title: React.FunctionComponent = ({ text = '' }: TitleProps) => { 23 | const classes = useStyles() 24 | 25 | return ( 26 | <> 27 | 28 | {text} 29 | 30 | 31 | 32 | ) 33 | } 34 | 35 | Title.propTypes = { 36 | /** 37 | * The title's text. 38 | */ 39 | text: PropTypes.string.isRequired, 40 | } 41 | 42 | export interface TitleProps { 43 | /** 44 | * The title's text. 45 | */ 46 | text: string 47 | } 48 | 49 | export default Title 50 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/Footer/components/TopContainer.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { CardMedia } from '@material-ui/core' 3 | 4 | const TopContainer = styled(CardMedia)` 5 | padding: 36px; 6 | ` 7 | 8 | export default TopContainer 9 | -------------------------------------------------------------------------------- /packages/components-extra/src/components/Footer/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './Footer' 2 | export * from './Footer' 3 | export { FooterColumnProps, FooterColumnType } from './components/Column' 4 | export { FooterItemProps, FooterItemType } from './components/Item' -------------------------------------------------------------------------------- /packages/components-extra/src/components/Footer/tests/Footer.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { fireEvent } from '@testing-library/react' 3 | 4 | import { renderWithTheme } from 'utils-test' 5 | import Footer from '..' 6 | 7 | let I_WAS_FIRED = false 8 | const TITLE = 'awesome title' 9 | const BANNER_CONTENT = "I'm the banner content" 10 | const LINK = 'https://components-extra.netlify.com' 11 | const ITEMS = [ 12 | { 13 | label: 'label 1', 14 | onClick: (): void => { 15 | I_WAS_FIRED = true 16 | }, 17 | }, 18 | { 19 | label: 'label 2', 20 | href: LINK, 21 | }, 22 | ] 23 | 24 | const BannerContent: React.FC = () =>
{BANNER_CONTENT}
25 | 26 | describe('