|
18 | 18 | * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | 19 | */ |
20 | 20 |
|
| 21 | +import createCache from '@emotion/cache'; |
| 22 | +import { CacheProvider } from '@emotion/react'; |
21 | 23 | import { HeadlessMantineProvider } from '@mantine/core'; |
22 | | -import { PropsWithChildren } from 'react'; |
| 24 | +import { PropsWithChildren, useMemo } from 'react'; |
23 | 25 | import { useIntl } from 'react-intl'; |
24 | 26 | import { Toaster as ToastContainer } from 'sonner'; |
25 | 27 | import { ToastGlobalStyles } from '~common/components/Toast'; |
26 | 28 | import { TooltipProvider, TooltipProviderProps, TypographyGlobalStyles } from '..'; |
27 | 29 | import { SelectGlobalStyles } from '../select/SelectCommons'; |
| 30 | +import { NonceContext } from './NonceContext'; |
28 | 31 |
|
29 | 32 | export interface EchoesProviderProps { |
| 33 | + /** |
| 34 | + * A nonce value for inline styles (Content Security Policy - CSP) (optional). |
| 35 | + * When provided, this nonce will be: |
| 36 | + * - Applied to Emotion's CSS-in-JS style tags |
| 37 | + * - Made available to components like Spotlight that need it for inline styles |
| 38 | + * - Used to comply with strict Content Security Policy requirements |
| 39 | + * |
| 40 | + * This should be set once at the application root and will automatically |
| 41 | + * propagate to all Echoes components that require it. |
| 42 | + */ |
| 43 | + nonce?: string; |
30 | 44 | /** |
31 | 45 | * Custom class name for all the toasts (optional). |
32 | 46 | */ |
@@ -85,30 +99,57 @@ export interface EchoesProviderProps { |
85 | 99 | * ); |
86 | 100 | * } |
87 | 101 | * ``` |
| 102 | + * |
| 103 | + * **Content Security Policy (CSP) Support** |
| 104 | + * |
| 105 | + * If your application uses a strict Content Security Policy, you can provide a nonce |
| 106 | + * to enable inline styles required by Echoes components: |
| 107 | + * |
| 108 | + * ```tsx |
| 109 | + * function App() { |
| 110 | + * // Get nonce from meta tag or server context |
| 111 | + * const nonce = document.querySelector('meta[name="csp-nonce"]')?.getAttribute('content'); |
| 112 | + * |
| 113 | + * return ( |
| 114 | + * <EchoesProvider nonce={nonce}> |
| 115 | + * {children} |
| 116 | + * </EchoesProvider> |
| 117 | + * ); |
| 118 | + * } |
| 119 | + * ``` |
88 | 120 | */ |
89 | 121 | export function EchoesProvider(props: PropsWithChildren<EchoesProviderProps>) { |
90 | | - const { children, tooltipsDelayDuration, toastsClassName, toastsVisibleNb = 5 } = props; |
| 122 | + const { children, nonce, tooltipsDelayDuration, toastsClassName, toastsVisibleNb = 5 } = props; |
91 | 123 | const intl = useIntl(); |
92 | 124 |
|
| 125 | + // Create Emotion cache with nonce support for CSP compliance |
| 126 | + const emotionCache = useMemo(() => { |
| 127 | + const cache = createCache({ key: 'echoes', nonce }); |
| 128 | + cache.compat = true; |
| 129 | + return cache; |
| 130 | + }, [nonce]); |
| 131 | + |
93 | 132 | return ( |
94 | | - <> |
95 | | - <TypographyGlobalStyles /> |
96 | | - <SelectGlobalStyles /> |
97 | | - <ToastGlobalStyles /> |
98 | | - <TooltipProvider delayDuration={tooltipsDelayDuration}> |
99 | | - <HeadlessMantineProvider>{children}</HeadlessMantineProvider> |
100 | | - <ToastContainer |
101 | | - containerAriaLabel={intl.formatMessage({ |
102 | | - id: 'toasts.keyboard_shortcut_aria_label', |
103 | | - defaultMessage: 'Focus toasts messages with', |
104 | | - description: 'ARIA-label for the toasts container keyboard shortcut', |
105 | | - })} |
106 | | - position="bottom-right" |
107 | | - toastOptions={{ className: toastsClassName }} |
108 | | - visibleToasts={toastsVisibleNb} |
109 | | - /> |
110 | | - </TooltipProvider> |
111 | | - </> |
| 133 | + <CacheProvider value={emotionCache}> |
| 134 | + <NonceContext.Provider value={nonce}> |
| 135 | + <TypographyGlobalStyles /> |
| 136 | + <SelectGlobalStyles /> |
| 137 | + <ToastGlobalStyles /> |
| 138 | + <TooltipProvider delayDuration={tooltipsDelayDuration}> |
| 139 | + <HeadlessMantineProvider>{children}</HeadlessMantineProvider> |
| 140 | + <ToastContainer |
| 141 | + containerAriaLabel={intl.formatMessage({ |
| 142 | + id: 'toasts.keyboard_shortcut_aria_label', |
| 143 | + defaultMessage: 'Focus toasts messages with', |
| 144 | + description: 'ARIA-label for the toasts container keyboard shortcut', |
| 145 | + })} |
| 146 | + position="bottom-right" |
| 147 | + toastOptions={{ className: toastsClassName }} |
| 148 | + visibleToasts={toastsVisibleNb} |
| 149 | + /> |
| 150 | + </TooltipProvider> |
| 151 | + </NonceContext.Provider> |
| 152 | + </CacheProvider> |
112 | 153 | ); |
113 | 154 | } |
114 | 155 |
|
|
0 commit comments