@griffel/react 1.7.1 → 1.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/.storybook/main.js +20 -0
  2. package/.storybook/preview.js +1 -0
  3. package/CHANGELOG.json +1281 -0
  4. package/CHANGELOG.md +501 -0
  5. package/README.md +4 -0
  6. package/bundle-size/__css.fixture.js +7 -0
  7. package/bundle-size/__styles.fixture.js +7 -0
  8. package/bundle-size/makeResetStyles.fixture.js +7 -0
  9. package/bundle-size/makeStaticStyles.fixture.js +7 -0
  10. package/bundle-size/makeStyles.fixture.js +7 -0
  11. package/eslint.config.mjs +31 -0
  12. package/package.json +3 -3
  13. package/project.json +96 -0
  14. package/src/RendererContext.tsx +52 -0
  15. package/src/TextDirectionContext.tsx +34 -0
  16. package/src/__css.ts +21 -0
  17. package/src/__resetCSS.ts +19 -0
  18. package/src/__resetStyles.ts +28 -0
  19. package/src/{__staticCSS.js → __staticCSS.ts} +7 -6
  20. package/src/__staticStyles.ts +22 -0
  21. package/src/__styles.ts +27 -0
  22. package/src/createDOMRenderer.test.tsx +133 -0
  23. package/src/{index.d.ts → index.ts} +6 -0
  24. package/src/insertionFactory-node.test.ts +31 -0
  25. package/src/insertionFactory.test.ts +29 -0
  26. package/src/insertionFactory.ts +27 -0
  27. package/src/makeResetStyles.test.tsx +27 -0
  28. package/src/makeResetStyles.ts +31 -0
  29. package/src/makeStaticStyles.ts +23 -0
  30. package/src/makeStyles.test.tsx +27 -0
  31. package/src/makeStyles.ts +31 -0
  32. package/src/renderToStyleElements-node.test.tsx +418 -0
  33. package/src/renderToStyleElements.test.tsx +103 -0
  34. package/src/renderToStyleElements.ts +61 -0
  35. package/src/stories/ComponentStyles.stories.tsx +55 -0
  36. package/src/stories/DOMRendererFilter.stories.tsx +76 -0
  37. package/src/stories/FallbackValues.stories.tsx +50 -0
  38. package/src/stories/makeStyles.stories.tsx +17 -0
  39. package/src/useInsertionEffect.ts +11 -0
  40. package/src/utils/canUseDOM-node.test.ts +14 -0
  41. package/src/utils/canUseDOM.test.tsx +8 -0
  42. package/src/utils/canUseDOM.ts +8 -0
  43. package/src/utils/isInsideComponent.ts +41 -0
  44. package/tsconfig.json +20 -0
  45. package/tsconfig.lib.json +28 -0
  46. package/tsconfig.spec.json +21 -0
  47. package/tsconfig.storybook.json +12 -0
  48. package/vitest.config.ts +21 -0
  49. package/LICENSE.md +0 -21
  50. package/lib/RendererContext.cjs +0 -45
  51. package/lib/TextDirectionContext.cjs +0 -33
  52. package/lib/__css.cjs +0 -22
  53. package/lib/__resetCSS.cjs +0 -22
  54. package/lib/__resetStyles.cjs +0 -26
  55. package/lib/__staticCSS.cjs +0 -18
  56. package/lib/__staticStyles.cjs +0 -23
  57. package/lib/__styles.cjs +0 -26
  58. package/lib/index.cjs +0 -78
  59. package/lib/insertionFactory.cjs +0 -33
  60. package/lib/makeResetStyles.cjs +0 -35
  61. package/lib/makeStaticStyles.cjs +0 -28
  62. package/lib/makeStyles.cjs +0 -35
  63. package/lib/renderToStyleElements.cjs +0 -50
  64. package/lib/useInsertionEffect.cjs +0 -20
  65. package/lib/utils/canUseDOM.cjs +0 -17
  66. package/lib/utils/isInsideComponent.cjs +0 -46
  67. package/src/RendererContext.d.ts +0 -24
  68. package/src/RendererContext.js +0 -31
  69. package/src/RendererContext.js.map +0 -1
  70. package/src/TextDirectionContext.d.ts +0 -19
  71. package/src/TextDirectionContext.js +0 -22
  72. package/src/TextDirectionContext.js.map +0 -1
  73. package/src/__css.d.ts +0 -7
  74. package/src/__css.js +0 -17
  75. package/src/__css.js.map +0 -1
  76. package/src/__resetCSS.d.ts +0 -6
  77. package/src/__resetCSS.js +0 -17
  78. package/src/__resetCSS.js.map +0 -1
  79. package/src/__resetStyles.d.ts +0 -7
  80. package/src/__resetStyles.js +0 -20
  81. package/src/__resetStyles.js.map +0 -1
  82. package/src/__staticCSS.d.ts +0 -6
  83. package/src/__staticCSS.js.map +0 -1
  84. package/src/__staticStyles.d.ts +0 -7
  85. package/src/__staticStyles.js +0 -18
  86. package/src/__staticStyles.js.map +0 -1
  87. package/src/__styles.d.ts +0 -7
  88. package/src/__styles.js +0 -20
  89. package/src/__styles.js.map +0 -1
  90. package/src/index.js +0 -16
  91. package/src/index.js.map +0 -1
  92. package/src/insertionFactory.d.ts +0 -2
  93. package/src/insertionFactory.js +0 -21
  94. package/src/insertionFactory.js.map +0 -1
  95. package/src/makeResetStyles.d.ts +0 -2
  96. package/src/makeResetStyles.js +0 -23
  97. package/src/makeResetStyles.js.map +0 -1
  98. package/src/makeStaticStyles.d.ts +0 -2
  99. package/src/makeStaticStyles.js +0 -17
  100. package/src/makeStaticStyles.js.map +0 -1
  101. package/src/makeStyles.d.ts +0 -2
  102. package/src/makeStyles.js +0 -23
  103. package/src/makeStyles.js.map +0 -1
  104. package/src/renderToStyleElements.d.ts +0 -8
  105. package/src/renderToStyleElements.js +0 -51
  106. package/src/renderToStyleElements.js.map +0 -1
  107. package/src/useInsertionEffect.d.ts +0 -1
  108. package/src/useInsertionEffect.js +0 -10
  109. package/src/useInsertionEffect.js.map +0 -1
  110. package/src/utils/canUseDOM.d.ts +0 -1
  111. package/src/utils/canUseDOM.js +0 -9
  112. package/src/utils/canUseDOM.js.map +0 -1
  113. package/src/utils/isInsideComponent.d.ts +0 -1
  114. package/src/utils/isInsideComponent.js +0 -39
  115. package/src/utils/isInsideComponent.js.map +0 -1
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+ import { createDOMRenderer, rehydrateRendererCache } from '@griffel/core';
4
+ import type { GriffelRenderer } from '@griffel/core';
5
+ import { createContext, useContext, useMemo, type FC, type ReactNode } from 'react';
6
+
7
+ import { canUseDOM } from './utils/canUseDOM.js';
8
+
9
+ export interface RendererProviderProps {
10
+ /** An instance of Griffel renderer. */
11
+ renderer: GriffelRenderer;
12
+
13
+ /**
14
+ * Document used to insert CSS variables to head
15
+ */
16
+ targetDocument?: Document;
17
+
18
+ /**
19
+ * Content wrapped by the RendererProvider
20
+ */
21
+ children: ReactNode;
22
+ }
23
+
24
+ /**
25
+ * @private
26
+ */
27
+ const RendererContext = /*#__PURE__*/ createContext<GriffelRenderer>(/*#__PURE__*/ createDOMRenderer());
28
+
29
+ /**
30
+ * @public
31
+ */
32
+ export const RendererProvider: FC<RendererProviderProps> = ({ children, renderer, targetDocument }) => {
33
+ // "rehydrateCache()" can't be called in effects as it needs to be called before any component will be rendered to
34
+ // avoid double insertion of classes — useMemo runs synchronously before render, useEffect would be too late.
35
+ // eslint-disable-next-line react-x/use-memo, react-hooks/void-use-memo
36
+ useMemo(() => {
37
+ if (canUseDOM()) {
38
+ rehydrateRendererCache(renderer, targetDocument);
39
+ }
40
+ }, [renderer, targetDocument]);
41
+
42
+ return <RendererContext.Provider value={renderer}>{children}</RendererContext.Provider>;
43
+ };
44
+
45
+ /**
46
+ * Returns an instance of current makeStyles() renderer.
47
+ *
48
+ * @private Exported as "useRenderer_unstable" use it on own risk. Can be changed or removed without a notice.
49
+ */
50
+ export function useRenderer(): GriffelRenderer {
51
+ return useContext(RendererContext);
52
+ }
@@ -0,0 +1,34 @@
1
+ 'use client';
2
+
3
+ import { createContext, useContext, type FC, type ReactNode } from 'react';
4
+
5
+ export interface TextDirectionProviderProps {
6
+ /** Indicates the directionality of the element's text. */
7
+ dir: 'ltr' | 'rtl';
8
+
9
+ /**
10
+ * Content wrapped by the TextDirectionProvider.
11
+ */
12
+ children: ReactNode;
13
+ }
14
+
15
+ /**
16
+ * @private
17
+ */
18
+ const TextDirectionContext = /*#__PURE__*/ createContext<'ltr' | 'rtl'>('ltr');
19
+
20
+ /**
21
+ * @public
22
+ */
23
+ export const TextDirectionProvider: FC<TextDirectionProviderProps> = ({ children, dir }) => {
24
+ return <TextDirectionContext.Provider value={dir}>{children}</TextDirectionContext.Provider>;
25
+ };
26
+
27
+ /**
28
+ * Returns current directionality of the element's text.
29
+ *
30
+ * @private
31
+ */
32
+ export function useTextDirection() {
33
+ return useContext(TextDirectionContext);
34
+ }
package/src/__css.ts ADDED
@@ -0,0 +1,21 @@
1
+ 'use client';
2
+
3
+ import { __css as vanillaCSS } from '@griffel/core';
4
+ import type { CSSClassesMapBySlot } from '@griffel/core';
5
+
6
+ import { useTextDirection } from './TextDirectionContext.js';
7
+
8
+ /**
9
+ * A version of makeStyles() that accepts build output as an input and skips all runtime transforms & DOM insertion.
10
+ *
11
+ * @private
12
+ */
13
+ export function __css<Slots extends string>(classesMapBySlot: CSSClassesMapBySlot<Slots>) {
14
+ const getStyles = vanillaCSS(classesMapBySlot);
15
+
16
+ return function useClasses(): Record<Slots, string> {
17
+ const dir = useTextDirection();
18
+
19
+ return getStyles({ dir });
20
+ };
21
+ }
@@ -0,0 +1,19 @@
1
+ 'use client';
2
+
3
+ import { __resetCSS as vanillaResetCSS } from '@griffel/core';
4
+ import { useTextDirection } from './TextDirectionContext.js';
5
+
6
+ /**
7
+ * A version of makeResetStyles() that accepts build output as an input and skips all runtime transforms & DOM insertion.
8
+ *
9
+ * @private
10
+ */
11
+ export function __resetCSS(ltrClassName: string, rtlClassName: string | null) {
12
+ const getStyles = vanillaResetCSS(ltrClassName, rtlClassName);
13
+
14
+ return function useClasses(): string {
15
+ const dir = useTextDirection();
16
+
17
+ return getStyles({ dir });
18
+ };
19
+ }
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import { __resetStyles as vanillaResetStyles } from '@griffel/core';
4
+ import type { CSSRulesByBucket } from '@griffel/core';
5
+
6
+ import { insertionFactory } from './insertionFactory.js';
7
+ import { useRenderer } from './RendererContext.js';
8
+ import { useTextDirection } from './TextDirectionContext.js';
9
+
10
+ /**
11
+ * A version of makeResetStyles() that accepts build output as an input and skips all runtime transforms.
12
+ *
13
+ * @private
14
+ */
15
+ export function __resetStyles(
16
+ ltrClassName: string,
17
+ rtlClassName: string | null,
18
+ cssRules: CSSRulesByBucket | string[],
19
+ ) {
20
+ const getStyles = vanillaResetStyles(ltrClassName, rtlClassName, cssRules, insertionFactory);
21
+
22
+ return function useClasses(): string {
23
+ const dir = useTextDirection();
24
+ const renderer = useRenderer();
25
+
26
+ return getStyles({ dir, renderer });
27
+ };
28
+ }
@@ -1,15 +1,16 @@
1
1
  'use client';
2
+
2
3
  import { __staticCSS as vanillaStaticCSS } from '@griffel/core';
4
+
3
5
  /**
4
6
  * A version of makeStaticStyles() that accepts build output as an input and skips all runtime transforms & DOM insertion.
5
7
  *
6
8
  * @private
7
9
  */
8
- // eslint-disable-next-line @typescript-eslint/naming-convention
9
10
  export function __staticCSS() {
10
- const getStyles = vanillaStaticCSS();
11
- return function useStaticStyles() {
12
- return getStyles();
13
- };
11
+ const getStyles = vanillaStaticCSS();
12
+
13
+ return function useStaticStyles(): void {
14
+ return getStyles();
15
+ };
14
16
  }
15
- //# sourceMappingURL=__staticCSS.js.map
@@ -0,0 +1,22 @@
1
+ 'use client';
2
+
3
+ import { __staticStyles as vanillaStaticStyles } from '@griffel/core';
4
+ import type { CSSRulesByBucket } from '@griffel/core';
5
+
6
+ import { insertionFactory } from './insertionFactory.js';
7
+ import { useRenderer } from './RendererContext.js';
8
+
9
+ /**
10
+ * A version of makeStaticStyles() that accepts build output as an input and skips all runtime transforms.
11
+ *
12
+ * @private
13
+ */
14
+ export function __staticStyles(cssRules: CSSRulesByBucket) {
15
+ const getStyles = vanillaStaticStyles(cssRules, insertionFactory);
16
+
17
+ return function useStaticStyles(): void {
18
+ const renderer = useRenderer();
19
+
20
+ return getStyles({ renderer });
21
+ };
22
+ }
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import { __styles as vanillaStyles } from '@griffel/core';
4
+ import type { CSSClassesMapBySlot, CSSRulesByBucket } from '@griffel/core';
5
+
6
+ import { insertionFactory } from './insertionFactory.js';
7
+ import { useRenderer } from './RendererContext.js';
8
+ import { useTextDirection } from './TextDirectionContext.js';
9
+
10
+ /**
11
+ * A version of makeStyles() that accepts build output as an input and skips all runtime transforms.
12
+ *
13
+ * @private
14
+ */
15
+ export function __styles<Slots extends string>(
16
+ classesMapBySlot: CSSClassesMapBySlot<Slots>,
17
+ cssRules: CSSRulesByBucket,
18
+ ) {
19
+ const getStyles = vanillaStyles(classesMapBySlot, cssRules, insertionFactory);
20
+
21
+ return function useClasses(): Record<Slots, string> {
22
+ const dir = useTextDirection();
23
+ const renderer = useRenderer();
24
+
25
+ return getStyles({ dir, renderer });
26
+ };
27
+ }
@@ -0,0 +1,133 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import type { Mock } from 'vitest';
3
+ import { createDOMRenderer, mergeClasses } from '@griffel/core';
4
+ import * as React from 'react';
5
+ import { hydrateRoot } from 'react-dom/client';
6
+ import { renderToStaticMarkup } from 'react-dom/server';
7
+
8
+ import { makeStyles } from './makeStyles.js';
9
+ import { makeResetStyles } from './makeResetStyles.js';
10
+ import { RendererProvider } from './RendererContext.js';
11
+ import { renderToStyleElements } from './renderToStyleElements.js';
12
+ import { useInsertionEffect as _useInsertionEffect } from './useInsertionEffect.js';
13
+
14
+ vi.mock('./useInsertionEffect', () => ({
15
+ useInsertionEffect: vi.fn(),
16
+ }));
17
+
18
+ const useInsertionEffect = _useInsertionEffect as Mock<typeof React.useInsertionEffect>;
19
+
20
+ describe('createDOMRenderer', () => {
21
+ it('rehydrateCache() avoids double insertion', () => {
22
+ // This test validates a scenario for Server-Side rendering
23
+
24
+ const clientRenderer = createDOMRenderer(document);
25
+ const serverRenderer = createDOMRenderer(
26
+ // we should use "null" as "undefined" will fall back to "document" which is present in this environment
27
+ null as unknown as undefined,
28
+ );
29
+
30
+ const useExampleClasses = makeStyles({
31
+ root: {
32
+ animationName: {
33
+ from: { height: '10px' },
34
+ to: { height: '20px' },
35
+ },
36
+
37
+ color: 'red',
38
+ '@media screen and (max-width: 992px)': { ':hover': { color: 'blue' } },
39
+ },
40
+ });
41
+ const useExampleClass = makeResetStyles({
42
+ color: 'red',
43
+ ':hover': { color: 'blue' },
44
+ });
45
+ const ExampleComponent: React.FC = () => {
46
+ const classes = useExampleClasses();
47
+ const className = useExampleClass();
48
+
49
+ return <div className={mergeClasses(className, classes.root)} />;
50
+ };
51
+
52
+ //
53
+ // Server
54
+ // A "server" renders components to static HTML that will be transferred to a client
55
+ //
56
+
57
+ // Heads up!
58
+ // Mock there is need as this test is executed in DOM environment and uses "useInsertionEffect".
59
+ // However, "useInsertionEffect" will not be called in "renderToStaticMarkup()".
60
+ useInsertionEffect.mockImplementation(fn => fn());
61
+
62
+ const componentHTML = renderToStaticMarkup(
63
+ <RendererProvider renderer={serverRenderer}>
64
+ <ExampleComponent />
65
+ </RendererProvider>,
66
+ );
67
+ const stylesHTML = renderToStaticMarkup(<>{renderToStyleElements(serverRenderer)}</>);
68
+
69
+ useInsertionEffect.mockImplementation(React.useInsertionEffect);
70
+
71
+ // Ensure that all styles are inserted into the cache
72
+ expect(serverRenderer.insertionCache).toMatchInlineSnapshot(`
73
+ {
74
+ ".f1p9cr64{animation-name:f1kgwxhb;}": "d",
75
+ ".fe3e8s9{color:red;}": "d",
76
+ ".rp2atum:hover{color:blue;}": "r",
77
+ ".rp2atum{color:red;}": "r",
78
+ "@keyframes f1kgwxhb{from{height:10px;}to{height:20px;}}": "k",
79
+ "@media screen and (max-width: 992px){.fzd6x39:hover{color:blue;}}": "m",
80
+ }
81
+ `);
82
+ // There is no DOM on a server, style nodes should not be present
83
+ expect(document.querySelector('style')).toBe(null);
84
+
85
+ //
86
+ // Client
87
+ // Creates an element to render components and inserts HTML rendered from a server
88
+ //
89
+
90
+ const container = document.createElement('div');
91
+
92
+ document.body.appendChild(container);
93
+
94
+ container.innerHTML = componentHTML;
95
+ document.head.innerHTML = stylesHTML;
96
+
97
+ // As all style came from a server, we should not insert any CSS on a client
98
+ // (this tests internal implementation, but there is no other way?)
99
+ const styleElementsBeforeHydration = document.querySelectorAll<HTMLStyleElement>('style');
100
+ const insertRules = [...(styleElementsBeforeHydration as unknown as HTMLStyleElement[])].map(styleEl =>
101
+ vi.spyOn(styleEl.sheet!, 'insertRule'),
102
+ );
103
+
104
+ React.act(() => {
105
+ hydrateRoot(
106
+ container,
107
+ // "RendererProvider" is not required there, we need it only for Jest spies
108
+ <RendererProvider renderer={clientRenderer}>
109
+ <ExampleComponent />
110
+ </RendererProvider>,
111
+ );
112
+ });
113
+
114
+ const styleElementsAfterHydration = document.querySelectorAll<HTMLStyleElement>('style');
115
+
116
+ // We also would to ensure that new elements have not been inserted
117
+ expect(styleElementsBeforeHydration.length).toBe(styleElementsAfterHydration.length);
118
+
119
+ // Following rules are present in cache:
120
+ // - makeResetStyles
121
+ // - color
122
+ // - :hover + color
123
+ // - makeStyles
124
+ // - "animationName"
125
+ // - "color"
126
+ // - @keyframes
127
+ // - @media
128
+ expect(Object.keys(clientRenderer.insertionCache)).toHaveLength(6);
129
+ insertRules.forEach(insertRule => {
130
+ expect(insertRule).not.toHaveBeenCalled();
131
+ });
132
+ });
133
+ });
@@ -1,11 +1,17 @@
1
+ 'use client';
2
+
1
3
  export { RESET, shorthands, mergeClasses, createDOMRenderer } from '@griffel/core';
2
4
  export type { GriffelStyle, GriffelResetStyle, CreateDOMRendererOptions, GriffelRenderer } from '@griffel/core';
5
+
3
6
  export { makeStyles } from './makeStyles.js';
4
7
  export { makeResetStyles } from './makeResetStyles.js';
5
8
  export { makeStaticStyles } from './makeStaticStyles.js';
6
9
  export { renderToStyleElements } from './renderToStyleElements.js';
10
+
7
11
  export { RendererProvider, useRenderer as useRenderer_unstable } from './RendererContext.js';
8
12
  export { TextDirectionProvider } from './TextDirectionContext.js';
13
+
14
+ // Private exports, are used by build time transforms
9
15
  export { __css } from './__css.js';
10
16
  export { __styles } from './__styles.js';
11
17
  export { __resetCSS } from './__resetCSS.js';
@@ -0,0 +1,31 @@
1
+ /*
2
+ * @vitest-environment node
3
+ */
4
+
5
+ // 👆 this is intentionally to test in SSR like environment
6
+
7
+ import { describe, it, expect, vi } from 'vitest';
8
+ import type { GriffelRenderer } from '@griffel/core';
9
+
10
+ const { useInsertionEffect } = vi.hoisted(() => ({
11
+ useInsertionEffect: vi.fn(),
12
+ }));
13
+
14
+ vi.mock('react', async importOriginal => {
15
+ const actual = await importOriginal<typeof import('react')>();
16
+ return { ...actual, useInsertionEffect };
17
+ });
18
+
19
+ import { insertionFactory } from './insertionFactory.js';
20
+
21
+ describe('insertionFactory (node)', () => {
22
+ it('does not use insertionEffect', () => {
23
+ const renderer: Partial<GriffelRenderer> = { id: 'a', insertCSSRules: vi.fn() };
24
+ const insertStyles = insertionFactory();
25
+
26
+ insertStyles(renderer as GriffelRenderer, { d: ['a'] });
27
+
28
+ expect(useInsertionEffect).not.toHaveBeenCalled();
29
+ expect(renderer.insertCSSRules).toHaveBeenCalledTimes(1);
30
+ });
31
+ });
@@ -0,0 +1,29 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import type { Mock } from 'vitest';
3
+ import type { GriffelRenderer } from '@griffel/core';
4
+
5
+ import { insertionFactory } from './insertionFactory.js';
6
+ import { useInsertionEffect as _useInsertionEffect } from './useInsertionEffect.js';
7
+ import type * as React from 'react';
8
+
9
+ vi.mock('./useInsertionEffect', () => ({
10
+ useInsertionEffect: vi.fn().mockImplementation(fn => fn()),
11
+ }));
12
+
13
+ const useInsertionEffect = _useInsertionEffect as Mock<typeof React.useInsertionEffect>;
14
+
15
+ describe('canUseDOM', () => {
16
+ beforeEach(() => {
17
+ vi.clearAllMocks();
18
+ });
19
+
20
+ it('uses "useInsertionEffect" when available', () => {
21
+ const renderer: Partial<GriffelRenderer> = { insertCSSRules: vi.fn() };
22
+ const insertStyles = insertionFactory();
23
+
24
+ insertStyles(renderer as GriffelRenderer, { d: ['a'] });
25
+
26
+ expect(useInsertionEffect).toHaveBeenCalledTimes(1);
27
+ expect(renderer.insertCSSRules).toHaveBeenCalledTimes(1);
28
+ });
29
+ });
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+
3
+ import type { CSSRulesByBucket, GriffelInsertionFactory, GriffelRenderer } from '@griffel/core';
4
+
5
+ import { canUseDOM } from './utils/canUseDOM.js';
6
+ import { useInsertionEffect } from './useInsertionEffect.js';
7
+
8
+ export const insertionFactory: GriffelInsertionFactory = () => {
9
+ const insertionCache: Record<string, boolean> = {};
10
+
11
+ return function insert(renderer: GriffelRenderer, cssRules: CSSRulesByBucket) {
12
+ // Even if `useInsertionEffect` is available, we can use it on a client only as it will not be executed in SSR
13
+ if (useInsertionEffect && canUseDOM()) {
14
+ // eslint-disable-next-line react-x/rules-of-hooks
15
+ useInsertionEffect(() => {
16
+ renderer.insertCSSRules(cssRules!);
17
+ }, [renderer, cssRules]);
18
+
19
+ return;
20
+ }
21
+
22
+ if (insertionCache[renderer.id] === undefined) {
23
+ renderer.insertCSSRules(cssRules!);
24
+ insertionCache[renderer.id] = true;
25
+ }
26
+ };
27
+ };
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import type * as React from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { act } from 'react-dom/test-utils';
5
+
6
+ import { makeResetStyles } from './makeResetStyles.js';
7
+
8
+ describe('makeResetStyles', () => {
9
+ it('works outside of React components', () => {
10
+ expect(() => makeResetStyles({ root: { color: 'red' } })).not.toThrow();
11
+ });
12
+
13
+ it('throws inside React components', () => {
14
+ vi.spyOn(console, 'error').mockImplementation(() => {});
15
+
16
+ const Example: React.FC = () => {
17
+ makeResetStyles({ color: 'red' });
18
+ return null;
19
+ };
20
+ const root = createRoot(document.createElement('div'));
21
+
22
+ expect(() => act(() => root.render(<Example />))).toThrow(/All makeResetStyles\(\) calls should be top level/);
23
+
24
+ // Should not throw outside React components after rendering
25
+ expect(() => makeResetStyles({ color: 'red' })).not.toThrow();
26
+ });
27
+ });
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { makeResetStyles as vanillaMakeResetStyles } from '@griffel/core';
4
+ import type { GriffelResetStyle } from '@griffel/core';
5
+
6
+ import { insertionFactory } from './insertionFactory.js';
7
+ import { useRenderer } from './RendererContext.js';
8
+ import { useTextDirection } from './TextDirectionContext.js';
9
+ import { isInsideComponent } from './utils/isInsideComponent.js';
10
+
11
+ export function makeResetStyles(styles: GriffelResetStyle) {
12
+ const getStyles = vanillaMakeResetStyles(styles, insertionFactory);
13
+
14
+ if (process.env.NODE_ENV !== 'production') {
15
+ if (isInsideComponent()) {
16
+ throw new Error(
17
+ [
18
+ "makeResetStyles(): this function cannot be called in component's scope.",
19
+ 'All makeResetStyles() calls should be top level i.e. in a root scope of a file.',
20
+ ].join(' '),
21
+ );
22
+ }
23
+ }
24
+
25
+ return function useClassName(): string {
26
+ const dir = useTextDirection();
27
+ const renderer = useRenderer();
28
+
29
+ return getStyles({ dir, renderer });
30
+ };
31
+ }
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+ import { makeStaticStyles as vanillaMakeStaticStyles } from '@griffel/core';
4
+ import type { GriffelStaticStyles, MakeStaticStylesOptions } from '@griffel/core';
5
+
6
+ import { insertionFactory } from './insertionFactory.js';
7
+ import { useRenderer } from './RendererContext.js';
8
+
9
+ export function makeStaticStyles(styles: GriffelStaticStyles | GriffelStaticStyles[]) {
10
+ const getStyles = vanillaMakeStaticStyles(styles, insertionFactory);
11
+
12
+ if (process.env.NODE_ENV === 'test') {
13
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
14
+ return () => {};
15
+ }
16
+
17
+ return function useStaticStyles(): void {
18
+ const renderer = useRenderer();
19
+ const options: MakeStaticStylesOptions = { renderer };
20
+
21
+ return getStyles(options);
22
+ };
23
+ }
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import type * as React from 'react';
3
+ import { createRoot } from 'react-dom/client';
4
+ import { act } from 'react-dom/test-utils';
5
+
6
+ import { makeStyles } from './makeStyles.js';
7
+
8
+ describe('makeStyles', () => {
9
+ it('works outside of React components', () => {
10
+ expect(() => makeStyles({ root: { color: 'red' } })).not.toThrow();
11
+ });
12
+
13
+ it('throws inside React components', () => {
14
+ vi.spyOn(console, 'error').mockImplementation(() => {});
15
+
16
+ const Example: React.FC = () => {
17
+ makeStyles({ root: { color: 'red' } });
18
+ return null;
19
+ };
20
+ const root = createRoot(document.createElement('div'));
21
+
22
+ expect(() => act(() => root.render(<Example />))).toThrow(/All makeStyles\(\) calls should be top level/);
23
+
24
+ // Should not throw outside React components after rendering
25
+ expect(() => makeStyles({ root: { color: 'red' } })).not.toThrow();
26
+ });
27
+ });
@@ -0,0 +1,31 @@
1
+ 'use client';
2
+
3
+ import { makeStyles as vanillaMakeStyles } from '@griffel/core';
4
+ import type { GriffelStyle } from '@griffel/core';
5
+
6
+ import { insertionFactory } from './insertionFactory.js';
7
+ import { useRenderer } from './RendererContext.js';
8
+ import { useTextDirection } from './TextDirectionContext.js';
9
+ import { isInsideComponent } from './utils/isInsideComponent.js';
10
+
11
+ export function makeStyles<Slots extends string | number>(stylesBySlots: Record<Slots, GriffelStyle>) {
12
+ const getStyles = vanillaMakeStyles(stylesBySlots, insertionFactory);
13
+
14
+ if (process.env.NODE_ENV !== 'production') {
15
+ if (isInsideComponent()) {
16
+ throw new Error(
17
+ [
18
+ "makeStyles(): this function cannot be called in component's scope.",
19
+ 'All makeStyles() calls should be top level i.e. in a root scope of a file.',
20
+ ].join(' '),
21
+ );
22
+ }
23
+ }
24
+
25
+ return function useClasses(): Record<Slots, string> {
26
+ const dir = useTextDirection();
27
+ const renderer = useRenderer();
28
+
29
+ return getStyles({ dir, renderer });
30
+ };
31
+ }