Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add dark mode support #4548

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
13 changes: 7 additions & 6 deletions packages/orbit-components/src/OrbitProvider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import OrbitProvider from "@kiwicom/orbit-components/lib/OrbitProvider";
After adding import please wrap your application into `OrbitProvider` and you can provide your own [`theme`](https://github.com/kiwicom/orbit/blob/master/.github/theming.md).

```jsx
<OrbitProvider useId={React.useId} theme={ownTheme}>
<OrbitProvider useId={React.useId} theme={ownTheme} colorScheme="system">
<App />
</OrbitProvider>
```
Expand All @@ -18,8 +18,9 @@ After adding import please wrap your application into `OrbitProvider` and you ca

Table below contains all types of the props available in the OrbitProvider component.

| Name | Type | Default | Description |
| :----------- | :----------- | :------ | :------------------------------------------------------------------------------------------------------------------------------------ |
| **children** | `React.Node` | | Your app |
| theme | `[Object]` | | See [`theming`](https://github.com/kiwicom/orbit/blob/master/.github/theming.md) |
| useId | `[Object]` | | If using React 18 or above, use `React.useId`. If not, use `useRandomId` from [`react-uid`](https://www.npmjs.com/package/react-uid). |
| Name | Type | Default | Description |
| :----------- | :------------------------------ | :-------- | :------------------------------------------------------------------------------------------------------------------------------------ |
| **children** | `React.Node` | | Your app |
| theme | `[Object]` | | See [`theming`](https://github.com/kiwicom/orbit/blob/master/.github/theming.md) |
| useId | `[Object]` | | If using React 18 or above, use `React.useId`. If not, use `useRandomId` from [`react-uid`](https://www.npmjs.com/package/react-uid). |
| colorScheme | `"light" \| "dark" \| "system"` | `"light"` | Controls the color scheme of the application. Use "system" to follow the user's system preferences. |
11 changes: 8 additions & 3 deletions packages/orbit-components/src/OrbitProvider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,18 @@ const getCssVarsForWL = (theme: typeof defaultTokens) =>
*
*/

const OrbitProvider = ({ theme, children, useId }: Props) => {
const OrbitProvider = ({ theme, children, useId, colorScheme = "light" }: Props) => {
const effectiveTheme = {
...theme,
colorScheme: colorScheme || theme.colorScheme || "light",
};

return (
<RandomIdProvider useId={useId}>
<style id="orbit-theme-css-vars">
{tokensToCssVars({ tokens: getCssVarsForWL(theme.orbit), cssClass: ":root" })}
{tokensToCssVars({ tokens: getCssVarsForWL(effectiveTheme.orbit), cssClass: ":root" })}
</style>
<ThemeProvider theme={theme}>
<ThemeProvider theme={effectiveTheme}>
<QueryContextProvider>{children}</QueryContextProvider>
</ThemeProvider>
</RandomIdProvider>
Expand Down
1 change: 1 addition & 0 deletions packages/orbit-components/src/OrbitProvider/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export interface Props {
readonly theme: Theme;
readonly children: React.ReactNode;
readonly useId: () => string;
readonly colorScheme?: "light" | "dark" | "system";
}
2 changes: 2 additions & 0 deletions packages/orbit-components/src/defaultTheme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface Theme {
readonly lockScrolling?: boolean;
readonly lockScrollingBarGap?: boolean;
readonly rtl?: boolean;
readonly colorScheme?: "light" | "dark" | "system";
}

const defaultTheme: Theme = {
Expand All @@ -14,6 +15,7 @@ const defaultTheme: Theme = {
lockScrolling: true,
lockScrollingBarGap: false,
rtl: false,
colorScheme: "light",
};

export interface ThemeProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as React from "react";
import { render } from "@testing-library/react";

import { defaultTheme, OrbitProvider } from "../../..";
import { useColorScheme } from "..";

const mockMatchMedia = jest.fn();
window.matchMedia = mockMatchMedia;

const TestComponent = () => {
const isDark = useColorScheme();
return <div data-test="dark-mode">{isDark ? "dark" : "light"}</div>;
};

describe("useColorScheme", () => {
beforeEach(() => {
mockMatchMedia.mockReset();
});

it("should return false for light theme", () => {
mockMatchMedia.mockReturnValue({ matches: false });
const { getByTestId } = render(
<OrbitProvider theme={defaultTheme} useId={React.useId}>
<TestComponent />
</OrbitProvider>,
);
expect(getByTestId("dark-mode")).toHaveTextContent("light");
});

it("should return true for dark theme", () => {
mockMatchMedia.mockReturnValue({ matches: false });
const { getByTestId } = render(
<OrbitProvider theme={{ ...defaultTheme, colorScheme: "dark" }} useId={React.useId}>
<TestComponent />
</OrbitProvider>,
);
expect(getByTestId("dark-mode")).toHaveTextContent("dark");
});

it("should follow system preference when set to system", () => {
mockMatchMedia.mockReturnValue({ matches: true });
const { getByTestId } = render(
<OrbitProvider theme={{ ...defaultTheme, colorScheme: "system" }} useId={React.useId}>
<TestComponent />
</OrbitProvider>,
);
expect(getByTestId("dark-mode")).toHaveTextContent("dark");
});
});
15 changes: 15 additions & 0 deletions packages/orbit-components/src/hooks/useColorScheme/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useTheme } from "../..";

export const useColorScheme = (): boolean => {
const theme = useTheme();
const darkQuery = window.matchMedia("(prefers-color-scheme: dark)");
const prefersDark = darkQuery.matches;

if (theme.colorScheme === "system") {
return prefersDark;
}

return theme.colorScheme === "dark";
};

export default useColorScheme;
1 change: 1 addition & 0 deletions packages/orbit-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ export { default as useLockScrolling } from "./hooks/useLockScrolling";
export { default as useRandomId, useRandomIdSeed } from "./hooks/useRandomId";
export { default as useFocusTrap } from "./hooks/useFocusTrap";
export { default as useInterval } from "./hooks/useInterval";
export { default as useColorScheme } from "./hooks/useColorScheme";

// primitives
export { default as BadgePrimitive } from "./primitives/BadgePrimitive";
Expand Down
18 changes: 18 additions & 0 deletions packages/orbit-components/src/test-utils/DarkModeWrapper.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from "react";

import { OrbitProvider } from "..";
import defaultTheme from "../defaultTheme";

interface Props {
children: React.ReactNode;
}

export const DarkModeWrapper = ({ children }: Props) => {
return (
<OrbitProvider theme={defaultTheme} useId={React.useId} colorScheme="dark">
{children}
</OrbitProvider>
);
};

export default DarkModeWrapper;
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{
"foundation": {
"palette": {
"darkMode": {
"background": {
"value": "#252A31",
"internal": true,
"type": "color"
},
"background-hover": {
"value": "#181B20",
"internal": true,
"type": "color"
},
"background-active": {
"value": "#0B0C0F",
"internal": true,
"type": "color"
},
"surface": {
"value": "#4F5E71",
"internal": true,
"type": "color"
},
"surface-hover": {
"value": "#3E4E63",
"internal": true,
"type": "color"
},
"surface-active": {
"value": "#324256",
"internal": true,
"type": "color"
},
"text": {
"value": "#FFFFFF",
"internal": true,
"type": "color"
},
"text-secondary": {
"value": "#BAC7D5",
"internal": true,
"type": "color"
},
"border": {
"value": "#4F5E71",
"internal": true,
"type": "color"
},
"border-hover": {
"value": "#3E4E63",
"internal": true,
"type": "color"
},
"border-active": {
"value": "#324256",
"internal": true,
"type": "color"
}
}
}
}
}
28 changes: 27 additions & 1 deletion packages/orbit-design-tokens/src/js/defaultFoundation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@ export interface Palette {
blue: Blue;
bundle: Bundle;
cloud: Cloud;
darkMode: {
background: string;
backgroundHover: string;
backgroundActive: string;
surface: string;
surfaceHover: string;
surfaceActive: string;
text: string;
textSecondary: string;
border: string;
borderHover: string;
borderActive: string;
};
green: Green;
ink: Ink;
orange: Orange;
Expand Down Expand Up @@ -307,6 +320,19 @@ const red = {
};
const social = { facebook: "#3B5998", facebookHover: "#385490", facebookActive: "#354F88" };
const white = { normal: "#FFFFFF", normalActive: "#E7ECF1", normalHover: "#F1F4F7" };
const darkMode = {
background: "#252A31",
backgroundHover: "#181B20",
backgroundActive: "#0B0C0F",
surface: "#4F5E71",
surfaceHover: "#3E4E63",
surfaceActive: "#324256",
text: "#FFFFFF",
textSecondary: "#BAC7D5",
border: "#4F5E71",
borderHover: "#3E4E63",
borderActive: "#324256",
};
const borderRadius = {
50: "2px",
100: "4px",
Expand Down Expand Up @@ -365,7 +391,7 @@ const fontFamily = {
const fontSize = { small: "13px", normal: "15px", large: "16px", extraLarge: "18px" };
const lineHeight = { small: "16px", normal: "20px", large: "24px", extraLarge: "24px" };
const fontWeight = { normal: "400", medium: "500", bold: "700" };
const palette = { blue, bundle, cloud, green, ink, orange, product, red, social, white };
const palette = { blue, bundle, cloud, darkMode, green, ink, orange, product, red, social, white };
const foundation = {
palette,
borderRadius,
Expand Down
Loading