Skip to content

Commit afa9a91

Browse files
feat(ui): Add mosaic design systems foundations (#8755)
1 parent 7ff246b commit afa9a91

8 files changed

Lines changed: 1370 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
---
2+
---

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ Clerk's JavaScript SDK and library monorepo.
1313
## References
1414

1515
- For questions about theming, appearance customization, or the styled system, see `references/theming-architecture.md`.
16+
- For the Mosaic design system (tokens, CVA utility, `MosaicProvider`, migration from existing system), see `references/mosaic-architecture.md`.
1617
- For dev setup, testing, JSDoc/Typedoc, publishing, changesets, and commit conventions, see `docs/CONTRIBUTING.md`.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// eslint-disable-next-line no-restricted-imports
2+
import createCache from '@emotion/cache';
3+
// eslint-disable-next-line no-restricted-imports
4+
import { CacheProvider, type SerializedStyles } from '@emotion/react';
5+
import React from 'react';
6+
7+
import { defaultMosaicVariables, resolveVariables } from './variables';
8+
import type { MosaicTheme, MosaicVariables } from './variables';
9+
10+
const getInsertionPoint = (): HTMLElement | null => {
11+
if (typeof document === 'undefined') {
12+
return null;
13+
}
14+
return document.querySelector('style#cl-mosaic-style-insertion-point');
15+
};
16+
17+
const MosaicThemeContext = React.createContext<MosaicTheme | null>(null);
18+
19+
export interface MosaicProviderProps {
20+
children: React.ReactNode;
21+
variables?: MosaicVariables;
22+
nonce?: string;
23+
cssLayerName?: string;
24+
}
25+
26+
export function MosaicProvider({ children, variables, nonce, cssLayerName }: MosaicProviderProps) {
27+
const theme = React.useMemo(() => resolveVariables(defaultMosaicVariables, variables), [variables]);
28+
const cache = React.useMemo(() => {
29+
const el = getInsertionPoint();
30+
const emotionCache = createCache({
31+
key: 'cl-mosaic',
32+
stylisPlugins: [],
33+
prepend: cssLayerName ? false : !el,
34+
insertionPoint: el ?? undefined,
35+
nonce,
36+
});
37+
38+
if (cssLayerName) {
39+
const prevInsert = emotionCache.insert.bind(emotionCache);
40+
emotionCache.insert = (selector: string, serialized: SerializedStyles, sheet: any, shouldCache: boolean) => {
41+
if (serialized && typeof serialized.styles === 'string') {
42+
const newSerialized = { ...serialized };
43+
newSerialized.styles = `@layer ${cssLayerName} {${serialized.styles}}`;
44+
return prevInsert(selector, newSerialized, sheet, shouldCache);
45+
}
46+
return prevInsert(selector, serialized, sheet, shouldCache);
47+
};
48+
}
49+
50+
return emotionCache;
51+
}, [nonce, cssLayerName]);
52+
return (
53+
<MosaicThemeContext.Provider value={theme}>
54+
<CacheProvider value={cache}>{children}</CacheProvider>
55+
</MosaicThemeContext.Provider>
56+
);
57+
}
58+
59+
export function useMosaicTheme(): MosaicTheme {
60+
const theme = React.useContext(MosaicThemeContext);
61+
if (!theme) {
62+
throw new Error('useMosaicTheme must be used within a MosaicProvider');
63+
}
64+
return theme;
65+
}

0 commit comments

Comments
 (0)