Skip to content

Commit

Permalink
feat: support tablet and phone media queries
Browse files Browse the repository at this point in the history
  • Loading branch information
haideralsh committed Oct 30, 2024
1 parent d5767a2 commit 9100a79
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 56 deletions.
2 changes: 1 addition & 1 deletion .storybook/nds-theme/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const StorybookNDSProvider = ({ children }) => {
const [themeVariant] = useLocalStorage<ComponentVariant>("nds-sb-theme-variant", "desktop");

return (
<NDSProvider locale={select("NDSProvider Locale", localeKnobOptions, "en_US")} variant={themeVariant} theme={theme}>
<NDSProvider locale={select("NDSProvider Locale", localeKnobOptions, "en_US")} variant="touch">
{children}
</NDSProvider>
);
Expand Down
20 changes: 16 additions & 4 deletions .storybook/nds-theme/register.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// .storybook/my-addon/register.js

import React from "react";
import React, { useEffect } from "react";
import { addons, types, RenderOptions } from "@storybook/addons";
import { AddonPanel } from "@storybook/components";
import { Box, Flex, NDSProvider, Heading3, Heading2, QuietButton } from "../../src";
import { themes } from "../../src/theme";
import { Box, Flex, NDSProvider, Heading3, Heading2, QuietButton, DefaultNDSThemeType } from "../../src";
import { desktop, themes } from "../../src/theme";
import ThemeKey from "./ThemeKey";
import { ThemeInput, ThemeOption, ThemeSelect } from "./ThemeInput";
import ThemeColorInput from "./ThemeColorInput";
import { useLocalStorage } from "./useLocalStorage/useLocalStorage";
import { ComponentVariant } from "../../src/NDSProvider/ComponentVariantContext";
import useMediaQuery from "../../src/hooks/useMediaQuery";
import { getThemeByVariant } from "../../src/NDSProvider/useNDSTheme";

const ADDON_ID = "ndsThemeAddon";
const PANEL_ID = `${ADDON_ID}/panel`;
Expand All @@ -29,11 +32,20 @@ const composeTheme = (data, theme) => {
const DEFAULT_THEME_VARIANT = "desktop";

const ThemePanel = () => {
const [themeVariant, setThemeVariant] = useLocalStorage("nds-sb-theme-variant", DEFAULT_THEME_VARIANT);
const [themeVariant, setThemeVariant] = useLocalStorage<ComponentVariant>(
"nds-sb-theme-variant",
DEFAULT_THEME_VARIANT
);
const [theme, setTheme] = useLocalStorage("nds-sb-theme", themes[themeVariant], {
serializer: (value) => JSON.stringify(value),
deserializer: (value) => JSON.parse(value),
});
const isTabletSize = useMediaQuery(`(min-width: ${desktop.breakpoints.small})`);

useEffect(() => {
const newTheme = getThemeByVariant(themeVariant, isTabletSize);
setTheme(newTheme);
}, [themeVariant, isTabletSize]);

const onChange = (group, prop) => (e) => {
const value = e.target.value;
Expand Down
33 changes: 33 additions & 0 deletions src/NDSProvider/GlobalStylesComposer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from "react";
import { DefaultNDSThemeType } from "../theme.type";
import Reset from "./Reset";
import ModalStyleOverride from "./ModalStyleOverride";
import GlobalStyles from "./GlobalStyles";

type GlobalStylesComposerProps = {
theme?: DefaultNDSThemeType;
locale?: string;
disableGlobalStyles?: boolean;
children?: React.ReactNode;
};

export default function GlobalStylesComposer({
theme,
locale,
disableGlobalStyles,
children,
}: GlobalStylesComposerProps) {
if (disableGlobalStyles) {
return <>{children}</>;
}

return (
<>
<Reset />
<ModalStyleOverride theme={theme} locale={locale} />
<GlobalStyles theme={theme} locale={locale}>
{children}
</GlobalStyles>
</>
);
}
56 changes: 8 additions & 48 deletions src/NDSProvider/NDSProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,12 @@
import React, { useEffect } from "react";
import { ThemeProvider } from "styled-components";
import { I18nextProvider } from "react-i18next";
import { themes } from "../theme";
import i18n from "../i18n";
import { ThemeType, DefaultNDSThemeType, Breakpoints } from "../theme.type";
import { ThemeType } from "../theme.type";
import { LocaleContext } from "./LocaleContext";
import { mergeThemes } from "./mergeThemes.util";
import GlobalStyles from "./GlobalStyles";
import ModalStyleOverride from "./ModalStyleOverride";
import Reset from "./Reset";
import ComponentVariantContextProvider, { ComponentVariant } from "./ComponentVariantContext";

export const buildBreakPoints = (breakpointsConfig: Readonly<Breakpoints>) => ({
...breakpointsConfig,

// We need the map function as a polyfill because the `variant` function
// from `styled-system` expects the breakpoints
// to be an array and not an object
map: (fn) => Object.values(breakpointsConfig).map(fn),
});
import GlobalStylesComposer from "./GlobalStylesComposer";
import { useNDSTheme } from "./useNDSTheme";

type NDSProviderProps = {
theme?: ThemeType;
Expand All @@ -28,28 +16,8 @@ type NDSProviderProps = {
variant?: ComponentVariant;
};

type AllNDSGlobalStylesProps = {
theme?: DefaultNDSThemeType;
locale?: string;
disableGlobalStyles?: boolean;
children?: any;
};

const AllNDSGlobalStyles = ({ theme, locale, disableGlobalStyles, children }: AllNDSGlobalStylesProps) =>
!disableGlobalStyles ? (
<>
<Reset />
<ModalStyleOverride theme={theme} locale={locale} />
<GlobalStyles theme={theme} locale={locale}>
{children}
</GlobalStyles>
</>
) : (
children
);

function NDSProvider({
theme,
theme: customTheme,
children,
disableGlobalStyles = false,
locale = "en_US",
Expand All @@ -59,24 +27,16 @@ function NDSProvider({
i18n.changeLanguage(locale);
}, [locale]);

if (!(variant in themes)) {
throw new Error(
`Invalid variant "${variant}" provided to NDSProvider. Valid variants are: ${Object.keys(themes).join(", ")}`
);
}

const themeVariant = themes[variant];
const mergedTheme = mergeThemes(themeVariant, theme);
const themeWithBreakpoints = { ...mergedTheme, breakpoints: buildBreakPoints(mergedTheme.breakpoints) };
const theme = useNDSTheme(variant, customTheme);

return (
<LocaleContext.Provider value={{ locale }}>
<ComponentVariantContextProvider variant={variant}>
<AllNDSGlobalStyles theme={themeWithBreakpoints} locale={locale} disableGlobalStyles={disableGlobalStyles}>
<GlobalStylesComposer theme={theme} locale={locale} disableGlobalStyles={disableGlobalStyles}>
<I18nextProvider i18n={i18n}>
<ThemeProvider theme={themeWithBreakpoints}>{children}</ThemeProvider>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</I18nextProvider>
</AllNDSGlobalStyles>
</GlobalStylesComposer>
</ComponentVariantContextProvider>
</LocaleContext.Provider>
);
Expand Down
50 changes: 50 additions & 0 deletions src/NDSProvider/useNDSTheme.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { useState, useEffect } from "react";
import { desktop, themes } from "../theme";
import { ThemeType, DefaultNDSThemeType, Breakpoints } from "../theme.type";
import useMediaQuery from "../hooks/useMediaQuery";
import { mergeThemes } from "./mergeThemes.util";
import { ComponentVariant } from "./ComponentVariantContext";

const THEME_VARIANTS: ReadonlySet<ComponentVariant> = new Set(["desktop", "touch"]);

export const buildBreakPoints = (breakpointsConfig: Readonly<Breakpoints>) => ({
...breakpointsConfig,
map: function <T>(fn: (value: string) => T) {
return Object.values(breakpointsConfig).map(fn);
},
});

const validateVariantOrThrow = (variant: ComponentVariant): void => {
if (!THEME_VARIANTS.has(variant)) {
throw new Error(
`Invalid variant "${variant}" provided to NDSProvider. Valid variants are: ${Array.from(THEME_VARIANTS).join(
", "
)}.`
);
}
};

export const getThemeByVariant = (variant: ComponentVariant, isTabletSize: boolean): DefaultNDSThemeType => {
if (variant === "touch") {
return isTabletSize ? themes.tablet : themes.phone;
}
return themes.desktop;
};

export function useNDSTheme(variant: ComponentVariant = "desktop", customTheme?: ThemeType): DefaultNDSThemeType {
validateVariantOrThrow(variant);

const [themeVariant, setThemeVariant] = useState<DefaultNDSThemeType>(desktop);
const isTabletSize = useMediaQuery(`(min-width: ${desktop.breakpoints.small})`);

useEffect(() => {
const newTheme = getThemeByVariant(variant, isTabletSize);
setThemeVariant(newTheme);
}, [variant, isTabletSize]);

const mergedTheme = mergeThemes(themeVariant, customTheme);
return {
...mergedTheme,
breakpoints: buildBreakPoints(mergedTheme.breakpoints),
};
}
71 changes: 68 additions & 3 deletions src/theme.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as tokens from "@nulogy/tokens";
import type { DefaultNDSThemeType } from "./theme.type";

type ThemeKey = "desktop" | "touch";
type ThemeKey = "desktop" | "tablet" | "phone";

const BASE_THEME: DefaultNDSThemeType = {
colors: {
Expand Down Expand Up @@ -135,7 +135,7 @@ const themes: Record<ThemeKey, DefaultNDSThemeType> = {
desktop: {
...BASE_THEME,
},
touch: {
tablet: {
...BASE_THEME,
fontSizes: {
smaller: "18px",
Expand Down Expand Up @@ -200,7 +200,72 @@ const themes: Record<ThemeKey, DefaultNDSThemeType> = {
rounded: "9999px",
},
},
phone: {
...BASE_THEME,
fontSizes: {
smaller: "13.5px",
small: "15.75px",
medium: "18px",
large: "20.25px",
larger: "22.5px",
largest: "31.5px",
heading1: "22.5px",
heading2: "18px",
heading3: "15.75px",
heading4: "13.5px",
},
lineHeights: {
base: "1.33333333",
relaxed: "1.66666667",
smallTextBase: "1.33333333",
smallTextCompressed: "1.33333333",
smallerText: "1.33333333",
heading1: "1.33333333",
heading2: "1.33333333",
heading3: "1.33333333",
heading4: "1.33333333",
title: "1.33333333",
sectionTitle: "1.33333333",
subsectionTitle: "1.33333333",
},
space: {
none: "0px",
half: "3.6px",
x0_5: "3.6px",
x0: "0px",
x1: "7.2px",
x1_5: "10.8",
x2: "14.4px",
x2_5: "18px",
x3: "21.6px",
x4: "28.8px",
x5: "36px",
x6: "43.2px",
x8: "50.4px",
},
sizes: {
none: "0px",
half: "3.6px",
x0_5: "3.6px",
x0: "0px",
x1: "7.2px",
x1_5: "10.8",
x2: "14.4px",
x2_5: "18px",
x3: "21.6px",
x4: "28.8px",
x5: "36px",
x6: "43.2px",
x8: "50.4px",
},
radii: {
small: "1.8px",
medium: "3.6px",
circle: "50%",
rounded: "9999px",
},
},
};

export { themes };
export const { desktop, touch } = themes;
export const { desktop, tablet, phone } = themes;

0 comments on commit 9100a79

Please sign in to comment.