Skip to content

Commit

Permalink
feat(js): Scope variables under class of id (#5820)
Browse files Browse the repository at this point in the history
  • Loading branch information
desiprisg authored Jun 26, 2024
1 parent 4359c52 commit d0f1c5a
Show file tree
Hide file tree
Showing 8 changed files with 106 additions and 68 deletions.
5 changes: 3 additions & 2 deletions packages/js/src/ui/Inbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { For, createSignal, onMount } from 'solid-js';
import { Notification } from '../feeds';
import { Novu } from '../novu';
import type { NovuOptions } from '../novu';
import { Appearance, AppearanceProvider } from './context';
import { Appearance, AppearanceProvider, Elements, useAppearance } from './context';
import { useStyle } from './helpers';

type InboxProps = {
id: string;
name: string;
options: NovuOptions;
appearance?: Appearance;
Expand All @@ -24,7 +25,7 @@ export const Inbox = (props: InboxProps) => {
});

return (
<AppearanceProvider elements={props.appearance?.elements} variables={props.appearance?.variables}>
<AppearanceProvider id={props.id} elements={props.appearance?.elements} variables={props.appearance?.variables}>
<InternalInbox feeds={feeds()} />
</AppearanceProvider>
);
Expand Down
1 change: 0 additions & 1 deletion packages/js/src/ui/config/constants.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/js/src/ui/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
export * from './constants';
export * from './default-appearance';
29 changes: 23 additions & 6 deletions packages/js/src/ui/context/AppearanceContext.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ParentProps, createContext, createEffect, createSignal, onMount, useContext } from 'solid-js';
import { ParentProps, createContext, createEffect, createSignal, onCleanup, onMount, useContext } from 'solid-js';
import { createStore } from 'solid-js/store';
import { NOVU_CSS_IN_JS_STYLESHEET_ID, defaultVariables } from '../config';
import { defaultVariables } from '../config';
import { parseElements, parseVariables } from '../helpers';

export type CSSProperties = {
Expand All @@ -26,17 +26,18 @@ export type Variables = {
borderRadius?: string;
};

export type AppearanceContextType = {
type AppearanceContextType = {
variables?: Variables;
elements?: Elements;
descriptorToCssInJsClass: Record<string, string>;
id: string;
};

const AppearanceContext = createContext<AppearanceContextType | undefined>(undefined);

export type Appearance = Pick<AppearanceContextType, 'elements' | 'variables'>;

type AppearanceProviderProps = ParentProps & Appearance;
type AppearanceProviderProps = ParentProps & Appearance & { id: string };

export const AppearanceProvider = (props: AppearanceProviderProps) => {
const [store, setStore] = createStore<{
Expand All @@ -48,13 +49,28 @@ export const AppearanceProvider = (props: AppearanceProviderProps) => {

//place style element on HEAD. Placing in body is available for HTML 5.2 onward.
onMount(() => {
const el = document.getElementById(props.id);
if (el) {
setStyleElement(el as HTMLStyleElement);

return;
}

const styleEl = document.createElement('style');
styleEl.id = NOVU_CSS_IN_JS_STYLESHEET_ID;
styleEl.id = props.id;
console.log(props.id);
document.head.appendChild(styleEl);

setStyleElement(styleEl);
});

onCleanup(() => {
const el = document.getElementById(props.id);
if (el) {
el.remove();
}
});

//handle variables
createEffect(() => {
const styleEl = styleElement();
Expand All @@ -63,7 +79,7 @@ export const AppearanceProvider = (props: AppearanceProviderProps) => {
return;
}

setVariableRules(parseVariables({ ...defaultVariables, ...(props.variables || ({} as Variables)) }));
setVariableRules(parseVariables({ ...defaultVariables, ...(props.variables || ({} as Variables)) }, props.id));
});

//handle elements
Expand Down Expand Up @@ -101,6 +117,7 @@ export const AppearanceProvider = (props: AppearanceProviderProps) => {
value={{
elements: props.elements || {},
descriptorToCssInJsClass: store.descriptorToCssInJsClass,
id: props.id,
}}
>
{props.children}
Expand Down
6 changes: 3 additions & 3 deletions packages/js/src/ui/helpers/use-style.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ export const useStyle = () => {
setIsServer(false);
});

const styleFuncMemo = createMemo(() => (className: string, descriptor?: keyof Elements | keyof Elements[]) => {
const styleFuncMemo = createMemo(() => (className: string, descriptor?: keyof Elements | (keyof Elements)[]) => {
const appearanceClassname =
descriptor && typeof appearance.elements?.[descriptor] === 'string'
typeof descriptor === 'string' && typeof appearance.elements?.[descriptor] === 'string'
? (appearance.elements?.[descriptor] as string) || ''
: '';

const descriptors = (Array.isArray(descriptor) ? descriptor : [descriptor]).map((des) => `nv-${des}`);
const descriptors = (Array.isArray(descriptor) ? descriptor : [descriptor]).map((desc) => `nv-${desc}`);
const cssInJsClasses =
!!descriptors.length && !isServer() ? descriptors.map((des) => appearance.descriptorToCssInJsClass[des]) : [];

Expand Down
58 changes: 34 additions & 24 deletions packages/js/src/ui/helpers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ export function cn(...inputs: ClassValue[]) {
return clsx(inputs);
}

function generateRandomString(length: number): string {
const characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
export function generateRandomString(length: number): string {
const characters = 'abcdefghijklmnopqrstuvwxyz';
let result = '';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
Expand Down Expand Up @@ -45,17 +45,17 @@ export function createClassAndRuleFromCssString(classNameSet: Set<string>, style
}

const shades = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900];
export function generateDefaultColor(color: string, key: string) {
const cssVariableDefaultRule = `:root { --nv-${key}: oklch(from ${color} l c h); }`;
export function generateDefaultColor(props: { color: string; key: string; id: string }) {
const cssVariableDefaultRule = `.${props.id} { --nv-${props.key}: oklch(from ${props.color} l c h); }`;

return cssVariableDefaultRule;
}

export function generatesSolidShadesFromColor(color: string, key: string) {
export function generatesSolidShadesFromColor(props: { color: string; key: string; id: string }) {
const rules = [];
for (let i = 0; i < shades.length; i++) {
const shade = shades[i];
const cssVariableSolidRule = `:root { --nv-${key}-${shade}: oklch(from ${color} calc(l - ${
const cssVariableSolidRule = `.${props.id} { --nv-${props.key}-${shade}: oklch(from ${props.color} calc(l - ${
(shade - 500) / 1000
}) c h); }`;
rules.push(cssVariableSolidRule);
Expand All @@ -64,34 +64,44 @@ export function generatesSolidShadesFromColor(color: string, key: string) {
return rules;
}

export function generatesAlphaShadesFromColor(color: string, key: string) {
export function generatesAlphaShadesFromColor(props: { color: string; key: string; id: string }) {
const rules = [];
for (let i = 0; i < shades.length; i++) {
const shade = shades[i];
const cssVariableAlphaRule = `:root { --nv-${key}-${shade}: oklch(from ${color} l c h / ${shade / 1000}); }`;
const cssVariableAlphaRule = `.${props.id} { --nv-${props.id}-${shade}: oklch(from ${props.color} l c h / ${
shade / 1000
}); }`;
rules.push(cssVariableAlphaRule);
}

return rules;
}

export const parseVariables = (variables: Required<Variables>) => {
export const parseVariables = (variables: Required<Variables>, id: string) => {
return [
generateDefaultColor(variables.colorBackground, 'color-background'),
generateDefaultColor(variables.colorForeground, 'color-foreground'),
generateDefaultColor(variables.colorPrimary, 'color-primary'),
generateDefaultColor(variables.colorPrimaryForeground, 'color-primary-foreground'),
generateDefaultColor(variables.colorSecondary, 'color-secondary'),
generateDefaultColor(variables.colorSecondaryForeground, 'color-secondary-foreground'),
...generatesAlphaShadesFromColor(variables.colorBackground, 'color-background-alpha'),
...generatesAlphaShadesFromColor(variables.colorForeground, 'color-foreground-alpha'),
...generatesSolidShadesFromColor(variables.colorPrimary, 'color-primary'),
...generatesAlphaShadesFromColor(variables.colorPrimary, 'color-primary-alpha'),
...generatesAlphaShadesFromColor(variables.colorPrimaryForeground, 'color-primary-foreground-alpha'),
...generatesSolidShadesFromColor(variables.colorSecondary, 'color-secondary'),
...generatesAlphaShadesFromColor(variables.colorSecondary, 'color-secondary-alpha'),
...generatesAlphaShadesFromColor(variables.colorSecondaryForeground, 'color-secondary-foreground-alpha'),
...generatesAlphaShadesFromColor(variables.colorNeutral, 'color-neutral-alpha'),
generateDefaultColor({ color: variables.colorBackground, key: 'color-background', id }),
generateDefaultColor({ color: variables.colorForeground, key: 'color-foreground', id }),
generateDefaultColor({ color: variables.colorPrimary, key: 'color-primary', id }),
generateDefaultColor({ color: variables.colorPrimaryForeground, key: 'color-primary-foreground', id }),
generateDefaultColor({ color: variables.colorSecondary, key: 'color-secondary', id }),
generateDefaultColor({ color: variables.colorSecondaryForeground, key: 'color-secondary-foreground', id }),
...generatesAlphaShadesFromColor({ color: variables.colorBackground, key: 'color-background-alpha', id }),
...generatesAlphaShadesFromColor({ color: variables.colorForeground, key: 'color-foreground-alpha', id }),
...generatesSolidShadesFromColor({ color: variables.colorPrimary, key: 'color-primary', id }),
...generatesAlphaShadesFromColor({ color: variables.colorPrimary, key: 'color-primary-alpha', id }),
...generatesAlphaShadesFromColor({
color: variables.colorPrimaryForeground,
key: 'color-primary-foreground-alpha',
id,
}),
...generatesSolidShadesFromColor({ color: variables.colorSecondary, key: 'color-secondary', id }),
...generatesAlphaShadesFromColor({ color: variables.colorSecondary, key: 'color-secondary-alpha', id }),
...generatesAlphaShadesFromColor({
color: variables.colorSecondaryForeground,
key: 'color-secondary-foreground-alpha',
id,
}),
...generatesAlphaShadesFromColor({ color: variables.colorNeutral, key: 'color-neutral-alpha', id }),
];
};

Expand Down
Loading

0 comments on commit d0f1c5a

Please sign in to comment.