Skip to content

Commit

Permalink
Enhance useBiteConsent with theme support
Browse files Browse the repository at this point in the history
This commit adds theme support to the useBiteConsent hook, allowing users to
specify a theme object containing color configurations. It updates the
implementation to use the theme colors for the consent banner.
  • Loading branch information
Seishin committed May 2, 2024
1 parent ce5c2f4 commit af9eefd
Show file tree
Hide file tree
Showing 5 changed files with 108 additions and 26 deletions.
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Bite Consent

Bite Consent is a lightweight React library for managing cookie consent banners with customizable descriptions and actions. It provides an easy way to implement cookie consent functionality in your React applications while ensuring compliance with privacy regulations.
Bite Consent is a lightweight React library for managing cookie consent views with customizable descriptions and actions. It provides an easy way to implement cookie consent functionality in your React applications while ensuring compliance with privacy regulations.

![Screenshot 2024-04-27 at 18 43 55](https://github.com/Seishin/bite-consent/assets/324076/a4df7470-86b4-49a8-89be-73549c410695)

Expand Down Expand Up @@ -35,7 +35,10 @@ import { useBiteConsent } from 'bite-consent'

function App() {
const { consentCookie, show, revoke } = useBiteConsent({
privacyPolicyUrl: 'https://example.com/privacy'
privacyPolicyUrl: 'https://example.com/privacy',
theme: {
mode: 'auto'
}
})

return (
Expand All @@ -61,7 +64,7 @@ export default App
| `visibility` | `auto` \| `visible` \| `hidden` | `auto` | Specifies the visibility of the consent banner. `auto` displays the view based on the availability of the consent cookie, whereas `visible` displays and `hidden` hides it. |
| `position` | `Position` \| `CustomPosition` | `bottom-left` | Sets the position of the consent view on the page. See below for details. |
| `cookieConfig` | `CookieConfig` | - | Configuration options for consent cookies. See below for details. |
| `themeMode` | `ThemeMode` | `auto` | Sets the theme mode. When `auto` is passed, the widget will automatically observe the theme mode. |
| `theme` | `Theme` | - | Theme object containing color configurations for the consent view. See below for details. |
| `onAccept` | `function` | `() => void` | A function to be called when the user accepts the cookie policy. |

### Position
Expand Down Expand Up @@ -94,6 +97,28 @@ export default App
| `secure` | `boolean` | Indicates if the cookie should only be sent over secure connections. |
| `sameSite` | `Strict` \| `Lax` \| `None` | The SameSite attribute of the consent cookie. |

### Theme

The `theme` object allows customization of the colors used in the consent banner.

| Property | Type | Description |
| -------- | ----------- | ------------------------------------ |
| `mode` | `ThemeMode` | The theme mode. |
| `light` | `ColorSet` | Color configurations for light mode. |
| `dark` | `ColorSet` | Color configurations for dark mode. |

ColorSet Properties:

- `background`: Background color of the consent banner.
- `text`: Text color of the consent banner.
- `shadow`: Box shadow of the consent banner.
- `primaryActionBackground`: Background color of primary action buttons.
- `primaryActionHoverBackground`: Background color of primary action buttons on hover.
- `primaryActionText`: Text color of primary action buttons.
- `secondaryActionBackground`: Background color of secondary action buttons.
- `secondaryActionText`: Text color of secondary action buttons.
- `secondaryActionHoverBackground`: Background color of secondary action buttons on hover.

## License

Bite Consent is released under the MIT License.
14 changes: 12 additions & 2 deletions example/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import React, { useEffect } from 'react'

function App() {
const { consentCookie, show, revoke } = useBiteConsent({
privacyPolicyUrl: 'https://example.com/privacy'
privacyPolicyUrl: 'https://example.com/privacy',
theme: {
mode: 'auto'
}
})

useEffect(() => {
Expand All @@ -13,7 +16,14 @@ function App() {
return (
<div
className="App"
style={{ height: '100vh', display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}
style={{
height: '100vh',
backgroundColor: '#000',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center'
}}
>
<h2>Cookie Consent</h2>
<p>{consentCookie ?? 'undefined'}</p>
Expand Down
20 changes: 13 additions & 7 deletions src/BiteConsent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useEffect } from 'react'
import CookieConfig from './CookieConfig'
import { Cookies } from './Illustrations'
import Position, { CustomPosition, isCustomPosition } from './Position'
import { useTheme } from './ThemeContext'

const CONSENT_COOKIE_NAME = 'cookie_consent'

Expand All @@ -17,6 +18,9 @@ interface Props {

const BiteConsent = ({ privacyPolicyUrl, text, visibility = 'auto', position = 'bottom-left', cookieConfig, onAccept }: Props) => {
const [visible, setVisible] = React.useState<boolean | undefined>()
const theme = useTheme()

const colorSet = theme.mode === 'light' ? theme.light : theme.dark

useEffect(() => {
if (typeof window === 'undefined' || typeof document === 'undefined') return
Expand Down Expand Up @@ -93,8 +97,9 @@ const BiteConsent = ({ privacyPolicyUrl, text, visibility = 'auto', position = '
position: 'fixed',
...getPosition(),
zIndex: 9999,
backgroundColor: '#ffffff',
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
backgroundColor: colorSet.background,
color: colorSet.text,
boxShadow: colorSet.shadow,
padding: '1rem',
width: '18rem',
maxHeight: '18rem',
Expand Down Expand Up @@ -132,12 +137,13 @@ const BiteConsent = ({ privacyPolicyUrl, text, visibility = 'auto', position = '
<motion.button
whileHover={{
scale: 1.05,
backgroundColor: '#f0f0f0'
backgroundColor: colorSet.secondaryActionHoverBackground
}}
whileTap={{ scale: 0.95 }}
style={{
flex: 1,
backgroundColor: '#ffffff',
color: colorSet.secondaryActionText,
backgroundColor: colorSet.secondaryActionBackground,
borderStyle: 'none',
borderRadius: '0.65rem',
fontWeight: 700,
Expand All @@ -148,13 +154,13 @@ const BiteConsent = ({ privacyPolicyUrl, text, visibility = 'auto', position = '
Privacy Policy
</motion.button>
<motion.button
whileHover={{ scale: 1.05 }}
whileHover={{ scale: 1.05, backgroundColor: colorSet.primaryActionHoverBackground }}
whileTap={{ scale: 0.95 }}
style={{
flex: 1,
borderStyle: 'none',
backgroundColor: '#38bdf8',
color: '#ffffff',
backgroundColor: colorSet.primaryActionBackground,
color: colorSet.primaryActionText,
fontWeight: 700,
padding: '0.65rem',
borderRadius: '0.65rem',
Expand Down
63 changes: 52 additions & 11 deletions src/ThemeContext.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,76 @@
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react'

type ThemeMode = 'auto' | 'light' | 'dark'

type ThemeContextData = {
type ColorSet = {
background?: string
text?: string
shadow?: string
primaryActionBackground?: string
primaryActionHoverBackground?: string
primaryActionText?: string
secondaryActionBackground?: string
secondaryActionText?: string
secondaryActionHoverBackground?: string
}
type Theme = {
mode: ThemeMode
light: ColorSet
dark: ColorSet
}

const ThemeContext = createContext({} as ThemeContextData)
const defaultTheme = {
mode: 'auto',
light: {
background: '#fff',
text: '#000',
shadow: '0 0 10px rgba(0, 0, 0, 0.1)',
primaryActionBackground: '#38bdf8',
primaryActionHoverBackground: '#38bdf8',
primaryActionText: '#fff',
secondaryActionBackground: '#ffffff',
secondaryActionText: '#000000',
secondaryActionHoverBackground: '#f0f0f0'
},
dark: {
background: '#262626',
text: '#ffffff',
shadow: '0 0 10px rgba(255, 255, 255, 0.1)',
primaryActionBackground: '#38bdf8',
primaryActionHoverBackground: '#38bdf8',
primaryActionText: '#ffffff',
secondaryActionBackground: '#2c2c2c',
secondaryActionText: '#ffffff',
secondaryActionHoverBackground: '#333333'
}
} as Theme
const ThemeContext = createContext(defaultTheme)

const ThemeProvider = ({ mode: presetMode, children }: { mode: ThemeMode; children: ReactNode }) => {
const [mode, setMode] = useState<ThemeMode>(presetMode ?? 'auto')
const ThemeProvider = ({ theme: providedTheme, children }: { theme?: Theme; children: ReactNode }) => {
const [theme, setTheme] = useState<Theme>({
mode: providedTheme?.mode ?? 'auto',
light: { ...defaultTheme.light, ...providedTheme?.light },
dark: { ...defaultTheme.dark, ...providedTheme?.dark }
})

useEffect(() => {
if (typeof window === 'undefined' || typeof document === 'undefined' || mode !== 'auto') return
if (typeof window === 'undefined' || typeof document === 'undefined' || theme.mode !== 'auto') return

const themeQuery = window.matchMedia('(prefers-color-scheme: dark)')
themeQuery.addEventListener('change', (e) => {
setMode(e.matches ? 'dark' : 'light')
setTheme({ ...theme, mode: e.matches ? 'dark' : 'light' })
})

return () => {
themeQuery.removeEventListener('change', (e) => {
setMode(e.matches ? 'dark' : 'light')
setTheme({ ...theme, mode: e.matches ? 'dark' : 'light' })
})
}
}, [setMode])
}, [setTheme])

return <ThemeContext.Provider value={{ mode }}>{children}</ThemeContext.Provider>
return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>
}

const useTheme = () => useContext(ThemeContext)

export default ThemeProvider
export { ThemeMode as Theme, useTheme }
export { Theme, useTheme }
6 changes: 3 additions & 3 deletions src/useBiteConsent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ type BiteConsentOptions = {
visibility?: 'auto' | 'visible' | 'hidden'
position?: Position | CustomPosition
cookieConfig?: CookieConfig
themeMode?: Theme
theme?: Theme
onAccept?: () => void
}

const useBiteConsent = (options: BiteConsentOptions) => {
const { privacyPolicyUrl, text, visibility, position, cookieConfig, themeMode, onAccept } = options
const { privacyPolicyUrl, text, visibility, position, cookieConfig, theme, onAccept } = options

const consentCookie = document.cookie.split(';').find((cookie) => cookie.trim().startsWith(cookieConfig?.name ?? CONSENT_COOKIE_NAME))

Expand All @@ -46,7 +46,7 @@ const useBiteConsent = (options: BiteConsentOptions) => {
const shadowRoot = root.attachShadow({ mode: 'open' })

createRoot(shadowRoot).render(
<ThemeProvider mode={themeMode ?? 'auto'}>
<ThemeProvider theme={theme}>
<BiteConsent
privacyPolicyUrl={privacyPolicyUrl}
text={text}
Expand Down

0 comments on commit af9eefd

Please sign in to comment.