25 Commits
7.3.0 ... 8.0.1

Author SHA1 Message Date
f83c3a7227 bye bye np 2022-07-31 12:22:29 +02:00
2a1973c008 8.0.1 2022-07-31 12:17:32 +02:00
38deb77dfd second try of np.... 2022-07-31 12:16:51 +02:00
a2fd789751 disabled windows builds (I don't want to mess with settings for crlf... windows should learn to behave lol)
added npx to install-husky
removed postinstall
2022-07-31 12:14:11 +02:00
78411dcf57 8.0.0 2022-07-31 12:06:33 +02:00
c544f19c41 Switched to tsdx and Typescript
Added storybook with examples from readme
2022-07-31 12:04:58 +02:00
c6799ea446 release 7.6.0 2022-07-26 14:46:46 +02:00
d7fce12ea4 changelog version update 2022-07-26 14:46:34 +02:00
Oskar Filipowicz
d05509bf04 added customButtonWrapperAttributes prop (#167)
* added customButtonWrapperAttributes prop

* escape characters in README
2022-07-26 14:43:44 +02:00
dependabot[bot]
a7ce2de923 Bump terser from 5.7.1 to 5.14.2 (#163)
Bumps [terser](https://github.com/terser/terser) from 5.7.1 to 5.14.2.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-20 09:09:28 +02:00
d8993bead2 release 7.5.0 2022-06-18 14:30:02 +02:00
ecb5f30a7c Added to add custom properties to the decline button 2022-06-18 14:29:39 +02:00
dependabot[bot]
d1c7252546 Bump semver-regex from 3.1.3 to 3.1.4 (#158)
Bumps [semver-regex](https://github.com/sindresorhus/semver-regex) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/sindresorhus/semver-regex/releases)
- [Commits](https://github.com/sindresorhus/semver-regex/commits/v3.1.4)

---
updated-dependencies:
- dependency-name: semver-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-06 09:25:54 +02:00
602a9bfe62 release 7.4.1 2022-04-03 10:30:45 +02:00
c9a6e1f0ce release 7.4.0 2022-04-03 10:25:55 +02:00
dependabot[bot]
fefe28e288 Bump minimist from 1.2.5 to 1.2.6 (#155)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-28 11:17:52 +02:00
Abe Tomoaki
d63cb81fb7 refactor: use const instead of let. (#153) 2022-02-11 09:24:48 +01:00
e96b4fe4c0 release 7.3.1 2022-02-10 14:25:18 +01:00
Davor Peic
3d8c766911 Update index.d.ts (#151) 2022-02-10 14:22:20 +01:00
Abe Tomoaki
3c17b66191 docs(readme): update line number of link to code (#152) 2022-02-10 14:21:55 +01:00
dependabot[bot]
f5447b15a9 Bump tar from 6.1.7 to 6.1.11 (#149)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-08 17:17:14 +01:00
dependabot[bot]
8526d622a5 Bump ansi-regex from 5.0.0 to 5.0.1 (#150)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-08 17:17:06 +01:00
dependabot[bot]
8780dd12f1 Bump semver-regex from 3.1.2 to 3.1.3 (#148)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-08 17:16:58 +01:00
a0e65af081 docfix :) 2022-02-08 17:15:44 +01:00
57dbfbbbd5 release 7.3.0 2022-02-08 17:13:48 +01:00
42 changed files with 39910 additions and 6829 deletions

View File

@@ -1,8 +0,0 @@
{
"presets": ["@babel/preset-env"],
"plugins": [
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-transform-react-jsx",
"@babel/plugin-transform-object-assign"
]
}

68
.eslintrc Normal file
View File

@@ -0,0 +1,68 @@
{
"env": {
"browser": true,
"jest": true,
"es6": true
},
"plugins": ["import"],
"extends": [
"react-app",
"prettier/@typescript-eslint",
"plugin:prettier/recommended",
"eslint:recommended",
"prettier"
],
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"rules": {
"react/react-in-jsx-scope": "off",
"no-console": [
"error",
{
"allow": ["debug", "error"]
}
],
"no-eval": "error",
"import/first": "error",
"camelcase": [
"error",
{
"ignoreImports": true,
"ignoreDestructuring": true
}
],
"consistent-return": "warn",
"constructor-super": "error",
"curly": "error",
"eol-last": "warn",
"eqeqeq": ["error", "smart"],
"import/order": 1,
"new-parens": "error",
"no-debugger": "error",
"no-fallthrough": "off",
"max-len": [
"warn",
{
"code": 120
}
],
"no-shadow": [
"error",
{
"hoist": "all"
}
],
"no-trailing-spaces": "warn",
"no-underscore-dangle": "error",
"no-unsafe-finally": "error",
"no-var": "error",
"object-shorthand": "error",
"one-var": ["error", "never"],
"prefer-arrow/prefer-arrow-functions": "off",
"prefer-const": "error",
"radix": "off",
"space-in-parens": ["off", "never"]
}
}

32
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: CI
on: [push]
jobs:
build:
name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
node: ["10.x", "12.x", "14.x"]
os: [ubuntu-latest, macOS-latest]
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Use Node ${{ matrix.node }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
- name: Install deps and build (with cache)
uses: bahmutov/npm-install@v1
- name: Lint
run: yarn lint
- name: Test
run: yarn test --ci --coverage --maxWorkers=2
- name: Build
run: yarn build

12
.github/workflows/size.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
name: size
on: [pull_request]
jobs:
size:
runs-on: ubuntu-latest
env:
CI_JOB_NUMBER: 1
steps:
- uses: actions/checkout@v1
- uses: andresz1/size-limit-action@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

7
.gitignore vendored
View File

@@ -1,3 +1,6 @@
*.log
.DS_Store
.cache
dist
node_modules node_modules
build/index.js.LICENSE.txt storybook-static
example/*

6
.husky/pre-commit Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm test
npm run lint
npm run build

8
.storybook/main.js Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
stories: ['../stories/**/*.stories.@(ts|tsx|js|jsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
// https://storybook.js.org/docs/react/configure/typescript#mainjs-configuration
typescript: {
check: true, // type-check stories during Storybook build
}
};

5
.storybook/preview.js Normal file
View File

@@ -0,0 +1,5 @@
// https://storybook.js.org/docs/react/writing-stories/parameters#global-parameters
export const parameters = {
// https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args
actions: { argTypesRegex: '^on.*' },
};

View File

@@ -5,7 +5,39 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [next] ## [[8.0.1]((https://github.com/Mastermindzh/react-cookie-consent/releases/tag/8.0.1)]
~~Second try of [np](https://github.com/sindresorhus/np)...~~
Removed postinstall
np is not my thing.. reverted to old release strategy
## [[8.0.0]((https://github.com/Mastermindzh/react-cookie-consent/releases/tag/8.0.0)]
- Switched to tsdx and Typescript
- Added storybook with examples from readme
## [[7.6.0]](https://github.com/Mastermindzh/react-cookie-consent/releases/tag/7.6.0]
- Added `customButtonWrapperAttributes` prop which allows to add custom attributes to the button wrapper div
## [[7.5.0]](https://github.com/Mastermindzh/react-cookie-consent/releases/tag/7.5.0)
- Added `customDeclineButtonProps` to add custom properties to the decline button
## [[7.4.1]](https://github.com/Mastermindzh/react-cookie-consent/releases/tag/7.4.1)
- Small version error in package.json that actually didn't make it work with React 18
## [[7.4.0]](https://github.com/Mastermindzh/react-cookie-consent/releases/tag/7.4.0)
- Added support for React 18
- Updated example and codebase to use React 18
## [[7.3.1]](https://github.com/Mastermindzh/react-cookie-consent/releases/tag/7.3.1)
- Added type annotations on public functions
## [[7.3.0]](https://github.com/Mastermindzh/react-cookie-consent/releases/tag/7.3.0)
- added `customButtonProps` that allows to use custom props with the button component. Specifically useful for library buttons components, for e.g. MUI Button. - added `customButtonProps` that allows to use custom props with the button component. Specifically useful for library buttons components, for e.g. MUI Button.

108
README.md
View File

@@ -137,53 +137,55 @@ That option would be interesting if you want to allow user to change their conse
## Props ## Props
| Prop | Type | Default value | Description | | Prop | Type | Default value | Description |
| ------------------------- | :-----------------------------------------: | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | | ----------------------------- | :-----------------------------------------: | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
| location | string, "top", "bottom" or "none" | "bottom" | Syntactic sugar to easily enable you to place the bar at the top or the bottom of the browser window. Use "none" to disable. | | location | string, "top", "bottom" or "none" | "bottom" | Syntactic sugar to easily enable you to place the bar at the top or the bottom of the browser window. Use "none" to disable. |
| visible | string, "show", "hidden" or "byCookieValue" | "byCookieValue" | Force the consent bar visibility. If "byCookieValue", visibility are defined by cookie consent existence. | | visible | string, "show", "hidden" or "byCookieValue" | "byCookieValue" | Force the consent bar visibility. If "byCookieValue", visibility are defined by cookie consent existence. |
| children | string or React component | | Content to appear inside the bar | | children | string or React component | | Content to appear inside the bar |
| disableStyles | boolean | false | If enabled the component will have no default style. (you can still supply style through props) | | disableStyles | boolean | false | If enabled the component will have no default style. (you can still supply style through props) |
| hideOnAccept | boolean | true | If disabled the component will not hide it self after the accept button has been clicked. You will need to hide yourself (see onAccept) | | hideOnAccept | boolean | true | If disabled the component will not hide it self after the accept button has been clicked. You will need to hide yourself (see onAccept) |
| buttonText | string or React component | "I understand" | Text to appear on the button | | buttonText | string or React component | "I understand" | Text to appear on the button |
| declineButtonText | string or React component | "I decline" | Text to appear on the decline button | | declineButtonText | string or React component | "I decline" | Text to appear on the decline button |
| cookieName | string | "CookieConsent" | Name of the cookie used to track whether the user has agreed. | | cookieName | string | "CookieConsent" | Name of the cookie used to track whether the user has agreed. |
| cookieValue | string or boolean or number | true | Value to be saved under the cookieName. | | cookieValue | string or boolean or number | true | Value to be saved under the cookieName. |
| declineCookieValue | string or boolean or number | false | Value to be saved under the cookieName when declined. | | declineCookieValue | string or boolean or number | false | Value to be saved under the cookieName when declined. |
| setDeclineCookie | boolean | true | Whether to set a cookie when the user clicks "decline" | | setDeclineCookie | boolean | true | Whether to set a cookie when the user clicks "decline" |
| onAccept | function | `() => {}` | Function to be called after the accept button has been clicked. | | onAccept | function | `() => {}` | Function to be called after the accept button has been clicked. |
| onDecline | function | `() => {}` | Function to be called after the decline button has been clicked. | | onDecline | function | `() => {}` | Function to be called after the decline button has been clicked. |
| debug | boolean | undefined | Bar will be drawn regardless of cookie for debugging purposes. | | debug | boolean | undefined | Bar will be drawn regardless of cookie for debugging purposes. |
| expires | number | 365 | Number of days before the cookie expires. | | expires | number | 365 | Number of days before the cookie expires. |
| extraCookieOptions | object | `{}` | Extra info (apart from expiry date) to add to the cookie | | extraCookieOptions | object | `{}` | Extra info (apart from expiry date) to add to the cookie |
| overlay | boolean | false | Whether to show a page obscuring overlay or not. | | overlay | boolean | false | Whether to show a page obscuring overlay or not. |
| containerClasses | string | "" | CSS classes to apply to the surrounding container | | containerClasses | string | "" | CSS classes to apply to the surrounding container |
| buttonClasses | string | "" | CSS classes to apply to the button | | buttonClasses | string | "" | CSS classes to apply to the button |
| buttonWrapperClasses | string | "" | CSS classes to apply to the div wrapping the buttons | | buttonWrapperClasses | string | "" | CSS classes to apply to the div wrapping the buttons |
| declineButtonClasses | string | "" | CSS classes to apply to the decline button | | customButtonWrapperAttributes | `React.HTMLAttributes<HTMLDivElement>` | `{}` | Allows you to set custom (data) attributes on the button wrapper div |
| buttonId | string | "" | Id to apply to the button | | declineButtonClasses | string | "" | CSS classes to apply to the decline button |
| declineButtonId | string | "" | Id to apply to the decline button | | buttonId | string | "" | Id to apply to the button |
| contentClasses | string | "" | CSS classes to apply to the content | | declineButtonId | string | "" | Id to apply to the decline button |
| overlayClasses | string | "" | CSS classes to apply to the surrounding overlay | | contentClasses | string | "" | CSS classes to apply to the content |
| style | object | [look at source][style] | React styling object for the bar. | | overlayClasses | string | "" | CSS classes to apply to the surrounding overlay |
| buttonStyle | object | [look at source][buttonstyle] | React styling object for the button. | | style | object | [look at source][style] | React styling object for the bar. |
| declineButtonStyle | object | [look at source][declinebuttonstyle] | React styling object for the decline button. | | buttonStyle | object | [look at source][buttonstyle] | React styling object for the button. |
| contentStyle | object | [look at source][contentstyle] | React styling object for the content. | | declineButtonStyle | object | [look at source][declinebuttonstyle] | React styling object for the decline button. |
| overlayStyle | object | [look at source][overlaystyle] | React styling object for the overlay. | | contentStyle | object | [look at source][contentstyle] | React styling object for the content. |
| disableButtonStyles | boolean | false | If enabled the button will have no default style. (you can still supply style through props) | | overlayStyle | object | [look at source][overlaystyle] | React styling object for the overlay. |
| enableDeclineButton | boolean | false | If enabled the decline button will be rendered | | disableButtonStyles | boolean | false | If enabled the button will have no default style. (you can still supply style through props) |
| flipButtons | boolean | false | If enabled the accept and decline buttons will be flipped | | enableDeclineButton | boolean | false | If enabled the decline button will be rendered |
| ButtonComponent | React component | button | React Component to render as a button. | | flipButtons | boolean | false | If enabled the accept and decline buttons will be flipped |
| sameSite | string, "strict", "lax" or "none" | none | Cookies sameSite attribute value | | ButtonComponent | React component | button | React Component to render as a button. |
| cookieSecurity | boolean ¡ | undefined | Cookie security level. Defaults to true unless running on http. | | sameSite | string, "strict", "lax" or "none" | none | Cookies sameSite attribute value |
| ariaAcceptLabel | string | Accept cookies | Aria label to set on the accept button | | cookieSecurity | boolean | undefined | Cookie security level. Defaults to true unless running on http. |
| ariaDeclineLabel | string | Decline cookies | Aria label to set on the decline button | | ariaAcceptLabel | string | Accept cookies | Aria label to set on the accept button |
| acceptOnScroll | boolean | false | Defines whether "accept" should be fired after the user scrolls a certain distance (see acceptOnScrollPercentage) | | ariaDeclineLabel | string | Decline cookies | Aria label to set on the decline button |
| acceptOnScrollPercentage | number | 25 | Percentage of the page height the user has to scroll to trigger the accept function if acceptOnScroll is enabled | | acceptOnScroll | boolean | false | Defines whether "accept" should be fired after the user scrolls a certain distance (see acceptOnScrollPercentage) |
| customContentAttributes | object | `{}` | Allows you to set custom (data) attributes on the content div | | acceptOnScrollPercentage | number | 25 | Percentage of the page height the user has to scroll to trigger the accept function if acceptOnScroll is enabled |
| customContainerAttributes | object | `{}` | Allows you to set custom (data) attributes on the container div | | customContentAttributes | object | `{}` | Allows you to set custom (data) attributes on the content div |
| onOverlayClick | function | `() => {}` | allows you to react to a click on the overlay | | customContainerAttributes | object | `{}` | Allows you to set custom (data) attributes on the container div |
| acceptOnOverlayClick | boolean | false | Determines whether the cookies should be accepted after clicking on the overlay | | onOverlayClick | function | `() => {}` | allows you to react to a click on the overlay |
| customButtonProps | object | `{}` | Allows you to set custom props on the button component | | acceptOnOverlayClick | boolean | false | Determines whether the cookies should be accepted after clicking on the overlay |
| customButtonProps | object | `{}` | Allows you to set custom props on the button component |
| customDeclineButtonProps | object | `{}` | Allows you to set custom props on the decline button component |
## Debugging it ## Debugging it
@@ -205,7 +207,7 @@ So react-cookie-consent fixes this like so:
- set the fallback cookie (e.g -legacy) first, this will always succeed (on all browsers) - set the fallback cookie (e.g -legacy) first, this will always succeed (on all browsers)
- set the correct cookie second (this will work on modern browsers, fail on older ones) - set the correct cookie second (this will work on modern browsers, fail on older ones)
This happens on lines [186-192](https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L186-L192) This happens on lines [29-37](https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L29-L37)
When checking the cookie it'll do it in reverse. If the regular cookie exists, it'll use that. If no regular cookie exists it'll check whether the legacy cookie exists. If both are non-existent no consent was given. When checking the cookie it'll do it in reverse. If the regular cookie exists, it'll use that. If no regular cookie exists it'll check whether the legacy cookie exists. If both are non-existent no consent was given.
@@ -336,11 +338,11 @@ You can also generate a page-obfuscating overlay that will prevent actions other
<!-- links --> <!-- links -->
[style]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L18-L29 [style]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L78-L89
[buttonstyle]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L30-L40 [buttonstyle]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L90-L100
[declinebuttonstyle]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L41-L51 [declinebuttonstyle]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L101-L111
[contentstyle]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L52-L55 [contentstyle]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L112-L115
[overlaystyle]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L62-L69 [overlaystyle]: https://github.com/Mastermindzh/react-cookie-consent/blob/master/src/index.js#L116-L124
## Contributor information ## Contributor information

75
build/index.d.ts vendored
View File

@@ -1,75 +0,0 @@
import * as React from "react";
import Cookies from "js-cookie";
export interface CookieConsentProps {
location?: "top" | "bottom" | "none";
sameSite?: "strict" | "lax" | "none";
visible?: "hidden" | "show" | "byCookieValue";
cookieSecurity?: boolean;
style?: object;
buttonStyle?: object;
declineButtonStyle?: object;
contentStyle?: object;
children?: React.ReactNode;
disableStyles?: boolean;
hideOnAccept?: boolean;
hideOnDecline?: boolean;
onAccept?: (acceptedByScrolling?: boolean) => void;
onDecline?: Function;
buttonText?: Function | React.ReactNode;
declineButtonText?: Function | React.ReactNode;
cookieName?: string;
cookieValue?: string | boolean | number;
declineCookieValue?: string | boolean | number;
setDeclineCookie?: boolean;
debug?: boolean;
expires?: number;
containerClasses?: string;
contentClasses?: string;
buttonWrapperClasses?: string;
buttonClasses?: string;
declineButtonClasses?: string;
buttonId?: string;
declineButtonId?: string;
extraCookieOptions?: object;
disableButtonStyles?: boolean;
enableDeclineButton?: boolean;
flipButtons?: boolean;
ButtonComponent?: React.ElementType;
overlay?: boolean;
overlayClasses?: string;
overlayStyle?: object;
onOverlayClick?: () => void;
acceptOnOverlayClick?: boolean;
ariaAcceptLabel?: string;
ariaDeclineLabel?: string;
acceptOnScroll?: boolean;
acceptOnScrollPercentage?: number;
customContentAttributes?: object;
customContainerAttributes?: object;
}
export default class CookieConsent extends React.Component<CookieConsentProps, {}> {}
/**
* Returns the value of the consent cookie
* Retrieves the regular value first and if not found the legacy one according
* to: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
* @param {*} name optional name of the cookie
*/
export function getCookieConsentValue(name?: string): string;
/**
* Reset the consent cookie
* Remove the cookie on browser in order to allow user to change their consent
* @param {*} name optional name of the cookie
*/
export function resetCookieConsentValue(name?: string);
/**
* Get the legacy cookie name by the regular cookie name
* @param {string} name of cookie to get
*/
export function getLegacyCookieName(name: string);
export { Cookies };

File diff suppressed because one or more lines are too long

Submodule example deleted from bff8dd76c5

44829
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,74 +1,102 @@
{ {
"name": "react-cookie-consent", "name": "react-cookie-consent",
"author": { "version": "8.0.0",
"name": "Rick van Lieshout",
"email": "info@rickvanlieshout.com"
},
"version": "7.2.1",
"description": "A small, simple and customizable cookie consent bar for use in React applications.", "description": "A small, simple and customizable cookie consent bar for use in React applications.",
"main": "build/index.js",
"types": "build/index.d.ts",
"dependencies": {
"js-cookie": "^2.2.1",
"prop-types": "^15.7.2"
},
"peerDependencies": {
"react": "^16.13.1 || ^17.0.0"
},
"scripts": {
"build": "webpack",
"prettier": "prettier 'src/**/*.{js*,ts*,htm*,md,scss}' --write",
"patch": "npm --no-git-tag-version version patch",
"minor": "npm --no-git-tag-version version minor",
"major": "npm --no-git-tag-version version major",
"preversion": "grep \"\\[$npm_package_version\\]\" CHANGELOG.md > /dev/null || ( echo 'You need to add an entry in CHANGELOG.md for this version.' && false )",
"release": "npm run build && git add -A && git tag $npm_package_version && git commit -m \"release $npm_package_version\" && git push && git push --tags && npm publish",
"release-patch": "npm run patch && npm run release",
"release-minor": "npm run minor && npm run release",
"release-major": "npm run major && npm run release"
},
"repository": {
"type": "git",
"url": "git+https://github.com/Mastermindzh/react-cookie-consent.git"
},
"keywords": [ "keywords": [
"react", "react",
"cookie", "cookie",
"consent", "consent",
"cookiebar" "cookiebar"
], ],
"license": "MIT", "homepage": "https://github.com/Mastermindzh/react-cookie-consent#readme",
"bugs": { "bugs": {
"url": "https://github.com/Mastermindzh/react-cookie-consent/issues" "url": "https://github.com/Mastermindzh/react-cookie-consent/issues"
}, },
"homepage": "https://github.com/Mastermindzh/react-cookie-consent#readme", "repository": {
"devDependencies": { "type": "git",
"@babel/cli": "^7.12.1", "url": "git+https://github.com/Mastermindzh/react-cookie-consent.git"
"@babel/core": "^7.12.3",
"@babel/plugin-proposal-object-rest-spread": "^7.12.1",
"@babel/plugin-transform-object-assign": "^7.12.1",
"@babel/plugin-transform-react-jsx": "^7.12.1",
"@babel/preset-env": "^7.12.1",
"@mastermindzh/prettier-config": "^1.0.0",
"@types/js-cookie": "^2.2.6",
"babel-loader": "^8.1.0",
"copy-webpack-plugin": "^6.2.1",
"husky": "^4.3.0",
"lint-staged": "^10.5.1",
"prettier": "^2.1.2",
"react": "^16.13.1",
"webpack": "^5.3.2",
"webpack-cli": "^4.1.0"
}, },
"prettier": "@mastermindzh/prettier-config", "license": "MIT",
"husky": { "author": {
"hooks": { "name": "Rick van Lieshout",
"pre-commit": "lint-staged --allow-empty" "email": "info@rickvanlieshout.com"
} },
"main": "dist/index.js",
"module": "dist/react-cookie-consent.esm.js",
"typings": "dist/index.d.ts",
"files": [
"dist",
"src"
],
"scripts": {
"analyze": "size-limit --why",
"build": "tsdx build",
"build-storybook": "build-storybook",
"install-husky": "npx husky install",
"lint": "tsdx lint",
"major": "npm --no-git-tag-version version major",
"minor": "npm --no-git-tag-version version minor",
"organize": "npx format-package -w && npx sort-package-json",
"patch": "npm --no-git-tag-version version patch",
"prepare": "tsdx build",
"prettier": "prettier 'src/**/*.{js*,ts*,htm*,md,scss}' --write",
"publish": "npx np",
"release": "npm run build && git add -A && git tag $npm_package_version && git commit -m \"release $npm_package_version\" && git push && git push --tags && npm publish",
"release-major": "npm run major && npm run release",
"release-minor": "npm run minor && npm run release",
"release-patch": "npm run patch && npm run release",
"size": "size-limit",
"start": "tsdx watch",
"storybook": "start-storybook -p 6006",
"test": "tsdx test --passWithNoTests",
"preversion": "grep \"\\[$npm_package_version\\]\" CHANGELOG.md > /dev/null || ( echo 'You need to add an entry in CHANGELOG.md for this version.' && false )"
}, },
"lint-staged": { "lint-staged": {
"*.{js*,ts*,htm*,md,scss}": [ "*.{js*,ts*,htm*,md,scss}": [
"prettier --write" "prettier --write"
] ]
} },
"prettier": "@mastermindzh/prettier-config",
"dependencies": {
"js-cookie": "^2.2.1"
},
"devDependencies": {
"@emotion/react": "^11.9.3",
"@emotion/styled": "^11.9.3",
"@mastermindzh/prettier-config": "^1.0.0",
"@mui/material": "^5.9.2",
"@size-limit/preset-small-lib": "^7.0.8",
"@storybook/addon-essentials": "^6.5.9",
"@storybook/addons": "^6.5.9",
"@storybook/react": "^6.5.9",
"@types/js-cookie": "^2.2.6",
"@types/react": "^18.0.15",
"@types/react-dom": "^18.0.6",
"babel-loader": "^8.2.5",
"husky": "^8.0.1",
"prettier": "^2.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"size-limit": "^7.0.8",
"tsdx": "^0.14.1",
"tslib": "^2.4.0",
"typescript": "^3.9.10"
},
"peerDependencies": {
"react": ">=16"
},
"engines": {
"node": ">=10"
},
"size-limit": [
{
"path": "dist/react-cookie-consent.cjs.production.min.js",
"limit": "10 KB"
},
{
"path": "dist/react-cookie-consent.esm.js",
"limit": "10 KB"
}
]
} }

115
src/CookieConsent.props.tsx Normal file
View File

@@ -0,0 +1,115 @@
import React, { FunctionComponent, ReactNode } from "react";
import {
defaultCookieConsentName,
POSITION_OPTIONS,
SAME_SITE_OPTIONS,
VISIBILITY_OPTIONS,
} from "./models/constants";
export interface CookieConsentProps {
children?: ReactNode;
style: React.CSSProperties;
buttonStyle: React.CSSProperties;
declineButtonStyle: React.CSSProperties;
contentStyle: React.CSSProperties;
disableStyles: boolean;
hideOnAccept: boolean;
hideOnDecline: boolean;
onAccept: (acceptedByScrolling: boolean) => void;
onDecline: () => void;
buttonText: string | ReactNode | Function;
declineButtonText: string | ReactNode | Function;
cookieName: string;
cookieValue: string | object;
declineCookieValue: string | object;
setDeclineCookie: boolean;
debug: boolean;
expires: number;
containerClasses: string;
contentClasses: string;
buttonClasses: string;
buttonWrapperClasses: string;
declineButtonClasses: string;
buttonId: string;
declineButtonId: string;
overlayClasses: string;
ariaAcceptLabel: string;
ariaDeclineLabel: string;
disableButtonStyles: boolean;
enableDeclineButton: boolean;
flipButtons: boolean;
cookieSecurity?: boolean;
overlay: boolean;
acceptOnOverlayClick: boolean;
acceptOnScroll: boolean;
acceptOnScrollPercentage: number;
ButtonComponent: any;
extraCookieOptions: Object;
overlayStyle: Object;
customContentAttributes: Object;
customContainerAttributes: Object;
customButtonProps: Object;
customDeclineButtonProps: Object;
customButtonWrapperAttributes: Object;
onOverlayClick: () => void;
// these should be enums
location: string;
visible: string;
sameSite: "strict" | "Strict" | "lax" | "Lax" | "none" | "None" | undefined;
}
const DefaultButtonComponent: FunctionComponent<{ children: ReactNode; [x: string]: any }> = ({
children,
...props
}) => {
return <button {...props}>{children}</button>;
};
export const defaultCookieConsentProps: CookieConsentProps = {
disableStyles: false,
hideOnAccept: true,
hideOnDecline: true,
location: POSITION_OPTIONS.BOTTOM,
visible: VISIBILITY_OPTIONS.BY_COOKIE_VALUE,
onAccept: (_acceptedByScrolling) => {},
onDecline: () => {},
cookieName: defaultCookieConsentName,
cookieValue: "true",
declineCookieValue: "false",
setDeclineCookie: true,
buttonText: "I understand",
declineButtonText: "I decline",
debug: false,
expires: 365,
containerClasses: "CookieConsent",
contentClasses: "",
buttonClasses: "",
buttonWrapperClasses: "",
declineButtonClasses: "",
buttonId: "rcc-confirm-button",
declineButtonId: "rcc-decline-button",
extraCookieOptions: {},
disableButtonStyles: false,
enableDeclineButton: false,
flipButtons: false,
sameSite: SAME_SITE_OPTIONS.LAX,
ButtonComponent: DefaultButtonComponent,
overlay: false,
overlayClasses: "",
onOverlayClick: () => {},
acceptOnOverlayClick: false,
ariaAcceptLabel: "Accept cookies",
ariaDeclineLabel: "Decline cookies",
acceptOnScroll: false,
acceptOnScrollPercentage: 25,
customContentAttributes: {},
customContainerAttributes: {},
customButtonProps: {},
customDeclineButtonProps: {},
customButtonWrapperAttributes: {},
style: {},
buttonStyle: {},
declineButtonStyle: {},
contentStyle: {},
overlayStyle: {},
};

View File

@@ -0,0 +1,59 @@
export interface CookieConsentState {
visible: boolean;
style: React.CSSProperties;
buttonStyle: React.CSSProperties;
declineButtonStyle: React.CSSProperties;
contentStyle: React.CSSProperties;
overlayStyle: React.CSSProperties;
}
export const defaultState: CookieConsentState = {
visible: false,
style: {
alignItems: "baseline",
background: "#353535",
color: "white",
display: "flex",
flexWrap: "wrap",
justifyContent: "space-between",
left: "0",
position: "fixed",
width: "100%",
zIndex: "999",
},
buttonStyle: {
background: "#ffd42d",
border: "0",
borderRadius: "0px",
boxShadow: "none",
color: "black",
cursor: "pointer",
flex: "0 0 auto",
padding: "5px 10px",
margin: "15px",
},
declineButtonStyle: {
background: "#c12a2a",
border: "0",
borderRadius: "0px",
boxShadow: "none",
color: "#e5e5e5",
cursor: "pointer",
flex: "0 0 auto",
padding: "5px 10px",
margin: "15px",
},
contentStyle: {
flex: "1 0 300px",
margin: "15px",
},
overlayStyle: {
position: "fixed",
left: 0,
top: 0,
width: "100%",
height: "100%",
zIndex: "999",
backgroundColor: "rgba(0,0,0,0.3)",
},
};

301
src/CookieConsent.tsx Normal file
View File

@@ -0,0 +1,301 @@
import Cookies from "js-cookie";
import React, { Component, CSSProperties } from "react";
import { ConditionalWrapper } from "./components/ConditionalWrapper";
import { CookieConsentProps, defaultCookieConsentProps } from "./CookieConsent.props";
import { CookieConsentState, defaultState } from "./CookieConsent.state";
import { POSITION_OPTIONS, SAME_SITE_OPTIONS, VISIBILITY_OPTIONS } from "./models/constants";
import { getCookieConsentValue, getLegacyCookieName } from "./utilities";
export class CookieConsent extends Component<CookieConsentProps, CookieConsentState> {
public static defaultProps = defaultCookieConsentProps;
state: CookieConsentState = defaultState;
componentDidMount() {
const { debug } = this.props;
// if cookie undefined or debug
if (this.getCookieValue() === undefined || debug) {
this.setState({ visible: true });
// if acceptOnScroll is set to true and (cookie is undefined or debug is set to true), add a listener.
if (this.props.acceptOnScroll) {
window.addEventListener("scroll", this.handleScroll, { passive: true });
}
}
}
componentWillUnmount() {
// remove listener if still set
this.removeScrollListener();
}
/**
* Set a persistent accept cookie
*/
accept(acceptedByScrolling = false) {
const { cookieName, cookieValue, hideOnAccept, onAccept } = this.props;
this.setCookie(cookieName, cookieValue);
onAccept(acceptedByScrolling ?? false);
if (hideOnAccept) {
this.setState({ visible: false });
this.removeScrollListener();
}
}
/**
* Handle a click on the overlay
*/
overlayClick() {
const { acceptOnOverlayClick, onOverlayClick } = this.props;
if (acceptOnOverlayClick) {
this.accept();
}
onOverlayClick();
}
/**
* Set a persistent decline cookie
*/
decline() {
const { cookieName, declineCookieValue, hideOnDecline, onDecline, setDeclineCookie } =
this.props;
if (setDeclineCookie) {
this.setCookie(cookieName, declineCookieValue);
}
onDecline();
if (hideOnDecline) {
this.setState({ visible: false });
}
}
/**
* Function to set the consent cookie based on the provided variables
* Sets two cookies to handle incompatible browsers, more details:
* https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
*/
setCookie(cookieName: string, cookieValue: string | object) {
const { extraCookieOptions, expires, sameSite } = this.props;
let { cookieSecurity } = this.props;
if (cookieSecurity === undefined) {
cookieSecurity = window.location ? window.location.protocol === "https:" : true;
}
const cookieOptions = { expires, ...extraCookieOptions, sameSite, secure: cookieSecurity };
// Fallback for older browsers where can not set SameSite=None,
// SEE: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
if (sameSite === SAME_SITE_OPTIONS.NONE) {
Cookies.set(getLegacyCookieName(cookieName), cookieValue, cookieOptions);
}
// set the regular cookie
Cookies.set(cookieName, cookieValue, cookieOptions);
}
/**
* Returns the value of the consent cookie
* Retrieves the regular value first and if not found the legacy one according
* to: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
*/
getCookieValue() {
const { cookieName } = this.props;
return getCookieConsentValue(cookieName);
}
/**
* checks whether scroll has exceeded set amount and fire accept if so.
*/
handleScroll = () => {
const { acceptOnScrollPercentage } = this.props;
// (top / height) - height * 100
const rootNode = document.documentElement;
const body = document.body;
const top = "scrollTop";
const height = "scrollHeight";
const percentage =
((rootNode[top] || body[top]) /
((rootNode[height] || body[height]) - rootNode.clientHeight)) *
100;
if (percentage > acceptOnScrollPercentage) {
this.accept(true);
}
};
removeScrollListener = () => {
const { acceptOnScroll } = this.props;
if (acceptOnScroll) {
window.removeEventListener("scroll", this.handleScroll);
}
};
render() {
// If the bar shouldn't be visible don't render anything.
switch (this.props.visible) {
case VISIBILITY_OPTIONS.HIDDEN:
return null;
case VISIBILITY_OPTIONS.BY_COOKIE_VALUE:
if (!this.state.visible) {
return null;
}
break;
default:
break;
}
const {
location,
style,
buttonStyle,
declineButtonStyle,
contentStyle,
disableStyles,
buttonText,
declineButtonText,
containerClasses,
contentClasses,
buttonClasses,
buttonWrapperClasses,
declineButtonClasses,
buttonId,
declineButtonId,
disableButtonStyles,
enableDeclineButton,
flipButtons,
ButtonComponent,
overlay,
overlayClasses,
overlayStyle,
ariaAcceptLabel,
ariaDeclineLabel,
customContainerAttributes,
customContentAttributes,
customButtonProps,
customDeclineButtonProps,
customButtonWrapperAttributes,
} = this.props;
let myStyle: CSSProperties = {};
let myButtonStyle: CSSProperties = {};
let myDeclineButtonStyle: CSSProperties = {};
let myContentStyle: CSSProperties = {};
let myOverlayStyle: CSSProperties = {};
if (disableStyles) {
// if styles are disabled use the provided styles (or none)
myStyle = Object.assign({}, style);
myButtonStyle = Object.assign({}, buttonStyle);
myDeclineButtonStyle = Object.assign({}, declineButtonStyle);
myContentStyle = Object.assign({}, contentStyle);
myOverlayStyle = Object.assign({}, overlayStyle);
} else {
// if styles aren't disabled merge them with the styles that are provided (or use default styles)
myStyle = Object.assign({}, { ...this.state.style, ...style });
myContentStyle = Object.assign({}, { ...this.state.contentStyle, ...contentStyle });
myOverlayStyle = Object.assign({}, { ...this.state.overlayStyle, ...overlayStyle });
// switch to disable JUST the button styles
if (disableButtonStyles) {
myButtonStyle = Object.assign({}, buttonStyle);
myDeclineButtonStyle = Object.assign({}, declineButtonStyle);
} else {
myButtonStyle = Object.assign({}, { ...this.state.buttonStyle, ...buttonStyle });
myDeclineButtonStyle = Object.assign(
{},
{ ...this.state.declineButtonStyle, ...declineButtonStyle }
);
}
}
// syntactic sugar to enable user to easily select top / bottom
switch (location) {
case POSITION_OPTIONS.TOP:
myStyle.top = "0";
break;
case POSITION_OPTIONS.BOTTOM:
myStyle.bottom = "0";
break;
}
const buttonsToRender = [];
// add decline button
enableDeclineButton &&
buttonsToRender.push(
<ButtonComponent
key="declineButton"
style={myDeclineButtonStyle}
className={declineButtonClasses}
id={declineButtonId}
aria-label={ariaDeclineLabel}
onClick={() => {
this.decline();
}}
{...customDeclineButtonProps}
>
{declineButtonText}
</ButtonComponent>
);
// add accept button
buttonsToRender.push(
<ButtonComponent
key="acceptButton"
style={myButtonStyle}
className={buttonClasses}
id={buttonId}
aria-label={ariaAcceptLabel}
onClick={() => {
this.accept();
}}
{...customButtonProps}
>
{buttonText}
</ButtonComponent>
);
if (flipButtons) {
buttonsToRender.reverse();
}
return (
<ConditionalWrapper
condition={overlay}
wrapper={(children) => (
<div
style={myOverlayStyle}
className={overlayClasses}
onClick={() => {
this.overlayClick();
}}
>
{children}
</div>
)}
>
<div className={`${containerClasses}`} style={myStyle} {...customContainerAttributes}>
<div style={myContentStyle} className={contentClasses} {...customContentAttributes}>
{this.props.children}
</div>
<div className={`${buttonWrapperClasses}`} {...customButtonWrapperAttributes}>
{buttonsToRender.map((button) => {
return button;
})}
</div>
</div>
</ConditionalWrapper>
);
}
}
export default CookieConsent;

View File

@@ -0,0 +1,18 @@
import { FunctionComponent, ReactNode } from "react";
type Props = {
condition: boolean;
wrapper: (_: ReactNode) => any;
children: ReactNode;
};
/**
* A function to wrap elements with a "wrapper" on a condition
* @param {object} wrappingOptions
* condition == boolean condition, when to wrap
* wrapper == style to wrap. e.g <div>{children}</div>
* children == react passes whatever is between tags as children. Don't supply this yourself.
*/
export const ConditionalWrapper: FunctionComponent<Props> = ({ condition, wrapper, children }) => {
return condition ? wrapper(children) : children;
};

76
src/index.d.ts vendored
View File

@@ -1,76 +0,0 @@
import * as React from "react";
import Cookies from "js-cookie";
export interface CookieConsentProps {
location?: "top" | "bottom" | "none";
sameSite?: "strict" | "lax" | "none";
visible?: "hidden" | "show" | "byCookieValue";
cookieSecurity?: boolean;
style?: object;
buttonStyle?: object;
declineButtonStyle?: object;
contentStyle?: object;
children?: React.ReactNode;
disableStyles?: boolean;
hideOnAccept?: boolean;
hideOnDecline?: boolean;
onAccept?: (acceptedByScrolling?: boolean) => void;
onDecline?: Function;
buttonText?: Function | React.ReactNode;
declineButtonText?: Function | React.ReactNode;
cookieName?: string;
cookieValue?: string | boolean | number;
declineCookieValue?: string | boolean | number;
setDeclineCookie?: boolean;
debug?: boolean;
expires?: number;
containerClasses?: string;
contentClasses?: string;
buttonWrapperClasses?: string;
buttonClasses?: string;
declineButtonClasses?: string;
buttonId?: string;
declineButtonId?: string;
extraCookieOptions?: object;
disableButtonStyles?: boolean;
enableDeclineButton?: boolean;
flipButtons?: boolean;
ButtonComponent?: React.ElementType;
overlay?: boolean;
overlayClasses?: string;
overlayStyle?: object;
onOverlayClick?: () => void;
acceptOnOverlayClick?: boolean;
ariaAcceptLabel?: string;
ariaDeclineLabel?: string;
acceptOnScroll?: boolean;
acceptOnScrollPercentage?: number;
customContentAttributes?: object;
customContainerAttributes?: object;
customButtonProps?: object;
}
export default class CookieConsent extends React.Component<CookieConsentProps, {}> {}
/**
* Returns the value of the consent cookie
* Retrieves the regular value first and if not found the legacy one according
* to: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
* @param {*} name optional name of the cookie
*/
export function getCookieConsentValue(name?: string): string;
/**
* Reset the consent cookie
* Remove the cookie on browser in order to allow user to change their consent
* @param {*} name optional name of the cookie
*/
export function resetCookieConsentValue(name?: string);
/**
* Get the legacy cookie name by the regular cookie name
* @param {string} name of cookie to get
*/
export function getLegacyCookieName(name: string);
export { Cookies };

View File

@@ -1,503 +0,0 @@
import React, { Component } from "react";
import PropTypes from "prop-types";
import Cookies from "js-cookie";
export const OPTIONS = {
TOP: "top",
BOTTOM: "bottom",
NONE: "none",
};
export const SAME_SITE_OPTIONS = {
STRICT: "strict",
LAX: "lax",
NONE: "none",
};
export const VISIBLE_OPTIONS = {
HIDDEN: "hidden",
SHOW: "show",
BY_COOKIE_VALUE: "byCookieValue",
};
/**
* Returns the value of the consent cookie
* Retrieves the regular value first and if not found the legacy one according
* to: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
* @param {*} name optional name of the cookie
*/
export const getCookieConsentValue = (name = defaultCookieConsentName) => {
let cookieValue = Cookies.get(name);
// if the cookieValue is undefined check for the legacy cookie
if (cookieValue === undefined) {
cookieValue = Cookies.get(getLegacyCookieName(name));
}
return cookieValue;
};
/**
* Reset the consent cookie
* Remove the cookie on browser in order to allow user to change their consent
* @param {*} name optional name of the cookie
*/
export const resetCookieConsentValue = (name = defaultCookieConsentName) => {
Cookies.remove(name);
};
/**
* Get the legacy cookie name by the regular cookie name
* @param {string} name of cookie to get
*/
const getLegacyCookieName = (name) => {
return `${name}-legacy`;
};
/**
* Default name of the cookie which is set by CookieConsent
*/
const defaultCookieConsentName = "CookieConsent";
/**
* A function to wrap elements with a "wrapper" on a condition
* @param {object} wrappingOptions
* condition == boolean condition, when to wrap
* wrapper == style to wrap. e.g <div>{children}</div>
* children == react passes whatever is between tags as children. Don't supply this yourself.
*/
const ConditionalWrapper = ({ condition, wrapper, children }) => {
return condition ? wrapper(children) : children;
};
class CookieConsent extends Component {
constructor(props) {
super(props);
this.state = {
visible: false,
style: {
alignItems: "baseline",
background: "#353535",
color: "white",
display: "flex",
flexWrap: "wrap",
justifyContent: "space-between",
left: "0",
position: "fixed",
width: "100%",
zIndex: "999",
},
buttonStyle: {
background: "#ffd42d",
border: "0",
borderRadius: "0px",
boxShadow: "none",
color: "black",
cursor: "pointer",
flex: "0 0 auto",
padding: "5px 10px",
margin: "15px",
},
declineButtonStyle: {
background: "#c12a2a",
border: "0",
borderRadius: "0px",
boxShadow: "none",
color: "#e5e5e5",
cursor: "pointer",
flex: "0 0 auto",
padding: "5px 10px",
margin: "15px",
},
contentStyle: {
flex: "1 0 300px",
margin: "15px",
},
overlayStyle: {
position: "fixed",
left: 0,
top: 0,
width: "100%",
height: "100%",
zIndex: "999",
backgroundColor: "rgba(0,0,0,0.3)",
},
};
}
componentDidMount() {
const { debug } = this.props;
// if cookie undefined or debug
if (this.getCookieValue() === undefined || debug) {
this.setState({ visible: true });
// if acceptOnScroll is set to true and (cookie is undefined or debug is set to true), add a listener.
if (this.props.acceptOnScroll) {
window.addEventListener("scroll", this.handleScroll, { passive: true });
}
}
}
componentWillUnmount() {
// remove listener if still set
this.removeScrollListener();
}
/**
* Set a persistent accept cookie
*/
accept(acceptedByScrolling = false) {
const { cookieName, cookieValue, hideOnAccept, onAccept } = this.props;
this.setCookie(cookieName, cookieValue);
onAccept(acceptedByScrolling ?? false);
if (hideOnAccept) {
this.setState({ visible: false });
this.removeScrollListener();
}
}
/**
* Handle a click on the overlay
*/
overlayClick() {
const { acceptOnOverlayClick, onOverlayClick } = this.props;
if (acceptOnOverlayClick) {
this.accept();
}
onOverlayClick();
}
/**
* Set a persistent decline cookie
*/
decline() {
const { cookieName, declineCookieValue, hideOnDecline, onDecline, setDeclineCookie } =
this.props;
if (setDeclineCookie) {
this.setCookie(cookieName, declineCookieValue);
}
onDecline();
if (hideOnDecline) {
this.setState({ visible: false });
}
}
/**
* Function to set the consent cookie based on the provided variables
* Sets two cookies to handle incompatible browsers, more details:
* https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
*/
setCookie(cookieName, cookieValue) {
const { extraCookieOptions, expires, sameSite } = this.props;
let { cookieSecurity } = this.props;
if (cookieSecurity === undefined) {
cookieSecurity = location ? location.protocol === "https:" : true;
}
let cookieOptions = { expires, ...extraCookieOptions, sameSite, secure: cookieSecurity };
// Fallback for older browsers where can not set SameSite=None, SEE: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
if (sameSite === SAME_SITE_OPTIONS.NONE) {
Cookies.set(getLegacyCookieName(cookieName), cookieValue, cookieOptions);
}
// set the regular cookie
Cookies.set(cookieName, cookieValue, cookieOptions);
}
/**
* Returns the value of the consent cookie
* Retrieves the regular value first and if not found the legacy one according
* to: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
*/
getCookieValue() {
const { cookieName } = this.props;
return getCookieConsentValue(cookieName);
}
/**
* checks whether scroll has exceeded set amount and fire accept if so.
*/
handleScroll = () => {
const { acceptOnScrollPercentage } = this.props;
// (top / height) - height * 100
let rootNode = document.documentElement,
body = document.body,
top = "scrollTop",
height = "scrollHeight";
let percentage =
((rootNode[top] || body[top]) /
((rootNode[height] || body[height]) - rootNode.clientHeight)) *
100;
if (percentage > acceptOnScrollPercentage) {
this.accept(true);
}
};
removeScrollListener = () => {
const { acceptOnScroll } = this.props;
if (acceptOnScroll) {
window.removeEventListener("scroll", this.handleScroll);
}
};
render() {
// If the bar shouldn't be visible don't render anything.
switch (this.props.visible) {
case VISIBLE_OPTIONS.HIDDEN:
return null;
case VISIBLE_OPTIONS.BY_COOKIE_VALUE:
if (!this.state.visible) {
return null;
}
break;
default:
break;
}
const {
location,
style,
buttonStyle,
declineButtonStyle,
contentStyle,
disableStyles,
buttonText,
declineButtonText,
containerClasses,
contentClasses,
buttonClasses,
buttonWrapperClasses,
declineButtonClasses,
buttonId,
declineButtonId,
disableButtonStyles,
enableDeclineButton,
flipButtons,
ButtonComponent,
overlay,
overlayClasses,
overlayStyle,
ariaAcceptLabel,
ariaDeclineLabel,
customContainerAttributes,
customContentAttributes,
customButtonProps,
} = this.props;
let myStyle = {};
let myButtonStyle = {};
let myDeclineButtonStyle = {};
let myContentStyle = {};
let myOverlayStyle = {};
if (disableStyles) {
// if styles are disabled use the provided styles (or none)
myStyle = Object.assign({}, style);
myButtonStyle = Object.assign({}, buttonStyle);
myDeclineButtonStyle = Object.assign({}, declineButtonStyle);
myContentStyle = Object.assign({}, contentStyle);
myOverlayStyle = Object.assign({}, overlayStyle);
} else {
// if styles aren't disabled merge them with the styles that are provided (or use default styles)
myStyle = Object.assign({}, { ...this.state.style, ...style });
myContentStyle = Object.assign({}, { ...this.state.contentStyle, ...contentStyle });
myOverlayStyle = Object.assign({}, { ...this.state.overlayStyle, ...overlayStyle });
// switch to disable JUST the button styles
if (disableButtonStyles) {
myButtonStyle = Object.assign({}, buttonStyle);
myDeclineButtonStyle = Object.assign({}, declineButtonStyle);
} else {
myButtonStyle = Object.assign({}, { ...this.state.buttonStyle, ...buttonStyle });
myDeclineButtonStyle = Object.assign(
{},
{ ...this.state.declineButtonStyle, ...declineButtonStyle }
);
}
}
// syntactic sugar to enable user to easily select top / bottom
switch (location) {
case OPTIONS.TOP:
myStyle.top = "0";
break;
case OPTIONS.BOTTOM:
myStyle.bottom = "0";
break;
}
const buttonsToRender = [];
// add decline button
enableDeclineButton &&
buttonsToRender.push(
<ButtonComponent
key="declineButton"
style={myDeclineButtonStyle}
className={declineButtonClasses}
id={declineButtonId}
aria-label={ariaDeclineLabel}
onClick={() => {
this.decline();
}}
>
{declineButtonText}
</ButtonComponent>
);
// add accept button
buttonsToRender.push(
<ButtonComponent
{...customButtonProps}
key="acceptButton"
style={myButtonStyle}
className={buttonClasses}
id={buttonId}
aria-label={ariaAcceptLabel}
onClick={() => {
this.accept();
}}
>
{buttonText}
</ButtonComponent>
);
if (flipButtons) {
buttonsToRender.reverse();
}
return (
<ConditionalWrapper
condition={overlay}
wrapper={(children) => (
<div
style={myOverlayStyle}
className={overlayClasses}
onClick={() => {
this.overlayClick();
}}
>
{children}
</div>
)}
>
<div className={`${containerClasses}`} style={myStyle} {...customContainerAttributes}>
<div style={myContentStyle} className={contentClasses} {...customContentAttributes}>
{this.props.children}
</div>
<div className={`${buttonWrapperClasses}`}>
{buttonsToRender.map((button) => {
return button;
})}
</div>
</div>
</ConditionalWrapper>
);
}
}
CookieConsent.propTypes = {
location: PropTypes.oneOf(Object.keys(OPTIONS).map((key) => OPTIONS[key])),
visible: PropTypes.oneOf(Object.keys(VISIBLE_OPTIONS).map((key) => VISIBLE_OPTIONS[key])),
sameSite: PropTypes.oneOf(Object.keys(SAME_SITE_OPTIONS).map((key) => SAME_SITE_OPTIONS[key])),
style: PropTypes.object,
buttonStyle: PropTypes.object,
declineButtonStyle: PropTypes.object,
contentStyle: PropTypes.object,
children: PropTypes.any, // eslint-disable-line react/forbid-prop-types
disableStyles: PropTypes.bool,
hideOnAccept: PropTypes.bool,
hideOnDecline: PropTypes.bool,
onAccept: PropTypes.func,
onDecline: PropTypes.func,
buttonText: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.element]),
declineButtonText: PropTypes.oneOfType([PropTypes.string, PropTypes.func, PropTypes.element]),
cookieName: PropTypes.string,
cookieValue: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.number]),
declineCookieValue: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.number]),
setDeclineCookie: PropTypes.bool,
debug: PropTypes.bool,
expires: PropTypes.number,
containerClasses: PropTypes.string,
contentClasses: PropTypes.string,
buttonClasses: PropTypes.string,
buttonWrapperClasses: PropTypes.string,
declineButtonClasses: PropTypes.string,
buttonId: PropTypes.string,
declineButtonId: PropTypes.string,
extraCookieOptions: PropTypes.object,
disableButtonStyles: PropTypes.bool,
enableDeclineButton: PropTypes.bool,
flipButtons: PropTypes.bool,
ButtonComponent: PropTypes.elementType,
cookieSecurity: PropTypes.bool,
overlay: PropTypes.bool,
overlayClasses: PropTypes.string,
overlayStyle: PropTypes.object,
onOverlayClick: PropTypes.func,
acceptOnOverlayClick: PropTypes.bool,
ariaAcceptLabel: PropTypes.string,
ariaDeclineLabel: PropTypes.string,
acceptOnScroll: PropTypes.bool,
acceptOnScrollPercentage: PropTypes.number,
customContentAttributes: PropTypes.object,
customContainerAttributes: PropTypes.object,
customButtonProps: PropTypes.object,
};
CookieConsent.defaultProps = {
disableStyles: false,
hideOnAccept: true,
hideOnDecline: true,
location: OPTIONS.BOTTOM,
visible: VISIBLE_OPTIONS.BY_COOKIE_VALUE,
onAccept: () => {},
onDecline: () => {},
cookieName: defaultCookieConsentName,
cookieValue: true,
declineCookieValue: false,
setDeclineCookie: true,
buttonText: "I understand",
declineButtonText: "I decline",
debug: false,
expires: 365,
containerClasses: "CookieConsent",
contentClasses: "",
buttonClasses: "",
buttonWrapperClasses: "",
declineButtonClasses: "",
buttonId: "rcc-confirm-button",
declineButtonId: "rcc-decline-button",
extraCookieOptions: {},
disableButtonStyles: false,
enableDeclineButton: false,
flipButtons: false,
sameSite: SAME_SITE_OPTIONS.LAX,
ButtonComponent: ({ children, ...props }) => <button {...props}>{children}</button>,
overlay: false,
overlayClasses: "",
onOverlayClick: () => {},
acceptOnOverlayClick: false,
ariaAcceptLabel: "Accept cookies",
ariaDeclineLabel: "Decline cookies",
acceptOnScroll: false,
acceptOnScrollPercentage: 25,
customContentAttributes: {},
customContainerAttributes: {},
customButtonProps: {},
};
export default CookieConsent;
export { Cookies };

11
src/index.tsx Normal file
View File

@@ -0,0 +1,11 @@
import Cookies from "js-cookie";
import { ConditionalWrapper } from "./components/ConditionalWrapper";
import { CookieConsent } from "./CookieConsent";
import { POSITION_OPTIONS, VISIBILITY_OPTIONS } from "./models/constants";
export * from "./models/constants";
export * from "./utilities";
export { Cookies, CookieConsent, ConditionalWrapper };
// backwards compatibility exports
export { POSITION_OPTIONS as OPTIONS, VISIBILITY_OPTIONS as VISIBLE_OPTIONS };
export default CookieConsent;

View File

@@ -0,0 +1 @@
export const defaultCookieConsentName = "CookieConsent";

View File

@@ -0,0 +1,4 @@
export * from "./positionOptions";
export * from "./sameSiteOptions";
export * from "./visibilityOptions";
export * from "./defaultCookieName";

View File

@@ -0,0 +1,5 @@
export const POSITION_OPTIONS = {
TOP: "top",
BOTTOM: "bottom",
NONE: "none",
};

View File

@@ -0,0 +1,5 @@
export enum SAME_SITE_OPTIONS {
STRICT = "strict",
LAX = "lax",
NONE = "none",
}

View File

@@ -0,0 +1,5 @@
export const VISIBILITY_OPTIONS = {
HIDDEN: "hidden",
SHOW: "show",
BY_COOKIE_VALUE: "byCookieValue",
};

35
src/utilities.ts Normal file
View File

@@ -0,0 +1,35 @@
import Cookies from "js-cookie";
import { defaultCookieConsentName } from "./models/constants";
/**
* Returns the value of the consent cookie
* Retrieves the regular value first and if not found the legacy one according
* to: https://web.dev/samesite-cookie-recipes/#handling-incompatible-clients
* @param {*} name optional name of the cookie
*/
export const getCookieConsentValue = (name = defaultCookieConsentName) => {
const cookieValue = Cookies.get(name);
// if the cookieValue is undefined check for the legacy cookie
if (cookieValue === undefined) {
return Cookies.get(getLegacyCookieName(name));
}
return cookieValue;
};
/**
* Reset the consent cookie
* Remove the cookie on browser in order to allow user to change their consent
* @param {*} name optional name of the cookie
*/
export const resetCookieConsentValue = (name = defaultCookieConsentName) => {
Cookies.remove(name);
};
/**
* Get the legacy cookie name by the regular cookie name
* @param {string} name of cookie to get
*/
export const getLegacyCookieName = (name: string) => {
return `${name}-legacy`;
};

View File

@@ -0,0 +1,11 @@
import { FunctionComponent } from "react";
type Props = {};
export const Intro: FunctionComponent<Props> = () => {
return (
<h1>
<p>Debug is turned on for all stories so that the bar always shows up</p>
</h1>
);
};

View File

@@ -0,0 +1,3 @@
import { CookieConsentProps, defaultCookieConsentProps } from "../../src/CookieConsent.props";
export const defaultStoryProps: CookieConsentProps = { ...defaultCookieConsentProps, debug: true };

View File

@@ -0,0 +1,13 @@
import { Story } from "@storybook/react";
import CookieConsent from "../../src";
import { Intro } from "./intro";
export const DefaultTemplate: Story<any> = (args) => (
<>
<Intro />
<CookieConsent {...args}>
This website uses cookies to enhance the user experience.
<span style={{ fontSize: "10px" }}>This bit of text is smaller :O</span>
</CookieConsent>
</>
);

34
stories/index.stories.tsx Normal file
View File

@@ -0,0 +1,34 @@
import { Meta } from "@storybook/react";
import { CookieConsent } from "../src";
import { defaultStoryProps } from "./defaults/storyProps";
import { DefaultTemplate } from "./defaults/template";
const meta: Meta = {
title: "CookieConsent",
component: CookieConsent,
argTypes: {
children: {
control: {
type: "text",
},
},
},
parameters: {
controls: { expanded: true },
},
};
export default meta;
export const Default = DefaultTemplate.bind({});
Default.args = defaultStoryProps;
// stories
export * from "./stories/acceptOnScroll.story";
export * from "./stories/additionalButtons.story";
export * from "./stories/customStyling.story";
export * from "./stories/flippedButtons.story";
export * from "./stories/muiButtons.story";
export * from "./stories/onAccept.story";
export * from "./stories/overlay.story";
export * from "./stories/rainbows.story";

View File

@@ -0,0 +1,34 @@
import { Story } from "@storybook/react";
import CookieConsent from "../../src";
import { CookieConsentProps } from "../../src/CookieConsent.props";
import { Intro } from "../defaults/intro";
import { defaultStoryProps } from "../defaults/storyProps";
const AcceptOnScrollTemplate: Story<any> = (args) => (
<>
<Intro />
{Array.from(Array(25).keys()).map((_something) => (
<p>
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Voluptates ipsa sequi soluta
mollitia illum, hic quaerat ipsum sint odit delectus magni neque sunt adipisci culpa harum
aut distinctio quisquam ab!
</p>
))}
<CookieConsent {...args}>
Scroll for {args.acceptOnScrollPercentage}% and the onAccept will trigger
</CookieConsent>
</>
);
const AcceptOnScroll = AcceptOnScrollTemplate.bind({});
AcceptOnScroll.args = {
...defaultStoryProps,
onAccept: (acceptedByScrolling) => {
alert(`ACCEPTED! By scrolling? ${JSON.stringify(acceptedByScrolling)}`);
},
acceptOnScroll: true,
acceptOnScrollPercentage: 25,
} as CookieConsentProps;
export { AcceptOnScroll };

View File

@@ -0,0 +1,17 @@
import { Story } from "@storybook/react";
import CookieConsent from "../../src";
import { Intro } from "../defaults/intro";
import { defaultStoryProps } from "../defaults/storyProps";
const AdditionalButtonsTemplate: Story<any> = (args) => (
<>
<Intro />
<CookieConsent {...args}>
<button style={{ color: "gray" }}>I am a custom config button</button>
<span style={{ fontSize: "10px" }}>This bit of text is smaller :O</span>
</CookieConsent>
</>
);
const AdditionalButtons = AdditionalButtonsTemplate.bind({});
AdditionalButtons.args = defaultStoryProps;
export { AdditionalButtons };

View File

@@ -0,0 +1,12 @@
import { CookieConsentProps } from "../../src/CookieConsent.props";
import { defaultStoryProps } from "../defaults/storyProps";
import { DefaultTemplate } from "../defaults/template";
const CustomStyling = DefaultTemplate.bind({});
CustomStyling.args = {
...defaultStoryProps,
style: { background: "red" },
buttonStyle: { fontWeight: "bold" },
} as CookieConsentProps;
export { CustomStyling };

View File

@@ -0,0 +1,12 @@
import { CookieConsentProps } from "../../src/CookieConsent.props";
import { defaultStoryProps } from "../defaults/storyProps";
import { DefaultTemplate } from "../defaults/template";
const FlippedButtons = DefaultTemplate.bind({});
FlippedButtons.args = {
...defaultStoryProps,
flipButtons: true,
enableDeclineButton: true,
} as CookieConsentProps;
export { FlippedButtons };

View File

@@ -0,0 +1,14 @@
import { Button } from "@mui/material";
import { CookieConsentProps } from "../../src/CookieConsent.props";
import { defaultStoryProps } from "../defaults/storyProps";
import { DefaultTemplate } from "../defaults/template";
const CustomMuiButton = DefaultTemplate.bind({});
CustomMuiButton.args = {
...defaultStoryProps,
disableButtonStyles: true,
ButtonComponent: Button,
customButtonProps: { variant: "contained", style: { marginRight: "10px" } },
} as CookieConsentProps;
export { CustomMuiButton };

View File

@@ -0,0 +1,13 @@
import { CookieConsentProps } from "../../src/CookieConsent.props";
import { defaultStoryProps } from "../defaults/storyProps";
import { DefaultTemplate } from "../defaults/template";
const CustomOnAccept = DefaultTemplate.bind({});
CustomOnAccept.args = {
...defaultStoryProps,
onAccept: (acceptedByScrolling) => {
alert(`ACCEPTED! By scrolling? ${JSON.stringify(acceptedByScrolling)}`);
},
} as CookieConsentProps;
export { CustomOnAccept };

View File

@@ -0,0 +1,11 @@
import { CookieConsentProps } from "../../src/CookieConsent.props";
import { defaultStoryProps } from "../defaults/storyProps";
import { DefaultTemplate } from "../defaults/template";
const Overlay = DefaultTemplate.bind({});
Overlay.args = {
...defaultStoryProps,
overlay: true,
} as CookieConsentProps;
export { Overlay };

View File

@@ -0,0 +1,22 @@
import { CookieConsentProps } from "../../src/CookieConsent.props";
import { defaultStoryProps } from "../defaults/storyProps";
import { DefaultTemplate } from "../defaults/template";
const Rainbows = DefaultTemplate.bind({});
Rainbows.args = {
...defaultStoryProps,
buttonText: "OMG DOUBLE RAINBOW",
style: {
background: "linear-gradient(to right, orange , yellow, green, cyan, blue, violet)",
textShadow: "2px 2px black",
},
buttonStyle: {
background: "linear-gradient(to left, orange , yellow, green, cyan, blue, violet)",
color: "white",
fontWeight: "bolder",
textShadow: "2px 2px black",
},
} as CookieConsentProps;
export { Rainbows };

35
tsconfig.json Normal file
View File

@@ -0,0 +1,35 @@
{
// see https://www.typescriptlang.org/tsconfig to better understand tsconfigs
"include": ["src", "types"],
"compilerOptions": {
"module": "esnext",
"lib": ["dom", "esnext"],
"importHelpers": true,
// output .d.ts declaration files for consumers
"declaration": true,
// output .js.map sourcemap files for consumers
"sourceMap": true,
// match output dir to input dir. e.g. dist/index instead of dist/src/index
"rootDir": "./src",
// stricter type-checking for stronger correctness. Recommended by TS
"strict": true,
// linter checks for common issues
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
// noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative
"noUnusedLocals": true,
"noUnusedParameters": true,
// use Node's module resolution algorithm, instead of the legacy TS one
"moduleResolution": "node",
// transpile JSX to React.createElement
"jsx": "react",
// interop between ESM and CJS modules. Recommended by TS
"esModuleInterop": true,
// significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS
"skipLibCheck": true,
// error out if import and file system have a casing mismatch. Recommended by TS
"forceConsistentCasingInFileNames": true,
// `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc`
"noEmit": true,
}
}

View File

@@ -1,37 +0,0 @@
var path = require("path");
var CopyWebpackPlugin = require("copy-webpack-plugin");
module.exports = {
entry: "./src/index.js",
output: {
path: path.resolve(__dirname, "build"),
filename: "index.js",
library: {
name: "ReactCookieConsent",
type: "umd",
},
environment: {
arrowFunction: false, // the generated runtime-code should not use arrow functions
},
globalObject: `typeof self !== 'undefined' ? self : this`,
},
module: {
rules: [
{
test: /\.js$/,
include: path.resolve(__dirname, "src"),
exclude: /(node_modules|build)/,
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
externals: {
react: "commonjs react", // this line is just to use the React dependency of our parent-testing-project instead of using our own React.
},
plugins: [new CopyWebpackPlugin({ patterns: [{ from: "src/index.d.ts", to: "index.d.ts" }] })],
};