Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion packages/react/src/components/design-system.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ export type DesignSystemProps = ThemeWrapperProps & DeviceContextProviderProps &

export const DesignSystem: FunctionComponent<PropsWithChildren<DesignSystemProps>> = (props) => (
<DeviceContextProvider staticDevice={props.staticDevice}>
<ThemeWrapper theme={props.theme} isolateStyles={props.isolateStyles}>
<ThemeWrapper
isolateStyles={props.isolateStyles}
themeCustomization={props.themeCustomization}
displayPreferences={props.displayPreferences}
>
<IntlProvider language={props.language}>
<ToastProvider>
{props.children}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createContext, FunctionComponent, PropsWithChildren, useContext, useEffect, useState } from 'react';
import {
createContext,
FunctionComponent,
PropsWithChildren,
useCallback,
useContext,
useEffect,
useRef,
useState,
} from 'react';
import { breakpoints, Breakpoints } from '../../legacy-constants/breakpoints';

export type DeviceType = 'desktop' | 'tablet' | 'mobile';
Expand All @@ -15,40 +24,20 @@ export interface DeviceContextProps {
breakpoints: Breakpoints;
}

const getDeviceContext = (deviceName: DeviceType | undefined = undefined): DeviceContextProps => {
let isDesktop = false;
let isTablet = false;
let isMobile = false;
const defaultContext: DeviceContextProps = {
device: 'desktop',
isDesktop: true,
isTablet: false,
isMobile: false,
function getDeviceContext(deviceName: DeviceType): DeviceContextProps {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Les modifs dans ce fichier sont essentiellement pour limiter les changements de state si les valeurs n'ont pas changé, et ainsi limiter les recompilations du thème

return {
device: deviceName,
isDesktop: deviceName === 'desktop',
isTablet: deviceName === 'tablet',
isMobile: deviceName === 'mobile',
breakpoints,
};
}

if (deviceName) {
if (deviceName === 'desktop') {
isDesktop = true;
} else if (deviceName === 'tablet') {
isTablet = true;
} else if (deviceName === 'mobile') {
isMobile = true;
}

return {
device: deviceName,
isDesktop,
isTablet,
isMobile,
breakpoints,
};
}
return defaultContext;
};

const getDevice = (screenWidth: number): DeviceType => {
function getDeviceType(): DeviceType {
let currentDevice: DeviceType = 'desktop';
const screenWidth = (window.innerWidth || document.documentElement.clientWidth);

if (screenWidth >= breakpoints.desktop) {
currentDevice = 'desktop';
} else if (screenWidth < breakpoints.desktop && screenWidth > breakpoints.mobile) {
Expand All @@ -58,39 +47,46 @@ const getDevice = (screenWidth: number): DeviceType => {
}

return currentDevice;
};
}

const DeviceContext = createContext<DeviceContextProps>(getDeviceContext());
const DeviceContext = createContext<DeviceContextProps>(getDeviceContext(getDeviceType()));

/**
* @deprecated Use {@link DesignSystem} instead
*/
export const DeviceContextProvider: FunctionComponent<PropsWithChildren<DeviceContextProviderProps>> = ({
children,
staticDevice,
}) => {
const [device, setDevice] = useState<DeviceContextProps>(getDeviceContext(staticDevice));
const deviceType = staticDevice || getDeviceType();

function handleScreenResize(): void {
const screenWidth = (window.innerWidth || document.documentElement.clientWidth);
const currentDevice = getDevice(screenWidth);
const previousDeviceType = useRef<DeviceType>(deviceType);
const previousStaticDevice = useRef<DeviceType | undefined>(staticDevice);
const [deviceContext, setDeviceContext] = useState<DeviceContextProps>(getDeviceContext(deviceType));

setDevice(getDeviceContext(currentDevice));
if (staticDevice !== previousStaticDevice.current) {
previousStaticDevice.current = staticDevice;
setDeviceContext(getDeviceContext(deviceType));
}

const handleScreenResize = useCallback(() => {
const currentDeviceType = getDeviceType();

if (currentDeviceType !== previousDeviceType.current) {
previousDeviceType.current = currentDeviceType;
setDeviceContext(getDeviceContext(currentDeviceType));
}
}, []);

useEffect(() => {
if (!staticDevice) {
handleScreenResize();
window.addEventListener('resize', handleScreenResize);
return () => {
window.removeEventListener('resize', handleScreenResize);
};
}
return undefined;
}, [staticDevice]);
}, [handleScreenResize, staticDevice]);

return (
<DeviceContext.Provider value={device}>
<DeviceContext.Provider value={deviceContext}>
{children}
</DeviceContext.Provider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ interface IntlContextProps {

const IntlContext = createContext<IntlContextProps>({ i18n: createI18n() });

/**
* @deprecated Use {@link DesignSystem} instead
*/
export const IntlProvider: FunctionComponent<PropsWithChildren<IntlProviderProps>> = ({ children, language }) => {
const [i18n] = useState(() => createI18n(language));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { shallow } from 'enzyme';
import { useTheme } from '../../hooks/use-theme';
import { equisoftTheme } from '../../themes';
import { SortButtonIcon } from './sort-button-icon';
import { buildTheme, equisoftThemeCustomization } from '../../themes';

jest.mock('../../hooks/use-theme');

const equisoftTheme = buildTheme(equisoftThemeCustomization);

describe('SortButtonIcon', () => {
beforeEach(() => {
jest.mocked(useTheme).mockReturnValue(equisoftTheme);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { render, shallow } from 'enzyme';
import { buildTheme, Button } from '../..';
import { Button } from '../..';
import { ShadowWrapper } from '../shadow-wrapper/shadow-wrapper';
import { ThemeWrapper } from './theme-wrapper';
import { ThemeCustomization } from '../../themes/theme';
import { ThemeCustomization } from '../../themes';

describe('Theme Wrapper', () => {
test('Returns component with default theme', () => {
Expand All @@ -25,10 +25,8 @@ describe('Theme Wrapper', () => {
},
};

const builtTheme = buildTheme(themeCustomization);

const tree1 = render(
<ThemeWrapper theme={builtTheme}>
<ThemeWrapper themeCustomization={themeCustomization}>
<Button buttonType="primary" />
</ThemeWrapper>,
);
Expand Down
61 changes: 45 additions & 16 deletions packages/react/src/components/theme-wrapper/theme-wrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,68 @@
import { FunctionComponent, PropsWithChildren, ReactNode } from 'react';
import { FunctionComponent, PropsWithChildren } from 'react';
import { ThemeProvider } from 'styled-components';
import { useStyle } from '../../styles';
import { equisoftTheme } from '../../theme';
import { ResolvedTheme } from '../../themes/theme';
import { buildTheme, equisoftThemeCustomization, ThemeCustomization, TokenContext } from '../../themes';
import { ShadowWrapper } from '../shadow-wrapper/shadow-wrapper';
import { DeviceContextProps, useDeviceContext } from '../device-context-provider/device-context-provider';

export interface DisplayPreferences {
// User-provided preferences for display (density, contrast, motion, etc.)
}

export interface ThemeWrapperProps {
/**
* When true, components are mounted in the Shadow DOM
* @default false
*/
isolateStyles?: boolean;
theme?: ResolvedTheme;
themeCustomization?: ThemeCustomization;
displayPreferences?: DisplayPreferences;
}

// Context values should be added in order of importance. Later values will be applied over earlier values.
function getTokenContext(
deviceContext: DeviceContextProps,
// @ts-ignore Future use

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tu pourrais toujours switcher à @ts-expect-error pour être certain de pas l'oublier, mais les chances sont faibles.

// eslint-disable-next-line @typescript-eslint/no-unused-vars
displayPreferences?: DisplayPreferences,
): TokenContext[] {
const context: TokenContext[] = [];

if (deviceContext.isMobile) {
context.push('mobile');
}

if (deviceContext.isTablet) {
context.push('tablet');
}

if (deviceContext.isDesktop) {
context.push('desktop');
}

return context;
}

/**
* @deprecated Use {@link DesignSystem} instead
*/
export const ThemeWrapper: FunctionComponent<PropsWithChildren<ThemeWrapperProps>> = ({
children,
isolateStyles = false,
theme = equisoftTheme,
themeCustomization = equisoftThemeCustomization,
displayPreferences,
}) => {
let content: ReactNode;
if (isolateStyles) {
content = <ShadowWrapper>{children}</ShadowWrapper>;
} else {
content = children;
}
const deviceContext = useDeviceContext();

const resolvedTheme = buildTheme(
themeCustomization,
getTokenContext(deviceContext, displayPreferences),
);

useStyle(isolateStyles);

return (
<ThemeProvider theme={theme}>
{content}
<ThemeProvider theme={resolvedTheme}>
{isolateStyles
? <ShadowWrapper>{children}</ShadowWrapper>
: children}
</ThemeProvider>
);
};
9 changes: 4 additions & 5 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,7 @@ export {
} from './components/password-creation-input/validation-condition';

// Context
export { ThemeWrapper } from './components/theme-wrapper/theme-wrapper';
export { DeviceContextProvider, useDeviceContext } from './components/device-context-provider/device-context-provider';
export { IntlProvider } from './components/internationalization-provider/internationalization-provider';
export { useDeviceContext } from './components/device-context-provider/device-context-provider';

// Lists
export { Listbox, ListboxOption } from './components/listbox/listbox';
Expand Down Expand Up @@ -105,9 +103,10 @@ export { ProgressCircle } from './components/progress-circle/progress-circle';
export { Pagination } from './components/pagination/pagination';

// Themes
export { equisoftTheme, buildTheme } from './themes';
export {
buildTheme, equisoftThemeCustomization, ResolvedTheme as Theme, ThemeCustomization,
} from './themes';
export { injectMainCss } from './styles';
export { ResolvedTheme as Theme, ThemeCustomization } from './themes/theme';

// Hooks
export { useTheme } from './hooks/use-theme';
Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/test-utils/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ import { ThemeWrapper } from '../components/theme-wrapper/theme-wrapper';
export const AllProviders: FunctionComponent<PropsWithChildren<DesignSystemProps>> = ({
children,
language,
theme,
themeCustomization,
staticDevice,
}) => (
<MemoryRouter>
<DesignSystem language={language} staticDevice={staticDevice} theme={theme}>
<DesignSystem language={language} staticDevice={staticDevice} themeCustomization={themeCustomization}>
{children}
</DesignSystem>
</MemoryRouter>
Expand Down
8 changes: 4 additions & 4 deletions packages/react/src/test-utils/testing-library.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { render } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { DesignSystem, DesignSystemProps } from '../components/design-system';
import { equisoftTheme } from '../theme';
import { equisoftThemeCustomization } from '../theme';

export function renderWithProviders(
ui: Parameters<typeof render>[0],
Expand All @@ -12,11 +12,11 @@ export function renderWithProviders(
isolateStyles,
language,
staticDevice,
theme,
themeCustomization,
} = {
isolateStyles: false,
language: 'fr',
theme: equisoftTheme,
themeCustomization: equisoftThemeCustomization,
...wrapperProps,
};
return render(
Expand All @@ -29,7 +29,7 @@ export function renderWithProviders(
isolateStyles={isolateStyles}
language={language}
staticDevice={staticDevice}
theme={theme}
themeCustomization={themeCustomization}
>
{children}
</DesignSystem>
Expand Down
9 changes: 6 additions & 3 deletions packages/react/src/theme.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export { equisoftTheme } from './themes/equisoft';
export { ResolvedTheme, ThemeCustomization } from './themes/theme';
export { buildTheme } from './themes/build-theme';
export {
buildTheme,
equisoftThemeCustomization,
ResolvedTheme,
ThemeCustomization,
} from './themes';
Loading