Repository: buildo/react-cookie-banner Branch: master Commit: 79eb5aca6629 Files: 27 Total size: 45.0 KB Directory structure: gitextract_jyj6cf0v/ ├── .gitignore ├── .hophoprc ├── .smooth-releaserc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── ci/ │ ├── pipeline.yml │ ├── test.sh │ └── test.yml ├── examples/ │ ├── Examples.md │ └── examples.scss ├── package.json ├── src/ │ ├── BannerContent.tsx │ ├── CookieBanner.tsx │ ├── CookieBannerUniversal.tsx │ ├── README.md │ ├── index.ts │ └── styleUtils.ts ├── styleguide/ │ ├── index.html │ └── setup.ts ├── styleguide.config.js ├── test/ │ ├── setup.js │ └── tests/ │ ├── CookieBanner.test.tsx │ └── __snapshots__/ │ └── CookieBanner.test.tsx.snap ├── tsconfig.json ├── typings/ │ └── react-cookie.d.ts └── webpack.config.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ node_modules coverage npm-debug.log lib .vscode ================================================ FILE: .hophoprc ================================================ toggl: n branchPrefix: n branchSuffix: y ================================================ FILE: .smooth-releaserc ================================================ { "github": { "dataType": "pullRequests" }, "tasks": { "npm-publish": true, "npm-version": true, "gh-release": true, "changelog": true } } ================================================ FILE: CHANGELOG.md ================================================ # Change Log ## [v4.1.0](https://github.com/buildo/react-cookie-banner/tree/v4.1.0) (2019-12-11) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v4.0.0...v4.1.0) #### New features: - Leverage componentDidUpdate hook instead of componentWillReceiveProps [#67](https://github.com/buildo/react-cookie-banner/pull/67) - Fixing build after migration to Concourse [#64](https://github.com/buildo/react-cookie-banner/pull/64) - Migrating from drone to concourse [#63](https://github.com/buildo/react-cookie-banner/pull/63) - Updated link prop documentation [#57](https://github.com/buildo/react-cookie-banner/pull/57) ## [v4.0.0](https://github.com/buildo/react-cookie-banner/tree/v4.0.0) (2018-05-04) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v3.0.0...v4.0.0) #### New features: - Clarify usage of onAccept-callback in documentation [#53](https://github.com/buildo/react-cookie-banner/pull/53) - #35: support `Link` from react-router-dom (button-close not rendered anymore when using custom children) (closes #35) [#52](https://github.com/buildo/react-cookie-banner/pull/52) - Add Click To Dismiss [#51](https://github.com/buildo/react-cookie-banner/pull/51) ## [v3.0.0](https://github.com/buildo/react-cookie-banner/tree/v3.0.0) (2017-12-12) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v2.0.0...v3.0.0) #### Breaking: - Accessibility improvements: Button elements & a rel attributes [#43](https://github.com/buildo/react-cookie-banner/pull/43) ## [v2.0.0](https://github.com/buildo/react-cookie-banner/tree/v2.0.0) (2017-12-12) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v1.0.1...v2.0.0) #### New features: - #41: Server side rendering issues (closes #41) [#42](https://github.com/buildo/react-cookie-banner/pull/42) ## [v1.0.1](https://github.com/buildo/react-cookie-banner/tree/v1.0.1) (2017-12-04) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v1.0.0...v1.0.1) #### Fixes (bugs & defects): - #39: import in v1.0.0 is broken (closes #39) [#40](https://github.com/buildo/react-cookie-banner/pull/40) ## [v1.0.0](https://github.com/buildo/react-cookie-banner/tree/v1.0.0) (2017-12-03) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.18...v1.0.0) #### Breaking: - #37: Refactor in TypeScript! (closes #37) [#38](https://github.com/buildo/react-cookie-banner/pull/38) #### New features: - Adding import to react-components showroom example [#36](https://github.com/buildo/react-cookie-banner/pull/36) ## [v0.0.18](https://github.com/buildo/react-cookie-banner/tree/v0.0.18) (2017-05-03) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.17...v0.0.18) #### New features: - Add a 'cookiePath' property [#34](https://github.com/buildo/react-cookie-banner/pull/34) - #29: Template and cookie logic should live in different components (closes #29) [#30](https://github.com/buildo/react-cookie-banner/pull/30) #### Fixes (bugs & defects): - #31: React15 throws warning for unknown prop (closes #31) [#32](https://github.com/buildo/react-cookie-banner/pull/32) ## [v0.0.17](https://github.com/buildo/react-cookie-banner/tree/v0.0.17) (2016-10-13) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.16...v0.0.17) #### New features: - #26: Use eslint-config-buildo (closes #26) [#27](https://github.com/buildo/react-cookie-banner/pull/27) - Custom component can receive onAccept prop using a children function [#25](https://github.com/buildo/react-cookie-banner/pull/25) ## [v0.0.16](https://github.com/buildo/react-cookie-banner/tree/v0.0.16) (2016-10-05) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.15...v0.0.16) #### New features: - #23: Cookie expiration date (closes #23) [#24](https://github.com/buildo/react-cookie-banner/pull/24) - #21: Renew example (closes #21) [#22](https://github.com/buildo/react-cookie-banner/pull/22) ## [v0.0.15](https://github.com/buildo/react-cookie-banner/tree/v0.0.15) (2016-09-14) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.14...v0.0.15) ## [v0.0.14](https://github.com/buildo/react-cookie-banner/tree/v0.0.14) (2016-08-10) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.13...v0.0.14) ## [v0.0.13](https://github.com/buildo/react-cookie-banner/tree/v0.0.13) (2016-08-10) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.12...v0.0.13) #### Fixes (bugs & defects): - Replace `mousewheel` event with `scroll` [#20](https://github.com/buildo/react-cookie-banner/pull/20) #### New features: - #16: if dismissOnScroll at false, the click on the close button do not close the banner (closes #16) [#17](https://github.com/buildo/react-cookie-banner/pull/17) ## [v0.0.12](https://github.com/buildo/react-cookie-banner/tree/v0.0.12) (2016-05-31) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.11...v0.0.12) #### New features: - add target to the cookie-link [#14](https://github.com/buildo/react-cookie-banner/pull/14) ## [v0.0.11](https://github.com/buildo/react-cookie-banner/tree/v0.0.11) (2015-12-11) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.10...v0.0.11) ## [v0.0.10](https://github.com/buildo/react-cookie-banner/tree/v0.0.10) (2015-10-14) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.9...v0.0.10) #### New features: - #9: code clean (closes #9) [#10](https://github.com/buildo/react-cookie-banner/pull/10) - Allow custom styles [#8](https://github.com/buildo/react-cookie-banner/pull/8) - #3: Remove .gitkeep from src and tests (closes #3) [#7](https://github.com/buildo/react-cookie-banner/pull/7) ## [v0.0.9](https://github.com/buildo/react-cookie-banner/tree/v0.0.9) (2015-07-14) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.8...v0.0.9) #### Fixes (bugs & defects): - #5: Is not safe for universal rendering [#6](https://github.com/buildo/react-cookie-banner/pull/6) ## [v0.0.8](https://github.com/buildo/react-cookie-banner/tree/v0.0.8) (2015-07-03) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.7...v0.0.8) #### New features: - dismissOnScroll should take an optional Y threshold (default: 0) [#4](https://github.com/buildo/react-cookie-banner/pull/4) ## [v0.0.7](https://github.com/buildo/react-cookie-banner/tree/v0.0.7) (2015-06-30) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.6...v0.0.7) ## [v0.0.6](https://github.com/buildo/react-cookie-banner/tree/v0.0.6) (2015-06-29) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.5...v0.0.6) ## [v0.0.5](https://github.com/buildo/react-cookie-banner/tree/v0.0.5) (2015-06-25) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.4...v0.0.5) ## [v0.0.4](https://github.com/buildo/react-cookie-banner/tree/v0.0.4) (2015-06-19) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.3...v0.0.4) ## [v0.0.3](https://github.com/buildo/react-cookie-banner/tree/v0.0.3) (2015-06-14) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.2...v0.0.3) ## [v0.0.2](https://github.com/buildo/react-cookie-banner/tree/v0.0.2) (2015-06-12) [Full Changelog](https://github.com/buildo/react-cookie-banner/compare/v0.0.1...v0.0.2) ## [v0.0.1](https://github.com/buildo/react-cookie-banner/tree/v0.0.1) (2015-06-12) ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015 buildo Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![Build Status](https://drone.our.buildo.io/api/badges/buildo/react-cookie-banner/status.svg)](https://drone.our.buildo.io/buildo/react-cookie-banner) ![](https://img.shields.io/npm/v/react-cookie-banner.svg) # React Cookie Banner A cookie banner for React that can be dismissed with a simple scroll. Because [fuck the Cookie Law](http://nocookielaw.com/) that's why. (If you *really* want to annoy your users you can disable this feature but this is strongly discouraged!). ```jsx import CookieBanner from 'react-cookie-banner'; React.renderComponent(
{}} cookie="user-has-accepted-cookies" />
, document.body ); ``` [Live Examples](http://react-components.buildo.io/#cookiebanner) ## Install ``` npm install --save react-cookie-banner ``` or using `yarn`: ``` yarn add react-cookie-banner ``` ## API You can see `CookieBanner`'s props in its own [README.md](https://github.com/buildo/react-cookie-banner/blob/master/src/README.md) ## Style `react-cookie-banner` comes with a nice default style made using inline-style. Of course, you can customize it as you like in several ways. Based on how many changes you want to apply, you can style `react-cookie-banner` as follows: ### You like the original style and you wish to make only a few modifications Why spending hours on the CSS when you have such a nice default style? :) In this case you can: #### 1) Override the predefined inline-styles In this example we change the message font-size and make the banner slightly transparent with the `styles` prop: ```jsx ``` See [`src/styleUtils.ts`](https://github.com/buildo/react-cookie-banner/blob/master/src/styleUtils.ts) for a complete list of overridable style objects. #### 2) Beat it with good old CSS (or SASS) The banner is structured as follows: ```jsx
{this.props.message} Learn more
``` You can style every part of it using the appropriate `className`: ```sass .your-class-name.react-cookie-banner { background-color: rgba(60, 60, 60, 0.8); .cookie-message { font-weight: 400; } } ``` ### You need to heavily adapt the style to your application Your creative designer wants to change the style of the cookie banner completely? Don't worry, we got your covered! If you need to re-style it, you can: #### 1) Disable the default style and use your CSS You may disable the default style by simply setting the prop `disableStyle` to `true`: ```jsx ``` Now you can re-style the cookie banner completely using your own CSS. #### 2) Use your own cookie banner! Don't like the layout either? You can use your own custom cookie banner component by passing it as `children` and still let `react-cookie-banner` handle the hassle of managing `cookies` for you :) ```jsx {(onAccept) => ( {/* rendered directly without any
wrapper */} )} ``` ## Cookies manipulation `react-cookie-banner` uses **`universal-cookie`** to manipulate cookies. You can import the `Cookies` class and use it as follows: ```js import { Cookies } from 'react-cookie-banner' const cookies = new Cookies(/* Your cookie header, on browsers defaults to document.cookie */) // simple set cookie.set('test', 'a') // complex set - cookie(name, value, ttl, path, domain, secure) cookie.set('test', 'a', { expires: new Date(2020-05-04) path: '/api', domain: '*.example.com', secure: true }) // get cookies.get('test') // destroy cookies.remove('test', '', -1) ``` Please refer to [universal-cookie](https://github.com/reactivestack/cookies/tree/master/packages/universal-cookie#api---cookies-class) repo for more documentation. ## Server side rendering (aka Universal) `react-cookie-banner` supports SSR thanks to `react-cookie`. If you want to support SSR, you should use the `CookieProvider` from `react-cookie` and the `CookieBannerUniversal` wrapper: ```jsx import { Cookies, CookiesProvider, CookieBannerUniversal } from 'react-cookie-banner' const cookies = new Cookies(/* Your cookie header, on browsers defaults to document.cookie */) ``` ================================================ FILE: ci/pipeline.yml ================================================ resource_types: - name: pull-request type: docker-image source: repository: teliaoss/github-pr-resource resources: - name: master type: git icon: github-circle source: uri: git@github.com:buildo/react-cookie-banner branch: master private_key: ((private-key)) - name: pr type: pull-request source: repository: buildo/react-cookie-banner access_token: ((github-token)) jobs: - name: pr-test plan: - get: react-cookie-banner resource: pr trigger: true version: every - put: pr params: path: react-cookie-banner status: pending context: concourse - do: - task: test file: react-cookie-banner/ci/test.yml attempts: 2 on_success: put: pr params: path: react-cookie-banner status: success context: concourse on_failure: put: pr params: path: react-cookie-banner status: failure context: concourse - name: test plan: - get: react-cookie-banner resource: master trigger: true - do: - task: test file: react-cookie-banner/ci/test.yml attempts: 2 ================================================ FILE: ci/test.sh ================================================ #!/bin/sh set -e yarn install --no-progress yarn typecheck yarn test ================================================ FILE: ci/test.yml ================================================ platform: linux image_resource: type: docker-image source: repository: node tag: 8-slim inputs: - name: react-cookie-banner caches: - path: react-cookie-banner/node_modules run: path: ci/test.sh dir: react-cookie-banner ================================================ FILE: examples/Examples.md ================================================ ### Examples ```js initialState = { dismissOnScroll: true, dismissOnClick: false } // reset cookies on first render !state.accepted && cookies.get('accepts-cookies') && cookies.remove('accepts-cookies') const styles = { banner: { fontFamily: 'Source Sans Pro', height: 57, background: 'rgba(52, 64, 81, 0.88) url(/cookie.png) 20px 50% no-repeat', backgroundSize: '30px 30px', backgroundColor: '', fontSize: '15px', fontWeight: 600 }, button: { border: '1px solid white', borderRadius: 4, width: 66, height: 32, lineHeight: '32px', background: 'transparent', color: 'white', fontSize: '14px', fontWeight: 600, opacity: 1, right: 20, marginTop: -18 }, message: { display: 'block', padding: '9px 67px', lineHeight: 1.3, textAlign: 'left', marginRight: 244, color: 'white' }, link: { textDecoration: 'none', fontWeight: 'bold' } } const message = "Buildo uses cookies to guarantee users the employment of its site features, offering a better purchasing experience. By continuing to browse the site you're agreeing to our use of cookies." function toggleDismissOnScroll() { setState({ dismissOnScroll: !state.dismissOnScroll }) } function toggleDismissOnClick() { setState({ dismissOnClick: !state.dismissOnClick }) } function resetCookies() { cookies.remove('accepts-cookies') setState({ accepted: false }) }

accepts-cookies: {cookies.get('accepts-cookies') || 'false'}

More information on our use of cookies} buttonMessage='Close' dismissOnScroll={state.dismissOnScroll} dismissOnClick={state.dismissOnClick} onAccept={() => setState({ accepted: true })} />
``` #### Server side rendering You can pass your own `cookies` instance to `CookiesProvider` to support SSR: ```js static // import { Cookies, CookiesProvider, CookieBannerUniversal } from 'react-cookie-banner'; const cookies = new Cookies(/* Your cookie header, on browsers defaults to document.cookie */); ``` ================================================ FILE: examples/examples.scss ================================================ .react-cookie-banner { position: fixed !important; bottom: 0 !important; left: 0 !important; width: 100% !important; z-index: 999 !important; font-family: 'Source Sans Pro' } ================================================ FILE: package.json ================================================ { "name": "react-cookie-banner", "version": "4.1.0", "description": "React Cookie banner which can be automatically dismissed with a scroll. Because fuck The Cookie Law, that's why.", "main": "lib", "typings": "lib", "files": [ "lib", "src", "examples", "styleguide", "typings" ], "scripts": { "test": "jest", "build": "rm -rf lib && mkdir lib && tsc", "preversion": "npm run test", "prepublish": "npm run build", "start": "styleguidist server", "typecheck": "tsc --noEmit", "release-version": "smooth-release" }, "repository": { "type": "git", "url": "https://github.com/buildo/react-cookie-banner.git" }, "keywords": [ "react", "react-component", "cookie", "cookies", "banner", "eu", "law" ], "author": "Francesco Cioria ", "license": "ISC", "bugs": { "url": "https://github.com/buildo/react-cookie-banner/issues" }, "homepage": "https://github.com/buildo/react-cookie-banner", "dependencies": { "classnames": "2.2.5", "lodash.omit": "^4.5.0", "react-addons-clone-with-props": "^0.14.8", "react-cookie": "^2.1.2" }, "devDependencies": { "@types/classnames": "^2.2.3", "@types/enzyme": "2.8.6", "@types/jest": "^22", "@types/lodash.omit": "^4.5.3", "@types/node": "9.6.4", "@types/prop-types": "^15.5.2", "@types/react": "^16.3.10", "babel-loader": "^7.1.2", "babel-preset-buildo": "^0.1.1", "css-loader": "^0.28.5", "enzyme": "^3.2.0", "enzyme-adapter-react-16": "^1.1.0", "file-loader": "^1.1.5", "jest": "^22", "node-sass": "^4.8.3", "progress-bar-webpack-plugin": "^1.10.0", "raw-loader": "^0.5.1", "react": "^16", "react-docgen-typescript": "^1.1.0", "react-dom": "^16", "react-styleguidist": "^6.0.33", "react-test-renderer": "^16.2.0", "sass-loader": "^6.0.6", "smooth-release": "^8.0.4", "ts-jest": "^22", "ts-loader": "^2.3.3", "typescript": "^2.8.1", "webpack": "3.5.5" }, "peerDependencies": { "react": ">= 0.12.x" }, "jest": { "setupFiles": [ "/test/setup.js" ], "transform": { "^.+\\.tsx?$": "/node_modules/ts-jest/preprocessor.js" }, "testRegex": "(.*[.](test))[.](tsx?)$", "moduleFileExtensions": [ "js", "ts", "tsx" ], "testURL": "http://localhost/" } } ================================================ FILE: src/BannerContent.tsx ================================================ import * as React from 'react'; import * as PropTypes from 'prop-types'; import omit = require('lodash.omit'); import * as cx from 'classnames'; import * as styleUtils from './styleUtils'; export type Props = { /** message written inside default cookie banner */ message?: string, /** called when user accepts cookies */ onAccept: () => void, /** JSX element to link to your cookie-policy page */ link?: JSX.Element, /** message written inside the button of the default cookie banner */ buttonMessage?: string, /** className passed to close-icon */ closeIcon?: string, /** pass `true` if you want to disable default style */ disableStyle?: boolean, /** object with custom styles used to overwrite default ones */ styles?: object, className?: string, /** pass `true` if you want to dismiss by clicking anywhere on the banner */ dismissOnClick?: boolean } export const propTypes = { message: PropTypes.string, onAccept: PropTypes.func.isRequired, link: PropTypes.element, buttonMessage: PropTypes.string, closeIcon: PropTypes.string, disableStyle: PropTypes.bool, styles: PropTypes.object, className: PropTypes.string, dismissOnClick: PropTypes.bool }; /** * React Cookie banner template */ export default class BannerContent extends React.Component { static propTypes = propTypes getStyle = (style: 'message' | 'banner' | 'link' | 'button' | 'icon') => { const { disableStyle, styles = {} } = this.props; if (!disableStyle) { // apply custom styles if available return { ...styleUtils.getStyle(style), ...styles[style] }; } } templateCloseIcon = (className: string, onClick: () => void, style: React.CSSProperties ) => ( ) templateCloseButton = (buttonMessage: string, onClick: () => void, style: React.CSSProperties) => ( ) templateLink = (link: JSX.Element, style: React.CSSProperties) => ( React.cloneElement(link, link.props.style ? undefined : { style }) ) render() { const { getStyle, props: { onAccept, className, message, closeIcon, link, buttonMessage = 'Got it', ..._wrapperProps } } = this; const cookieMessageStyle = getStyle('message'); const wrapperProps = { ...omit(_wrapperProps, Object.keys(propTypes)), className: cx('react-cookie-banner', className), style: getStyle('banner') }; return (
{message} {link && this.templateLink(link, getStyle('link'))} {!closeIcon && this.templateCloseButton(buttonMessage, onAccept, getStyle('button'))} {!!closeIcon && this.templateCloseIcon(closeIcon, onAccept, getStyle('icon'))}
); } bannerClicked = () => { if (this.props.dismissOnClick) { this.props.onAccept(); } } } ================================================ FILE: src/CookieBanner.tsx ================================================ import * as React from 'react'; import * as PropTypes from 'prop-types'; import omit = require('lodash.omit'); import { Cookies } from 'react-cookie'; import BannerContent, { propTypes as BannerContentPropTypes, Props as BannerContentProps } from './BannerContent'; export type CookieBannerRequiredProps = { /** custom component rendered if user has not accepted cookies */ children?: any, /** called when user accepts cookies */ onAccept?: (o: { cookie: string }) => void, /** instance of Cookies class to be used in server-side-rendering */ cookies?: Cookies, /** cookie-key used to save user's decision about you cookie-policy */ cookie?: string, /** used to set the cookie expiration */ cookieExpiration?: number | { years?: number, days?: number, hours?: number }, /** used to set the cookie path */ cookiePath?: string, /** whether the cookie banner should be dismissed on scroll or not */ dismissOnScroll?: boolean, /** amount of pixel the user need to scroll to dismiss the cookie banner */ dismissOnScrollThreshold?: number } export type CookieBannerDefaultProps = { onAccept: () => void, dismissOnScroll: boolean, cookies: Cookies, cookie: string, cookieExpiration: { years: number }, buttonMessage: string, dismissOnScrollThreshold: number, styles: object } export type CookieBannerProps = BannerContentProps & CookieBannerRequiredProps & Partial; export namespace CookieBanner { export type Props = CookieBannerProps; } type CookieBannerDefaultedProps = CookieBannerRequiredProps & CookieBannerDefaultProps; export type State = { listeningScroll: boolean } /** * React Cookie banner dismissable with just a scroll! */ export default class CookieBanner extends React.Component { static propTypes = { ...BannerContentPropTypes, children: PropTypes.oneOfType([ PropTypes.node, PropTypes.func ]), onAccept: PropTypes.func, cookies: PropTypes.instanceOf(Cookies), cookie: PropTypes.string, cookieExpiration: PropTypes.oneOfType([ PropTypes.number, PropTypes.shape({ years: PropTypes.number, days: PropTypes.number, hours: PropTypes.number }) ]), cookiePath: PropTypes.string, dismissOnScroll: PropTypes.bool, dismissOnScrollThreshold: PropTypes.number } static defaultProps = { onAccept: () => {}, dismissOnScroll: true, cookies: new Cookies(), cookie: 'accepts-cookies', cookieExpiration: { years: 1 }, buttonMessage: 'Got it', dismissOnScrollThreshold: 0, styles: {} }; state = { listeningScroll: false } componentDidMount() { this.addOnScrollListener(); } addOnScrollListener = (props?: CookieBanner.Props) => { const _props = props || this.props; if (!this.state.listeningScroll && !this.hasAcceptedCookies() && _props.dismissOnScroll) { if ((window as any).attachEvent) { // Internet Explorer (window as any).attachEvent('onscroll', this.onScroll); } else if (window.addEventListener) { window.addEventListener('scroll', this.onScroll, false); } this.setState({ listeningScroll: true }); } } removeOnScrollListener = () => { if (this.state.listeningScroll) { if ((window as any).detachEvent) { // Internet Explorer (window as any).detachEvent('onscroll', this.onScroll); } else if (window.removeEventListener) { window.removeEventListener('scroll', this.onScroll, false); } this.setState({ listeningScroll: false }); } } onScroll = () => { // tacit agreement buahaha! (evil laugh) const { dismissOnScrollThreshold } = this.props as CookieBannerDefaultedProps; if (window.pageYOffset > dismissOnScrollThreshold) { this.onAccept(); } } getSecondsSinceExpiration = (cookieExpiration: CookieBannerRequiredProps['cookieExpiration']) => { if (typeof cookieExpiration === 'number') { return cookieExpiration; } const SECONDS_IN_YEAR = 31536000; const SECONDS_IN_DAY = 86400; const SECONDS_IN_HOUR = 3600; const _cookieExpiration = { years: 0, days: 0, hours: 0, ...cookieExpiration }; const { years, days, hours } = _cookieExpiration; return (years * SECONDS_IN_YEAR) + (days * SECONDS_IN_DAY) + (hours * SECONDS_IN_HOUR); } onAccept = () => { const { cookies, cookie, cookieExpiration, cookiePath: path, onAccept } = this.props as CookieBannerDefaultedProps; cookies.set(cookie, true, { path, expires: new Date(Date.now() + (this.getSecondsSinceExpiration(cookieExpiration) * 1000)) }); onAccept({ cookie }); if (this.state.listeningScroll) { this.removeOnScrollListener(); } else { this.forceUpdate(); } } hasAcceptedCookies() { const { cookies, cookie } = this.props as CookieBannerDefaultedProps; return cookies.get(cookie); } templateChildren(children: CookieBanner.Props['children'], onAccept: CookieBannerDefaultProps['onAccept']) { if (typeof children === 'function') { return children(onAccept); } return children; } render() { const { onAccept, props: { message, link, buttonMessage, closeIcon, disableStyle, styles, className, children, dismissOnClick, ...props } } = this; const hasAcceptedCookies = this.hasAcceptedCookies(); const bannerContentProps = { ...omit(props, Object.keys(CookieBanner.propTypes)), message, onAccept, link, buttonMessage, closeIcon, disableStyle, styles, className, dismissOnClick, }; if (hasAcceptedCookies) { return null; } return children ? this.templateChildren(children, onAccept) : ; } componentDidUpdate() { const { props } = this; if (props.dismissOnScroll) { this.addOnScrollListener(props); } else { this.removeOnScrollListener(); } } componentWillUnmount() { this.removeOnScrollListener(); } } ================================================ FILE: src/CookieBannerUniversal.tsx ================================================ import * as React from 'react'; import { withCookies } from 'react-cookie'; import CookieBanner, { CookieBannerProps } from './CookieBanner'; export default withCookies(CookieBanner) as React.ComponentClass; ================================================ FILE: src/README.md ================================================ # CookieBanner React Cookie banner dismissable with just a scroll! ## Props |Name|Type|Default|Description| |----|----|-------|-----------| | **children** | union(ReactChildren | Function) | | *optional*. Custom component rendered if user has not accepted cookies | | **message** | String | | *optional*. Message written inside default cookie banner | | **onAccept** | Function | "onAccept" | *optional*. Called when user accepts cookies | | **link** | JSX.Element | | *optional*. JSX element to link to your cookie-policy page | | **buttonMessage** | String | "Got it" | *optional*. Message written inside the button of the default cookie banner | | **cookie** | String | "accepts-cookies" | *optional*. Cookie-key used to save user's decision about you cookie-policy | | **cookieExpiration** | union(Integer | {years: ?Number, days: ?Number, hours: ?Number}) | { "years": 1 } | *optional*. Used to set the cookie expiration | | **cookiePath** | String | | *optional*. Used to set the cookie path | | **dismissOnScroll** | Boolean | true | *optional*. Whether the cookie banner should be dismissed on scroll or not | | **dismissOnScrollThreshold** | Number | 0 | *optional*. amount of pixel the user need to scroll to dismiss the cookie banner | | **closeIcon** | String | | *optional*. ClassName passed to close-icon | | **disableStyle** | Boolean | | *optional*. Pass `true` if you want to disable default style | | **styles** | Object | {} | *optional*. Object with custom styles used to overwrite default ones | | **className** | String | | *optional*. Additional `className` for wrapper element | ================================================ FILE: src/index.ts ================================================ import CookieBanner from './CookieBanner'; import CookieBannerUniversal from './CookieBannerUniversal'; import BannerContent from './BannerContent'; export default CookieBanner; export { Cookies, CookiesProvider } from 'react-cookie'; export { BannerContent, CookieBannerUniversal } ================================================ FILE: src/styleUtils.ts ================================================ const styles = { icon: { background: 'none', border: 'none', boxShadow: 'none', padding: '0', position: 'absolute', fontSize: '1em', top: '50%', marginTop: '-0.5em', right: '1em', color: 'white', cursor: 'pointer' }, link: { color: '#F0F0F0', textDecoration: 'underline', marginLeft: '10px' }, button: { position: 'absolute', top: '50%', right: '35px', lineHeight: '24px', marginTop: '-12px', padding: '0 8px', backgroundColor: 'rgba(255, 255, 255, 0.6)', border: 'none', borderRadius: '3px', boxShadow: 'none', fontSize: '12px', fontWeight: '500', color: '#242424', cursor: 'pointer' }, message: { lineHeight: '45px', fontWeight: 500, color: '#F0F0F0' }, banner: { position: 'relative', textAlign: 'center', backgroundColor: '#484848', width: '100%', height: '45px', zIndex: '10000' } }; const getStyle = (style: 'message' | 'banner' | 'link' | 'button' | 'icon') => styles[style]; export { getStyle }; ================================================ FILE: styleguide/index.html ================================================
================================================ FILE: styleguide/setup.ts ================================================ import { Cookies, CookiesProvider } from 'react-cookie'; import { CookieBannerUniversal } from '../src'; import '../examples/examples.scss'; import '../examples/cookie.png'; (global as any).cookies = new Cookies(); (global as any).CookiesProvider = CookiesProvider; (global as any).Cookies = Cookies; (global as any).CookieBannerUniversal = CookieBannerUniversal; ================================================ FILE: styleguide.config.js ================================================ const path = require('path'); module.exports = { // build serverPort: 8080, require: [ // "global" setup + sass imports path.resolve(__dirname, 'styleguide/setup.ts') ], // content title: 'react-cookie-banner', // assetsDir: 'styleguide/assets', template: 'styleguide/index.html', propsParser: require('react-docgen-typescript').parse, // detect docs using TS information sections: [{ name: 'CookieBanner', components: () => [path.resolve(__dirname, 'src/CookieBanner.tsx')] }], showCode: true, showUsage: false, // show props by default getExampleFilename() { return path.resolve(__dirname, 'examples/Examples.md'); } }; ================================================ FILE: test/setup.js ================================================ global.requestAnimationFrame = (callback) => { setTimeout(callback, 0); }; const Enzyme = require('enzyme'); const Adapter = require('enzyme-adapter-react-16'); Enzyme.configure({ adapter: new Adapter() }); ================================================ FILE: test/tests/CookieBanner.test.tsx ================================================ import { execSync } from 'child_process'; import * as fs from 'fs'; import * as path from 'path'; import * as React from 'react'; import { shallow, mount } from 'enzyme'; import CookieBanner from '../../src'; import { getStyle } from '../../src/styleUtils'; function resetCookies() { const cookies = document.cookie.split(';'); cookies.forEach(cookie => { const eqPos = cookie.indexOf('='); const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie; document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 GMT`; }); }; beforeEach(resetCookies); describe('secondsSinceExpiration', () => { const { getSecondsSinceExpiration } = new CookieBanner({ onAccept: () => {} }); it('should return "cookieExpiration" if it is an integer', () => { expect(getSecondsSinceExpiration(12345)).toBe(12345); }); it('should transform "years", "days" and "hours" into seconds', () => { expect(getSecondsSinceExpiration({ years: 1, days: 10, hours: 5 })).toBe(32418000); }); it('should handle missing "years", "days" or "hours"', () => { expect(getSecondsSinceExpiration({ days: 10 })).toBe(864000); expect(getSecondsSinceExpiration({})).toBe(0); }); }); describe('CookieBanner', () => { it('should be displayed if no cookies are set', () => { const component = shallow( {}} /> ); expect(component).toMatchSnapshot(); }); it('should be displayed with correct default styles', () => { const component = mount( } onAccept={() => {}} /> ); expect(component.find('.cookie-message').prop('style')).toEqual(getStyle('message')); expect(component.find('.button-close').prop('style')).toEqual(getStyle('button')); expect(component.find('a').prop('style')).toEqual(getStyle('link')); const componentWithIcon = mount( {}} /> ); expect(componentWithIcon.find('.icon').length).toBe(1); expect(componentWithIcon.find('button').prop('style')).toEqual(getStyle('icon')); }); it('should hide on click', () => { const component = mount( {}} /> ); expect(component.find('.react-cookie-banner')).toHaveLength(1); component.find('.button-close').simulate('click'); expect(component.find('.react-cookie-banner')).toHaveLength(0); }); it('should hide on click when dismissOnScroll is false', () => { const component = mount( {}} dismissOnScroll={false} /> ); expect(component.find('.react-cookie-banner')).toHaveLength(1); component.find('.button-close').simulate('click'); expect(component.find('.react-cookie-banner')).toHaveLength(0); }); it('should be displayed with correct message', () => { const component = mount( {}} /> ); expect(component.find('.cookie-message').text()).toBe('cookie message'); }); it('should be displayed with correct link element', () => { const children = 'children!' const component = mount( {children}} onAccept={() => {}} /> ); const cookieBanner = component.find('.cookie-link'); expect(cookieBanner.text()).toBe(children); }); it('should not overwrite link\'s style props, if present', () => { const style = { color: 'red' } const component = mount( } onAccept={() => {}} /> ); const cookieBanner = component.find('.cookie-link'); expect(cookieBanner.prop('style')).toEqual(style); }); it('should be displayed with correct link element', () => { const children = 'children!' const component = mount( {children}} onAccept={() => {}} /> ); expect(component.find('.cookie-link').text()).toBe(children); }); it('should be replaced with custom child component', () => { const MyComponent = () => (
); const component = mount( {}}> ); expect(component.find('.my-component')).toHaveLength(1); }); it('should be replaced with custom child component using function', () => { const MyOtherComponent = ({ onAccept }) => (
); const customTrigger = onAccept => ; const component = mount( {}}> {customTrigger} ); expect(component.find('.my-other-component')).toHaveLength(1); }); }); describe('build', () => { it('build script generates every needed file', () => { execSync('npm run build') expect(fs.readdirSync(path.resolve(__dirname, '../../lib'))).toMatchSnapshot() }) }) ================================================ FILE: test/tests/__snapshots__/CookieBanner.test.tsx.snap ================================================ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`CookieBanner should be displayed if no cookies are set 1`] = ` ShallowWrapper { Symbol(enzyme.__root__): [Circular], Symbol(enzyme.__unrendered__): , Symbol(enzyme.__renderer__): Object { "batchedUpdates": [Function], "checkPropTypes": [Function], "getNode": [Function], "render": [Function], "simulateError": [Function], "simulateEvent": [Function], "unmount": [Function], }, Symbol(enzyme.__node__): Object { "instance": null, "key": undefined, "nodeType": "class", "props": Object { "buttonMessage": "Got it", "className": undefined, "closeIcon": undefined, "disableStyle": undefined, "dismissOnClick": undefined, "link": undefined, "message": "cookie message", "onAccept": [Function], "styles": Object {}, }, "ref": null, "rendered": null, "type": [Function], }, Symbol(enzyme.__nodes__): Array [ Object { "instance": null, "key": undefined, "nodeType": "class", "props": Object { "buttonMessage": "Got it", "className": undefined, "closeIcon": undefined, "disableStyle": undefined, "dismissOnClick": undefined, "link": undefined, "message": "cookie message", "onAccept": [Function], "styles": Object {}, }, "ref": null, "rendered": null, "type": [Function], }, ], Symbol(enzyme.__options__): Object { "adapter": ReactSixteenAdapter { "options": Object { "enableComponentDidUpdateOnSetState": true, "legacyContextMode": "parent", "lifecycles": Object { "componentDidUpdate": Object { "onSetState": true, }, "getChildContext": Object { "calledByRenderer": false, }, "getDerivedStateFromError": true, "getDerivedStateFromProps": Object { "hasShouldComponentUpdateBug": false, }, "getSnapshotBeforeUpdate": true, "setState": Object { "skipsComponentDidUpdateOnNullish": true, }, }, }, }, Symbol(enzyme.__providerValues__): undefined, }, Symbol(enzyme.__providerValues__): Map {}, Symbol(enzyme.__childContext__): null, } `; exports[`build build script generates every needed file 1`] = ` Array [ "BannerContent.d.ts", "BannerContent.js", "CookieBanner.d.ts", "CookieBanner.js", "CookieBannerUniversal.d.ts", "CookieBannerUniversal.js", "index.d.ts", "index.js", "styleUtils.d.ts", "styleUtils.js", ] `; ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "target": "ES3", "module": "commonjs", "declaration": true, "allowSyntheticDefaultImports": false, "allowJs": false, "experimentalDecorators": true, "outDir": "./lib", "baseUrl": ".", "noImplicitAny": true, "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true, "noUnusedParameters": true, "strictNullChecks": true, "sourceMap": false, "alwaysStrict": true, "jsx": "react", "lib": [ "dom", "es6" ] }, "include": [ "src/*", "typings" ] } ================================================ FILE: typings/react-cookie.d.ts ================================================ // react-cookies typings are in WIP... copied the following from https://github.com/reactivestack/cookies/issues/107 declare module 'react-cookie' { import { Component, ComponentClass } from 'react'; export type Cookie = string; export class CookiesProvider extends Component<{},{}>{} export function withCookies(Component: ComponentClass): ComponentClass; export interface ReactCookieGetOptions{ doNotParse?: boolean; } export interface ReactCookieGetAllOptions{ doNotParse?: boolean; } export interface ReactCookieSetOptions{ path?: string; expires?: Date; maxAge?: number; domain?: string; secure?: boolean; httpOnly?: boolean; } export interface ReactCookieRemoveOptions{ path?: string; expires?: Date; maxAge?: number; domain?: string; secure?: boolean; httpOnly?: boolean; } export class Cookies { get: (key: string, options?: ReactCookieGetOptions) => Cookie | undefined; getAll: (options?: ReactCookieGetAllOptions) => Cookie[]; set(name: string, value: any, options?: ReactCookieSetOptions): void; remove(name: string, options?: ReactCookieRemoveOptions): void; } } ================================================ FILE: webpack.config.js ================================================ const path = require('path'); const ProgressBarPlugin = require('progress-bar-webpack-plugin'); module.exports = { resolve: { extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'] }, plugins: [ new ProgressBarPlugin() ], module: { rules: [ { test: /\.[jt]sx?$/, include: [ path.resolve(__dirname, 'src'), path.resolve(__dirname, 'styleguide') ], use: [ { loader: 'babel-loader', options: { presets: [['buildo', { env: 'react' }]] } }, { loader: 'ts-loader', options: { configFile: require('path').resolve(__dirname, 'tsconfig.json') } } ] }, { test: /\.css$/, loader: [ 'style-loader', { loader: 'css-loader', options: { modules: true } } ] }, { test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'] }, { test: /\.(png|jpg|gif)$/, loader: 'file-loader', options: { name: '[name].[ext]' } } ] } };