From 4a0d9586b3d812043852a01a3143dbf183cb7827 Mon Sep 17 00:00:00 2001 From: Arne Vogt Date: Wed, 30 Aug 2023 17:42:48 +0200 Subject: [PATCH 1/8] allow custom theme for app --- src/packages/chakra-integration/Provider.tsx | 18 +++-- src/packages/runtime/CustomElement.ts | 11 ++- .../react-integration/ReactIntegration.tsx | 7 +- .../chakra-sample/chakra-app/SampleUI.tsx | 5 +- src/samples/chakra-sample/chakra-app/app.ts | 2 + .../chakra-sample/chakra-app/theme/theme.ts | 70 +++++++++++++++++++ 6 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 src/samples/chakra-sample/chakra-app/theme/theme.ts diff --git a/src/packages/chakra-integration/Provider.tsx b/src/packages/chakra-integration/Provider.tsx index 30c57b0f..0e1944d5 100644 --- a/src/packages/chakra-integration/Provider.tsx +++ b/src/packages/chakra-integration/Provider.tsx @@ -31,6 +31,11 @@ export type CustomChakraProviderProps = PropsWithChildren<{ * Configures the color mode of the application. */ colorMode?: "light" | "dark"; + + /** + * chakra theming object + */ + theme?: Record; }>; // todo min-height vs height @@ -49,7 +54,7 @@ const defaultStyles = ` font-feature-settings: 'kern'; }`; -const theme = extendTheme({ +const rootStyles = { styles: { global: { // Apply the same styles to the application root node that chakra would usually apply to the html and body. @@ -58,7 +63,7 @@ const theme = extendTheme({ (baseTheme.styles.global as Record).body } } -}); +}; // https://github.dev/chakra-ui/chakra-ui/blob/80971001d7b77d02d5f037487a37237ded315480/packages/components/color-mode/src/color-mode.utils.ts#L3-L6 const colorModeClassnames = { @@ -70,7 +75,8 @@ const colorModeClassnames = { export const CustomChakraProvider: FC = ({ container, colorMode, - children + children, + theme }) => { /* Chakra integration internals: @@ -141,10 +147,14 @@ export const CustomChakraProvider: FC = ({ }, [mode]); const ColorMode = mode === "light" ? LightMode : DarkMode; + //apply custom theme or Chakra UI default theme + let customTheme = theme ? extendTheme(theme) : baseTheme; + customTheme = extendTheme(rootStyles, customTheme); + return (
- + diff --git a/src/packages/runtime/CustomElement.ts b/src/packages/runtime/CustomElement.ts index 221f35c2..64ad59e7 100644 --- a/src/packages/runtime/CustomElement.ts +++ b/src/packages/runtime/CustomElement.ts @@ -70,6 +70,11 @@ export interface CustomElementOptions { * Defaults to `false` in production mode and `true` during development to make testing easier. */ openShadowRoot?: boolean; + + /** + * Chakra theming object + */ + theme?: Record; } /** @@ -326,7 +331,11 @@ class ElementState { } private render() { - this.reactIntegration?.render(this.options.component ?? emptyComponent, {}); + console.log("options"); + console.log(this.options); + this.reactIntegration?.render(this.options.component ?? emptyComponent, { + theme: this.options.theme + }); } private initStyles() { diff --git a/src/packages/runtime/react-integration/ReactIntegration.tsx b/src/packages/runtime/react-integration/ReactIntegration.tsx index c56eba12..4a605ec4 100644 --- a/src/packages/runtime/react-integration/ReactIntegration.tsx +++ b/src/packages/runtime/react-integration/ReactIntegration.tsx @@ -86,9 +86,14 @@ export class ReactIntegration { } render(Component: ComponentType, props: Record) { + const customTheme = props.theme as Record; this.root.render( - + diff --git a/src/samples/chakra-sample/chakra-app/SampleUI.tsx b/src/samples/chakra-sample/chakra-app/SampleUI.tsx index 351f6b1c..ae9a3fda 100644 --- a/src/samples/chakra-sample/chakra-app/SampleUI.tsx +++ b/src/samples/chakra-sample/chakra-app/SampleUI.tsx @@ -131,7 +131,7 @@ function PortalExample() { function TooltipExample() { return ( - + ); } @@ -140,7 +140,6 @@ function ToastExample() { const toast = useToast(); return ( Date: Fri, 1 Sep 2023 10:54:56 +0200 Subject: [PATCH 2/8] adjust base theme --- src/packages/chakra-integration/Provider.tsx | 7 +++++-- src/packages/chakra-integration/theme/theme.ts | 16 ++++++++++++++++ .../chakra-sample/chakra-app/SampleUI.tsx | 4 ++-- .../chakra-sample/chakra-app/theme/theme.ts | 6 +----- 4 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 src/packages/chakra-integration/theme/theme.ts diff --git a/src/packages/chakra-integration/Provider.tsx b/src/packages/chakra-integration/Provider.tsx index 0e1944d5..110ca389 100644 --- a/src/packages/chakra-integration/Provider.tsx +++ b/src/packages/chakra-integration/Provider.tsx @@ -17,6 +17,7 @@ import createCache, { EmotionCache } from "@emotion/cache"; import { CacheProvider, Global } from "@emotion/react"; import { FC, PropsWithChildren, useEffect, useRef } from "react"; import { PortalRootProvider } from "./PortalFix"; +import { theme as trailsDefaultTheme } from "./theme/theme"; export type CustomChakraProviderProps = PropsWithChildren<{ /** @@ -148,8 +149,10 @@ export const CustomChakraProvider: FC = ({ const ColorMode = mode === "light" ? LightMode : DarkMode; //apply custom theme or Chakra UI default theme - let customTheme = theme ? extendTheme(theme) : baseTheme; - customTheme = extendTheme(rootStyles, customTheme); + let customTheme = extendTheme(trailsDefaultTheme, rootStyles, baseTheme); //always add trails defaults and root styles to chakra base theme + if (theme) { + customTheme = extendTheme(theme, customTheme); //merge with custom theme if provided + } return (
diff --git a/src/packages/chakra-integration/theme/theme.ts b/src/packages/chakra-integration/theme/theme.ts new file mode 100644 index 00000000..c2e8df00 --- /dev/null +++ b/src/packages/chakra-integration/theme/theme.ts @@ -0,0 +1,16 @@ +// SPDX-FileCopyrightText: con terra GmbH and contributors +// SPDX-License-Identifier: Apache-2.0 +export const theme = { + components: {}, + semanticTokens: { + colors: { + background_primary: "blue.300", + background_secondary: "blue.200", + font_primary: "gray.900", + font_secondary: "gray.50", + error: "red.500", + success: "green.500", + highlight: "yellow.300" + } + } +}; diff --git a/src/samples/chakra-sample/chakra-app/SampleUI.tsx b/src/samples/chakra-sample/chakra-app/SampleUI.tsx index ae9a3fda..52659697 100644 --- a/src/samples/chakra-sample/chakra-app/SampleUI.tsx +++ b/src/samples/chakra-sample/chakra-app/SampleUI.tsx @@ -68,7 +68,7 @@ export function SampleUI() { function LinkComponent() { return ( - + This is a{" "} link to Chakra's Design system @@ -86,7 +86,7 @@ function ComponentStack() { spacing="24px" align="stretch" > - + diff --git a/src/samples/chakra-sample/chakra-app/theme/theme.ts b/src/samples/chakra-sample/chakra-app/theme/theme.ts index 317a523f..5b3d0a4b 100644 --- a/src/samples/chakra-sample/chakra-app/theme/theme.ts +++ b/src/samples/chakra-sample/chakra-app/theme/theme.ts @@ -60,11 +60,7 @@ export const theme = { }, semanticTokens: { colors: { - error: { - default: "red.500", - _dark: "red.400" - }, - success: "green.500" + error: "orange.500" } } }; From ded687271e8ddd2312d3749c205332f15098d432 Mon Sep 17 00:00:00 2001 From: Arne Vogt Date: Tue, 5 Sep 2023 18:19:31 +0200 Subject: [PATCH 3/8] clean themes --- src/packages/chakra-integration/Provider.tsx | 3 +- .../chakra-integration/theme/theme.ts | 44 +++++++-- src/packages/runtime/CustomElement.ts | 3 +- .../react-integration/ReactIntegration.tsx | 1 + .../chakra-sample/chakra-app/SampleUI.tsx | 44 +++------ .../chakra-sample/chakra-app/TableExample.tsx | 2 +- .../chakra-sample/chakra-app/theme/theme.ts | 93 ++++++++++--------- 7 files changed, 102 insertions(+), 88 deletions(-) diff --git a/src/packages/chakra-integration/Provider.tsx b/src/packages/chakra-integration/Provider.tsx index 110ca389..a67b0395 100644 --- a/src/packages/chakra-integration/Provider.tsx +++ b/src/packages/chakra-integration/Provider.tsx @@ -36,6 +36,7 @@ export type CustomChakraProviderProps = PropsWithChildren<{ /** * chakra theming object */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any theme?: Record; }>; @@ -149,7 +150,7 @@ export const CustomChakraProvider: FC = ({ const ColorMode = mode === "light" ? LightMode : DarkMode; //apply custom theme or Chakra UI default theme - let customTheme = extendTheme(trailsDefaultTheme, rootStyles, baseTheme); //always add trails defaults and root styles to chakra base theme + let customTheme = extendTheme(rootStyles, trailsDefaultTheme, baseTheme); //always add trails defaults and root styles to chakra base theme if (theme) { customTheme = extendTheme(theme, customTheme); //merge with custom theme if provided } diff --git a/src/packages/chakra-integration/theme/theme.ts b/src/packages/chakra-integration/theme/theme.ts index c2e8df00..920cb900 100644 --- a/src/packages/chakra-integration/theme/theme.ts +++ b/src/packages/chakra-integration/theme/theme.ts @@ -1,16 +1,40 @@ // SPDX-FileCopyrightText: con terra GmbH and contributors // SPDX-License-Identifier: Apache-2.0 export const theme = { - components: {}, + styles: { + //add global css styles here + /*global:{ + ".chakra-host": { + } + }*/ + }, + colors: { + //define colors and color schemes here/ + /*primary: { + 50: "#defffd", + 100: "#b3fffa", + 200: "#86feee", + 300: "#5bfedd", + 400: "#3efec9", + 500: "#32e5a6", + 600: "#23b277", + 700: "#147f4c", + 800: "#004d23", + 900: "#001b0a", + } */ + }, + components: { + //define component specific styling here + /*Button: { + defaultProps: { + colorScheme: "primary" + }*/ + }, semanticTokens: { - colors: { - background_primary: "blue.300", - background_secondary: "blue.200", - font_primary: "gray.900", - font_secondary: "gray.50", - error: "red.500", - success: "green.500", - highlight: "yellow.300" - } + //define sematinc tokens here + /*colors: { + background_primary: "primary.300", + background_secondary: "primary.500", + }*/ } }; diff --git a/src/packages/runtime/CustomElement.ts b/src/packages/runtime/CustomElement.ts index 64ad59e7..33993182 100644 --- a/src/packages/runtime/CustomElement.ts +++ b/src/packages/runtime/CustomElement.ts @@ -74,6 +74,7 @@ export interface CustomElementOptions { /** * Chakra theming object */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any theme?: Record; } @@ -331,8 +332,6 @@ class ElementState { } private render() { - console.log("options"); - console.log(this.options); this.reactIntegration?.render(this.options.component ?? emptyComponent, { theme: this.options.theme }); diff --git a/src/packages/runtime/react-integration/ReactIntegration.tsx b/src/packages/runtime/react-integration/ReactIntegration.tsx index 4a605ec4..647dc6a4 100644 --- a/src/packages/runtime/react-integration/ReactIntegration.tsx +++ b/src/packages/runtime/react-integration/ReactIntegration.tsx @@ -86,6 +86,7 @@ export class ReactIntegration { } render(Component: ComponentType, props: Record) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any const customTheme = props.theme as Record; this.root.render( diff --git a/src/samples/chakra-sample/chakra-app/SampleUI.tsx b/src/samples/chakra-sample/chakra-app/SampleUI.tsx index 52659697..c885393c 100644 --- a/src/samples/chakra-sample/chakra-app/SampleUI.tsx +++ b/src/samples/chakra-sample/chakra-app/SampleUI.tsx @@ -68,9 +68,9 @@ export function SampleUI() { function LinkComponent() { return ( - + This is a{" "} - + link to Chakra's Design system @@ -79,14 +79,8 @@ function LinkComponent() { function ComponentStack() { return ( - } - spacing="24px" - align="stretch" - > - + } spacing="24px" align="stretch"> + @@ -110,7 +104,7 @@ function ComponentStack() { - + @@ -119,7 +113,7 @@ function ComponentStack() { function PortalExample() { return ( - + Portal Example: This is box and displayed here. Scroll/Look down to see the portal that is added at the end of document.body. The Portal is part of this Box. @@ -131,7 +125,7 @@ function PortalExample() { function TooltipExample() { return ( - + ); } @@ -172,9 +166,7 @@ function AlertDialogExample() { return ( <> - + @@ -188,10 +180,10 @@ function AlertDialogExample() { - - @@ -207,9 +199,7 @@ function ModalExample() { return ( <> - + @@ -219,9 +209,7 @@ function ModalExample() { This is a modal text! - + @@ -256,7 +244,7 @@ function DrawerExample() { - + @@ -269,7 +257,7 @@ function PopoverExample() { <> - + @@ -281,9 +269,7 @@ function PopoverExample() { - + diff --git a/src/samples/chakra-sample/chakra-app/TableExample.tsx b/src/samples/chakra-sample/chakra-app/TableExample.tsx index 72c962ad..a1e102f2 100644 --- a/src/samples/chakra-sample/chakra-app/TableExample.tsx +++ b/src/samples/chakra-sample/chakra-app/TableExample.tsx @@ -14,7 +14,7 @@ import { export function TableExampleComponent() { return ( - +
This is the table cation diff --git a/src/samples/chakra-sample/chakra-app/theme/theme.ts b/src/samples/chakra-sample/chakra-app/theme/theme.ts index 5b3d0a4b..52d0971a 100644 --- a/src/samples/chakra-sample/chakra-app/theme/theme.ts +++ b/src/samples/chakra-sample/chakra-app/theme/theme.ts @@ -2,65 +2,68 @@ // SPDX-License-Identifier: Apache-2.0 export const theme = { colors: { - palegreen: { + primary: { 50: "#defffd", - 100: "#defffd", - 200: "#defffd", - 300: "#defffd", - 400: "#defffd", - 500: "#defffd", - 600: "#defffd", - 700: "#defffd", - 800: "#defffd", - 900: "#defffd" - }, - myblack: { - 50: "#f2f2f2", - 100: "#d9d9d9", - 200: "#bfbfbf", - 300: "#a6a6a6", - 400: "#8c8c8c", - 500: "#737373", - 600: "#595959", - 700: "#404040", - 800: "#262626", - 900: "#0d0d0d" - }, - mybrown: { - 50: "#feeded", - 100: "#e4d0d0", - 200: "#ccb2b2", - 300: "#b79494", - 400: "#a27676", - 500: "#885c5c", - 600: "#6a4747", - 700: "#4d3333", - 800: "#301d1d", - 900: "#170707" + 100: "#b3fffa", + 200: "#86feee", + 300: "#5bfedd", + 400: "#3efec9", + 500: "#32e5a6", + 600: "#23b277", + 700: "#147f4c", + 800: "#004d23", + 900: "#001b0a" } }, + fonts: { + heading: "Helvetica" + }, components: { Button: { defaultProps: { - //only colorScheme, variant, size - colorScheme: "palegreen" //default color must provided here - //variant: "brown" - }, - baseStyle: { - fontWeight: "italic", // Normally, it is "semibold" - bg: "red.500", //only applies if variant attribute is set - color: "myblack.500" + colorScheme: "primary" }, variants: { - brown: { - bg: "mybrown.500" + cancel: { + color: "font_inverse", + bg: "error", + _hover: { backgroundColor: "error_hover" } } } + }, + Link: { + baseStyle: { + color: "font_link" + } + }, + Divider: { + baseStyle: { + borderColor: "border" + } } }, semanticTokens: { colors: { - error: "orange.500" + "background_primary": "primary.300", + "background_secondary": "primary.500", + "placeholder": "primary.100", + "font_primary": "black", + "font_secondary": "grey.500", + "font_inverse": "white", + "font_link": "yellow.300", + "border": "black", + "error": "red.500", + "error_hover": "red.600", + "success": "green.500", + "highlight": "yellow.300", + //oderride internal chakra themeing variables + "chakra-body-bg": "background_primary", + "chakra-subtle-bg": "background_secondary", + "chakra-body-text": "font_primary", + "chakra-subtle-text": "font_secondary", + "chakra-inverse-text": "font_inverse", + "chakra-border-color": "border", + "chakra-placeholder-color": "placeholder" } } }; From 47b1525a25c7248d60f08b83fd743cb42ec869b4 Mon Sep 17 00:00:00 2001 From: Michael Beckemeyer Date: Mon, 18 Sep 2023 15:45:17 +0200 Subject: [PATCH 4/8] Refactor: simplify theme implementation; use extendTheme() everywhere --- src/packages/chakra-integration/Provider.tsx | 28 +--- src/packages/chakra-integration/index.tsx | 1 + .../chakra-integration/theme/theme.ts | 64 ++++++--- .../chakra-sample/chakra-app/theme/theme.ts | 126 +++++++++--------- 4 files changed, 115 insertions(+), 104 deletions(-) diff --git a/src/packages/chakra-integration/Provider.tsx b/src/packages/chakra-integration/Provider.tsx index a67b0395..ff68581b 100644 --- a/src/packages/chakra-integration/Provider.tsx +++ b/src/packages/chakra-integration/Provider.tsx @@ -4,10 +4,8 @@ import { CSSReset, DarkMode, EnvironmentProvider, - extendTheme, GlobalStyle, LightMode, - theme as baseTheme, ThemeProvider, ToastOptionProvider, ToastProvider, @@ -34,10 +32,9 @@ export type CustomChakraProviderProps = PropsWithChildren<{ colorMode?: "light" | "dark"; /** - * chakra theming object + * Chakra theming object. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - theme?: Record; + theme?: Record; }>; // todo min-height vs height @@ -56,17 +53,6 @@ const defaultStyles = ` font-feature-settings: 'kern'; }`; -const rootStyles = { - styles: { - global: { - // Apply the same styles to the application root node that chakra would usually apply to the html and body. - ".chakra-host": - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (baseTheme.styles.global as Record).body - } - } -}; - // https://github.dev/chakra-ui/chakra-ui/blob/80971001d7b77d02d5f037487a37237ded315480/packages/components/color-mode/src/color-mode.utils.ts#L3-L6 const colorModeClassnames = { light: "chakra-ui-light", @@ -78,7 +64,7 @@ export const CustomChakraProvider: FC = ({ container, colorMode, children, - theme + theme = trailsDefaultTheme }) => { /* Chakra integration internals: @@ -149,16 +135,10 @@ export const CustomChakraProvider: FC = ({ }, [mode]); const ColorMode = mode === "light" ? LightMode : DarkMode; - //apply custom theme or Chakra UI default theme - let customTheme = extendTheme(rootStyles, trailsDefaultTheme, baseTheme); //always add trails defaults and root styles to chakra base theme - if (theme) { - customTheme = extendTheme(theme, customTheme); //merge with custom theme if provided - } - return (
- + diff --git a/src/packages/chakra-integration/index.tsx b/src/packages/chakra-integration/index.tsx index 467e51a3..b351ad36 100644 --- a/src/packages/chakra-integration/index.tsx +++ b/src/packages/chakra-integration/index.tsx @@ -9,3 +9,4 @@ export { FixedPortal as Portal, FixedTooltip as Tooltip } from "./PortalFix"; +export { theme } from "./theme/theme"; diff --git a/src/packages/chakra-integration/theme/theme.ts b/src/packages/chakra-integration/theme/theme.ts index 920cb900..acf95269 100644 --- a/src/packages/chakra-integration/theme/theme.ts +++ b/src/packages/chakra-integration/theme/theme.ts @@ -1,16 +1,38 @@ // SPDX-FileCopyrightText: con terra GmbH and contributors // SPDX-License-Identifier: Apache-2.0 -export const theme = { - styles: { - //add global css styles here - /*global:{ - ".chakra-host": { +import { theme as baseTheme, extendTheme } from "@chakra-ui/react"; + +/** + * Base theme for open pioneer trails applications. + * + * All custom themes should extend this theme: + * + * ```ts + * import { theme as baseTheme, extendTheme } from "@open-pioneer/chakra-integration"; + * + * export const theme = extendTheme({ + * // Your overrides + * }, baseTheme); + * ``` + * + * NOTE: this API is still _experimental_. The base theme is likely to move into a different package. + * + * @experimental + */ +export const theme = extendTheme( + { + styles: { + //add global css styles here + global: { + // Apply the same styles to the application root node that chakra would usually apply to the html and body. + ".chakra-host": + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (baseTheme.styles.global as Record).body } - }*/ - }, - colors: { - //define colors and color schemes here/ - /*primary: { + }, + colors: { + //define colors and color schemes here/ + /*primary: { 50: "#defffd", 100: "#b3fffa", 200: "#86feee", @@ -22,19 +44,21 @@ export const theme = { 800: "#004d23", 900: "#001b0a", } */ - }, - components: { - //define component specific styling here - /*Button: { + }, + components: { + //define component specific styling here + /*Button: { defaultProps: { colorScheme: "primary" }*/ - }, - semanticTokens: { - //define sematinc tokens here - /*colors: { + }, + semanticTokens: { + //define sematinc tokens here + /*colors: { background_primary: "primary.300", background_secondary: "primary.500", }*/ - } -}; + } + }, + baseTheme +); diff --git a/src/samples/chakra-sample/chakra-app/theme/theme.ts b/src/samples/chakra-sample/chakra-app/theme/theme.ts index 52d0971a..2945a196 100644 --- a/src/samples/chakra-sample/chakra-app/theme/theme.ts +++ b/src/samples/chakra-sample/chakra-app/theme/theme.ts @@ -1,69 +1,75 @@ // SPDX-FileCopyrightText: con terra GmbH and contributors // SPDX-License-Identifier: Apache-2.0 -export const theme = { - colors: { - primary: { - 50: "#defffd", - 100: "#b3fffa", - 200: "#86feee", - 300: "#5bfedd", - 400: "#3efec9", - 500: "#32e5a6", - 600: "#23b277", - 700: "#147f4c", - 800: "#004d23", - 900: "#001b0a" - } - }, - fonts: { - heading: "Helvetica" - }, - components: { - Button: { - defaultProps: { - colorScheme: "primary" - }, - variants: { - cancel: { - color: "font_inverse", - bg: "error", - _hover: { backgroundColor: "error_hover" } - } +import { extendTheme, theme as baseTheme } from "@open-pioneer/chakra-integration"; + +export const theme = extendTheme( + { + colors: { + primary: { + 50: "#defffd", + 100: "#b3fffa", + 200: "#86feee", + 300: "#5bfedd", + 400: "#3efec9", + 500: "#32e5a6", + 600: "#23b277", + 700: "#147f4c", + 800: "#004d23", + 900: "#001b0a" } }, - Link: { - baseStyle: { - color: "font_link" + fonts: { + heading: "Helvetica" + }, + components: { + Button: { + defaultProps: { + colorScheme: "primary" + }, + variants: { + cancel: { + color: "font_inverse", + bg: "error", + _hover: { backgroundColor: "error_hover" } + } + } + }, + Link: { + baseStyle: { + color: "font_link" + } + }, + Divider: { + baseStyle: { + borderColor: "border" + } } }, - Divider: { - baseStyle: { - borderColor: "border" + semanticTokens: { + colors: { + "background_primary": "primary.300", + "background_secondary": "primary.500", + "placeholder": "primary.100", + "font_primary": "black", + "font_secondary": "grey.500", + "font_inverse": "white", + "font_link": "yellow.300", + "border": "black", + "error": "red.500", + "error_hover": "red.600", + "success": "green.500", + "highlight": "yellow.300", + + // override internal chakra theming variables + "chakra-body-bg": "background_primary", + "chakra-subtle-bg": "background_secondary", + "chakra-body-text": "font_primary", + "chakra-subtle-text": "font_secondary", + "chakra-inverse-text": "font_inverse", + "chakra-border-color": "border", + "chakra-placeholder-color": "placeholder" } } }, - semanticTokens: { - colors: { - "background_primary": "primary.300", - "background_secondary": "primary.500", - "placeholder": "primary.100", - "font_primary": "black", - "font_secondary": "grey.500", - "font_inverse": "white", - "font_link": "yellow.300", - "border": "black", - "error": "red.500", - "error_hover": "red.600", - "success": "green.500", - "highlight": "yellow.300", - //oderride internal chakra themeing variables - "chakra-body-bg": "background_primary", - "chakra-subtle-bg": "background_secondary", - "chakra-body-text": "font_primary", - "chakra-subtle-text": "font_secondary", - "chakra-inverse-text": "font_inverse", - "chakra-border-color": "border", - "chakra-placeholder-color": "placeholder" - } - } -}; + baseTheme +); From 9046113414d4f9a7e10d44de90d91189274c0f45 Mon Sep 17 00:00:00 2001 From: Michael Beckemeyer Date: Mon, 18 Sep 2023 16:01:03 +0200 Subject: [PATCH 5/8] Add test for `theme` parameter. Also removes the (unused) props of the application root component to avoid confusion. --- src/packages/runtime/CustomElement.ts | 10 ++-- .../ReactIntegration.test.ts | 60 +++++++++++++++---- .../react-integration/ReactIntegration.tsx | 11 ++-- 3 files changed, 57 insertions(+), 24 deletions(-) diff --git a/src/packages/runtime/CustomElement.ts b/src/packages/runtime/CustomElement.ts index 33993182..d15d1890 100644 --- a/src/packages/runtime/CustomElement.ts +++ b/src/packages/runtime/CustomElement.ts @@ -72,10 +72,9 @@ export interface CustomElementOptions { openShadowRoot?: boolean; /** - * Chakra theming object + * Chakra theming object. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - theme?: Record; + theme?: Record; } /** @@ -321,6 +320,7 @@ class ElementState { this.reactIntegration = new ReactIntegration({ rootNode: container, container: shadowRoot, + theme: options.theme, serviceLayer, packages }); @@ -332,9 +332,7 @@ class ElementState { } private render() { - this.reactIntegration?.render(this.options.component ?? emptyComponent, { - theme: this.options.theme - }); + this.reactIntegration?.render(this.options.component ?? emptyComponent); } private initStyles() { diff --git a/src/packages/runtime/react-integration/ReactIntegration.test.ts b/src/packages/runtime/react-integration/ReactIntegration.test.ts index 46aca5b5..c98cd51f 100644 --- a/src/packages/runtime/react-integration/ReactIntegration.test.ts +++ b/src/packages/runtime/react-integration/ReactIntegration.test.ts @@ -6,17 +6,18 @@ import { createElement } from "react"; import { beforeEach, expect, it } from "vitest"; import { usePropertiesInternal, useServiceInternal, useServicesInternal } from "./hooks"; -import { findByText } from "@testing-library/dom"; +import { findByTestId, findByText } from "@testing-library/dom"; +import { act } from "@testing-library/react"; import { Service, ServiceConstructor } from "../Service"; // eslint-disable-next-line import/no-relative-packages import { UIWithProperties, UIWithService, UIWithServices } from "./test-data/test-package/UI"; import { ServiceLayer } from "../service-layer/ServiceLayer"; import { ReactIntegration } from "./ReactIntegration"; -import { act } from "react-dom/test-utils"; import { PackageRepr } from "../service-layer/PackageRepr"; import { createConstructorFactory, ServiceRepr } from "../service-layer/ServiceRepr"; import { InterfaceSpec, ReferenceSpec } from "../service-layer/InterfaceSpec"; import { createEmptyI18n, PackageIntl } from "../i18n"; +import { useTheme } from "@open-pioneer/chakra-integration"; interface TestProvider { value: string; @@ -46,7 +47,7 @@ it("should allow access to service via react hook", async () => { }); act(() => { - integration.render(TestComponent, {}); + integration.render(TestComponent); }); const node = await findByText(wrapper, "Hello TEST"); @@ -65,7 +66,7 @@ it("should get error when using undefined service", async () => { expect(() => { act(() => { - integration.render(TestComponent, {}); + integration.render(TestComponent); }); }).toThrowErrorMatchingSnapshot(); }); @@ -92,7 +93,7 @@ it("should allow access to service with qualifier via react hook", async () => { }); act(() => { - integration.render(TestComponent, {}); + integration.render(TestComponent); }); const node = await findByText(wrapper, "Hello TEST"); @@ -122,7 +123,7 @@ it("should deny access to service when the qualifier does not match", async () = expect(() => { act(() => { - integration.render(TestComponent, {}); + integration.render(TestComponent); }); }).toThrowErrorMatchingSnapshot(); }); @@ -165,7 +166,7 @@ it("should allow access to all services via react hook", async () => { }); act(() => { - integration.render(TestComponent, {}); + integration.render(TestComponent); }); const node = await findByText(wrapper, /^Joined Values:/); @@ -188,7 +189,7 @@ it("should deny access to all services if declaration is missing", async () => { expect(() => { act(() => { - integration.render(TestComponent, {}); + integration.render(TestComponent); }); }).toThrowErrorMatchingSnapshot(); }); @@ -206,7 +207,7 @@ it("should be able to read properties from react component", async () => { }); act(() => { - integration.render(TestComponent, {}); + integration.render(TestComponent); }); const node = await findByText(wrapper, "Hello USER"); @@ -230,7 +231,7 @@ it("should provide the autogenerated useService hook", async () => { }); act(() => { - integration.render(UIWithService, {}); + integration.render(UIWithService); }); const node = await findByText(wrapper, /^Test-UI:/); @@ -261,7 +262,7 @@ it("should provide the autogenerated useServices hook", async () => { }); act(() => { - integration.render(UIWithServices, {}); + integration.render(UIWithServices); }); const node = await findByText(wrapper, /^Test-UI:/); @@ -278,7 +279,7 @@ it("should provide the autogenerated useProperties hook", async () => { }); act(() => { - integration.render(UIWithProperties, {}); + integration.render(UIWithProperties); }); const node = await findByText(wrapper, /^Test-UI:/); @@ -297,11 +298,42 @@ it("should throw error when requesting properties from an unknown package", asyn expect(() => { act(() => { - integration.render(TestComponent, {}); + integration.render(TestComponent); }); }).toThrowErrorMatchingSnapshot(); }); +it("should apply the configured chakra theme", async () => { + const testTheme = { + colors: { + dummyColor: "#123456" + } + }; + const { integration, wrapper } = createIntegration({ + disablePackage: true, + theme: testTheme + }); + + function TestComponent() { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const theme = useTheme() as any; + return createElement( + "div", + { + "data-testid": "test-div" + }, + `Color: ${theme.colors.dummyColor}` + ); + } + + act(() => { + integration.render(TestComponent); + }); + + const node = await findByTestId(wrapper, "test-div"); + expect(node.textContent).toBe("Color: #123456"); +}); + interface ServiceSpec { name: string; interfaces: InterfaceSpec[]; @@ -320,6 +352,7 @@ function createIntegration(options?: { packageUiReferences?: ReferenceSpec[]; i18n?: PackageIntl; services?: ServiceSpec[]; + theme?: Record; }): TestIntegration { const wrapper = document.createElement("div"); const packages = new Map(); @@ -354,6 +387,7 @@ function createIntegration(options?: { const integration = new ReactIntegration({ container: wrapper, rootNode: wrapper, + theme: options?.theme, packages, serviceLayer }); diff --git a/src/packages/runtime/react-integration/ReactIntegration.tsx b/src/packages/runtime/react-integration/ReactIntegration.tsx index 647dc6a4..cd40406e 100644 --- a/src/packages/runtime/react-integration/ReactIntegration.tsx +++ b/src/packages/runtime/react-integration/ReactIntegration.tsx @@ -16,10 +16,12 @@ export interface ReactIntegrationOptions { serviceLayer: ServiceLayer; rootNode: HTMLDivElement; container: Node; + theme: Record | undefined; } export class ReactIntegration { private containerNode: Node; + private theme: Record | undefined; private packages: Map; private serviceLayer: ServiceLayer; private root: Root; @@ -27,6 +29,7 @@ export class ReactIntegration { constructor(options: ReactIntegrationOptions) { this.containerNode = options.container; + this.theme = options.theme; this.packages = options.packages; this.serviceLayer = options.serviceLayer; this.root = createRoot(options.rootNode); @@ -85,18 +88,16 @@ export class ReactIntegration { }; } - render(Component: ComponentType, props: Record) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const customTheme = props.theme as Record; + render(Component: ComponentType) { this.root.render( - + From 5ec3c07f63d5d2a92ad298d1c5a926ed336e8686 Mon Sep 17 00:00:00 2001 From: Michael Beckemeyer Date: Mon, 18 Sep 2023 16:12:53 +0200 Subject: [PATCH 6/8] refactor: move logic into hooks --- src/packages/chakra-integration/Provider.tsx | 70 ++++++++++++-------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/src/packages/chakra-integration/Provider.tsx b/src/packages/chakra-integration/Provider.tsx index ff68581b..4910adbf 100644 --- a/src/packages/chakra-integration/Provider.tsx +++ b/src/packages/chakra-integration/Provider.tsx @@ -13,7 +13,7 @@ import { } from "@chakra-ui/react"; import createCache, { EmotionCache } from "@emotion/cache"; import { CacheProvider, Global } from "@emotion/react"; -import { FC, PropsWithChildren, useEffect, useRef } from "react"; +import { FC, PropsWithChildren, RefObject, useEffect, useRef } from "react"; import { PortalRootProvider } from "./PortalFix"; import { theme as trailsDefaultTheme } from "./theme/theme"; @@ -102,13 +102,7 @@ export const CustomChakraProvider: FC = ({ */ - const cacheRef = useRef(); - if (!cacheRef.current) { - cacheRef.current = createCache({ - key: "css", - container: container - }); - } + const cache = useEmotionCache(container); const chakraHost = useRef(null); const toastOptions: ToastProviderProps = { @@ -117,27 +111,11 @@ export const CustomChakraProvider: FC = ({ } }; - const mode = colorMode ?? "light"; - useEffect(() => { - const host = chakraHost.current; - if (!host) { - return; - } - - // https://github.dev/chakra-ui/chakra-ui/blob/80971001d7b77d02d5f037487a37237ded315480/packages/components/color-mode/src/color-mode.utils.ts#L16-L25 - const className = colorModeClassnames[mode]; - host.classList.add(className); - host.dataset.theme = mode; - return () => { - host.classList.remove(className); - host.dataset.theme = undefined; - }; - }, [mode]); - const ColorMode = mode === "light" ? LightMode : DarkMode; + const ColorMode = useSyncedColorMode(chakraHost, colorMode); return (
- + @@ -157,3 +135,43 @@ export const CustomChakraProvider: FC = ({
); }; + +/** + * Computes the correct color mode and returns a component that will apply that mode. + * + * The current color mode is automatically propagates as a css class on the chakra host element. + */ +function useSyncedColorMode( + chakraHost: RefObject, + colorMode: "light" | "dark" | undefined +) { + const mode = colorMode ?? "light"; + useEffect(() => { + const host = chakraHost.current; + if (!host) { + return; + } + + // https://github.dev/chakra-ui/chakra-ui/blob/80971001d7b77d02d5f037487a37237ded315480/packages/components/color-mode/src/color-mode.utils.ts#L16-L25 + const className = colorModeClassnames[mode]; + host.classList.add(className); + host.dataset.theme = mode; + return () => { + host.classList.remove(className); + host.dataset.theme = undefined; + }; + }, [chakraHost, mode]); + const ColorMode = mode === "light" ? LightMode : DarkMode; + return ColorMode; +} + +function useEmotionCache(container: Node): EmotionCache { + const cacheRef = useRef(); + if (!cacheRef.current) { + cacheRef.current = createCache({ + key: "css", + container: container + }); + } + return cacheRef.current; +} From 326817a7f9cbb91e08ddfe1d252896202cd8de25 Mon Sep 17 00:00:00 2001 From: Michael Beckemeyer Date: Mon, 18 Sep 2023 16:20:28 +0200 Subject: [PATCH 7/8] Add changeset entries --- .changeset/hip-dancers-fix.md | 5 +++++ .changeset/twenty-donuts-kneel.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/hip-dancers-fix.md create mode 100644 .changeset/twenty-donuts-kneel.md diff --git a/.changeset/hip-dancers-fix.md b/.changeset/hip-dancers-fix.md new file mode 100644 index 00000000..b3508796 --- /dev/null +++ b/.changeset/hip-dancers-fix.md @@ -0,0 +1,5 @@ +--- +"@open-pioneer/runtime": minor +--- + +Implement support for custom chakra themes via the `theme` parameter in `createCustomElement()`. diff --git a/.changeset/twenty-donuts-kneel.md b/.changeset/twenty-donuts-kneel.md new file mode 100644 index 00000000..af201126 --- /dev/null +++ b/.changeset/twenty-donuts-kneel.md @@ -0,0 +1,5 @@ +--- +"@open-pioneer/chakra-integration": minor +--- + +Add theme parameter and experimental base theme. From 2c3f3c296d687d9d429321ba79564eb78f3b9607 Mon Sep 17 00:00:00 2001 From: Michael Beckemeyer Date: Tue, 26 Sep 2023 12:06:43 +0200 Subject: [PATCH 8/8] Create base-theme package --- .changeset/hip-dancers-fix.md | 1 + .changeset/slow-walls-cheat.md | 5 + pnpm-lock.yaml | 18 ++ src/packages/base-theme/CHANGELOG.md | 1 + src/packages/base-theme/LICENSE | 202 ++++++++++++++++++ src/packages/base-theme/README.md | 10 + src/packages/base-theme/build.config.mjs | 7 + src/packages/base-theme/index.ts | 3 + src/packages/base-theme/package.json | 19 ++ src/packages/base-theme/theme.ts | 23 ++ src/packages/base-theme/typedoc.json | 4 + src/packages/chakra-integration/Provider.tsx | 28 ++- src/packages/chakra-integration/index.tsx | 1 - .../chakra-integration/theme/theme.ts | 64 ------ src/packages/runtime/package.json | 1 + .../react-integration/ReactIntegration.tsx | 3 +- src/samples/chakra-sample/chakra-app/app.ts | 4 +- .../chakra-sample/chakra-app/package.json | 1 + .../chakra-sample/chakra-app/theme/theme.ts | 3 +- src/samples/chakra-sample/index.html | 2 +- typedoc.config.cjs | 1 + 21 files changed, 327 insertions(+), 74 deletions(-) create mode 100644 .changeset/slow-walls-cheat.md create mode 100644 src/packages/base-theme/CHANGELOG.md create mode 100644 src/packages/base-theme/LICENSE create mode 100644 src/packages/base-theme/README.md create mode 100644 src/packages/base-theme/build.config.mjs create mode 100644 src/packages/base-theme/index.ts create mode 100644 src/packages/base-theme/package.json create mode 100644 src/packages/base-theme/theme.ts create mode 100644 src/packages/base-theme/typedoc.json delete mode 100644 src/packages/chakra-integration/theme/theme.ts diff --git a/.changeset/hip-dancers-fix.md b/.changeset/hip-dancers-fix.md index b3508796..706838f0 100644 --- a/.changeset/hip-dancers-fix.md +++ b/.changeset/hip-dancers-fix.md @@ -3,3 +3,4 @@ --- Implement support for custom chakra themes via the `theme` parameter in `createCustomElement()`. +`theme` from `@open-pioneer/base-theme` is used as default when no other theme is configured. diff --git a/.changeset/slow-walls-cheat.md b/.changeset/slow-walls-cheat.md new file mode 100644 index 00000000..c0af9321 --- /dev/null +++ b/.changeset/slow-walls-cheat.md @@ -0,0 +1,5 @@ +--- +"@open-pioneer/base-theme": minor +--- + +Initial release. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 390cb67e..4dba6269 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -171,6 +171,17 @@ importers: specifier: ^0.31.4 version: 0.31.4(jsdom@22.1.0)(sass@1.63.6) + src/packages/base-theme: + dependencies: + '@open-pioneer/chakra-integration': + specifier: workspace:^ + version: link:../chakra-integration + devDependencies: + core-packages: + specifier: workspace:^ + version: link:../../.. + publishDirectory: dist + src/packages/chakra-integration: dependencies: '@chakra-ui/react': @@ -240,6 +251,9 @@ importers: '@formatjs/intl': specifier: ^2.7.2 version: 2.9.0(typescript@5.1.6) + '@open-pioneer/base-theme': + specifier: workspace:^ + version: link:../base-theme '@open-pioneer/chakra-integration': specifier: workspace:^ version: link:../chakra-integration @@ -324,6 +338,9 @@ importers: src/samples/chakra-sample/chakra-app: dependencies: + '@open-pioneer/base-theme': + specifier: workspace:^ + version: link:../../../packages/base-theme '@open-pioneer/chakra-integration': specifier: workspace:^ version: link:../../../packages/chakra-integration @@ -1823,6 +1840,7 @@ packages: /@emotion/memoize@0.7.4: resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==} + requiresBuild: true dev: false optional: true diff --git a/src/packages/base-theme/CHANGELOG.md b/src/packages/base-theme/CHANGELOG.md new file mode 100644 index 00000000..7ecfde6b --- /dev/null +++ b/src/packages/base-theme/CHANGELOG.md @@ -0,0 +1 @@ +# @open-pioneer/base-theme diff --git a/src/packages/base-theme/LICENSE b/src/packages/base-theme/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/src/packages/base-theme/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/src/packages/base-theme/README.md b/src/packages/base-theme/README.md new file mode 100644 index 00000000..ace0e893 --- /dev/null +++ b/src/packages/base-theme/README.md @@ -0,0 +1,10 @@ +# @open-pioneer/base-theme + +Provides the default open pioneer trails chakra theme. + +```js +import { theme as baseTheme } from "@open-pioneer/base-theme"; +``` + +> NOTE: This package is still under active development. +> There will be breaking changes. diff --git a/src/packages/base-theme/build.config.mjs b/src/packages/base-theme/build.config.mjs new file mode 100644 index 00000000..015464d5 --- /dev/null +++ b/src/packages/base-theme/build.config.mjs @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: con terra GmbH and contributors +// SPDX-License-Identifier: Apache-2.0 +import { defineBuildConfig } from "@open-pioneer/build-support"; + +export default defineBuildConfig({ + entryPoints: ["index"] +}); diff --git a/src/packages/base-theme/index.ts b/src/packages/base-theme/index.ts new file mode 100644 index 00000000..c0176685 --- /dev/null +++ b/src/packages/base-theme/index.ts @@ -0,0 +1,3 @@ +// SPDX-FileCopyrightText: con terra GmbH and contributors +// SPDX-License-Identifier: Apache-2.0 +export { theme } from "./theme"; diff --git a/src/packages/base-theme/package.json b/src/packages/base-theme/package.json new file mode 100644 index 00000000..0477d5b5 --- /dev/null +++ b/src/packages/base-theme/package.json @@ -0,0 +1,19 @@ +{ + "name": "@open-pioneer/base-theme", + "version": "0.0.1", + "main": "index.ts", + "license": "Apache-2.0", + "scripts": { + "build": "build-pioneer-package" + }, + "peerDependencies": { + "@open-pioneer/chakra-integration": "workspace:^" + }, + "devDependencies": { + "core-packages": "workspace:^" + }, + "publishConfig": { + "directory": "dist", + "linkDirectory": false + } +} diff --git a/src/packages/base-theme/theme.ts b/src/packages/base-theme/theme.ts new file mode 100644 index 00000000..f4bf5c4e --- /dev/null +++ b/src/packages/base-theme/theme.ts @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: con terra GmbH and contributors +// SPDX-License-Identifier: Apache-2.0 +import { extendTheme } from "@open-pioneer/chakra-integration"; + +/** + * Base theme for open pioneer trails applications. + * + * All custom themes should extend this theme: + * + * ```ts + * import { extendTheme } from "@open-pioneer/chakra-integration"; + * import { theme as baseTheme } from "@open-pioneer/base-theme"; + * + * export const theme = extendTheme({ + * // Your overrides + * }, baseTheme); + * ``` + * + * NOTE: this API is still _experimental_. + * + * @experimental + */ +export const theme = extendTheme({}); diff --git a/src/packages/base-theme/typedoc.json b/src/packages/base-theme/typedoc.json new file mode 100644 index 00000000..9900c8bd --- /dev/null +++ b/src/packages/base-theme/typedoc.json @@ -0,0 +1,4 @@ +{ + "extends": ["core-packages/typedoc.base.json"], + "entryPoints": ["./index.tsx"] +} diff --git a/src/packages/chakra-integration/Provider.tsx b/src/packages/chakra-integration/Provider.tsx index 4910adbf..e8b17965 100644 --- a/src/packages/chakra-integration/Provider.tsx +++ b/src/packages/chakra-integration/Provider.tsx @@ -9,13 +9,14 @@ import { ThemeProvider, ToastOptionProvider, ToastProvider, - ToastProviderProps + ToastProviderProps, + extendTheme, + theme as chakraBaseTheme } from "@chakra-ui/react"; import createCache, { EmotionCache } from "@emotion/cache"; import { CacheProvider, Global } from "@emotion/react"; -import { FC, PropsWithChildren, RefObject, useEffect, useRef } from "react"; +import { FC, PropsWithChildren, RefObject, useEffect, useMemo, useRef } from "react"; import { PortalRootProvider } from "./PortalFix"; -import { theme as trailsDefaultTheme } from "./theme/theme"; export type CustomChakraProviderProps = PropsWithChildren<{ /** @@ -64,7 +65,7 @@ export const CustomChakraProvider: FC = ({ container, colorMode, children, - theme = trailsDefaultTheme + theme: themeProp }) => { /* Chakra integration internals: @@ -104,6 +105,8 @@ export const CustomChakraProvider: FC = ({ const cache = useEmotionCache(container); + const theme = useMemo(() => wrapTheme(themeProp), [themeProp]); + const chakraHost = useRef(null); const toastOptions: ToastProviderProps = { portalProps: { @@ -165,6 +168,23 @@ function useSyncedColorMode( return ColorMode; } +function wrapTheme(theme: Record = chakraBaseTheme): Record { + return extendTheme( + { + styles: { + //add global css styles here + global: { + // Apply the same styles to the application root node that chakra would usually apply to the html and body. + ".chakra-host": + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (chakraBaseTheme.styles.global as Record).body + } + } + }, + theme + ); +} + function useEmotionCache(container: Node): EmotionCache { const cacheRef = useRef(); if (!cacheRef.current) { diff --git a/src/packages/chakra-integration/index.tsx b/src/packages/chakra-integration/index.tsx index b351ad36..467e51a3 100644 --- a/src/packages/chakra-integration/index.tsx +++ b/src/packages/chakra-integration/index.tsx @@ -9,4 +9,3 @@ export { FixedPortal as Portal, FixedTooltip as Tooltip } from "./PortalFix"; -export { theme } from "./theme/theme"; diff --git a/src/packages/chakra-integration/theme/theme.ts b/src/packages/chakra-integration/theme/theme.ts deleted file mode 100644 index acf95269..00000000 --- a/src/packages/chakra-integration/theme/theme.ts +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-FileCopyrightText: con terra GmbH and contributors -// SPDX-License-Identifier: Apache-2.0 -import { theme as baseTheme, extendTheme } from "@chakra-ui/react"; - -/** - * Base theme for open pioneer trails applications. - * - * All custom themes should extend this theme: - * - * ```ts - * import { theme as baseTheme, extendTheme } from "@open-pioneer/chakra-integration"; - * - * export const theme = extendTheme({ - * // Your overrides - * }, baseTheme); - * ``` - * - * NOTE: this API is still _experimental_. The base theme is likely to move into a different package. - * - * @experimental - */ -export const theme = extendTheme( - { - styles: { - //add global css styles here - global: { - // Apply the same styles to the application root node that chakra would usually apply to the html and body. - ".chakra-host": - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (baseTheme.styles.global as Record).body - } - }, - colors: { - //define colors and color schemes here/ - /*primary: { - 50: "#defffd", - 100: "#b3fffa", - 200: "#86feee", - 300: "#5bfedd", - 400: "#3efec9", - 500: "#32e5a6", - 600: "#23b277", - 700: "#147f4c", - 800: "#004d23", - 900: "#001b0a", - } */ - }, - components: { - //define component specific styling here - /*Button: { - defaultProps: { - colorScheme: "primary" - }*/ - }, - semanticTokens: { - //define sematinc tokens here - /*colors: { - background_primary: "primary.300", - background_secondary: "primary.500", - }*/ - } - }, - baseTheme -); diff --git a/src/packages/runtime/package.json b/src/packages/runtime/package.json index c485a558..bfd4165b 100644 --- a/src/packages/runtime/package.json +++ b/src/packages/runtime/package.json @@ -8,6 +8,7 @@ }, "peerDependencies": { "@formatjs/intl": "^2.7.2", + "@open-pioneer/base-theme": "workspace:^", "@open-pioneer/chakra-integration": "workspace:^", "@open-pioneer/core": "workspace:^", "@open-pioneer/runtime-react-support": "workspace:^", diff --git a/src/packages/runtime/react-integration/ReactIntegration.tsx b/src/packages/runtime/react-integration/ReactIntegration.tsx index cd40406e..510dd62c 100644 --- a/src/packages/runtime/react-integration/ReactIntegration.tsx +++ b/src/packages/runtime/react-integration/ReactIntegration.tsx @@ -10,6 +10,7 @@ import { PackageRepr } from "../service-layer/PackageRepr"; import { InterfaceSpec, renderInterfaceSpec } from "../service-layer/InterfaceSpec"; import { renderAmbiguousServiceChoices } from "../service-layer/ServiceLookup"; import { CustomChakraProvider } from "@open-pioneer/chakra-integration"; +import { theme as defaultTrailsTheme } from "@open-pioneer/base-theme"; export interface ReactIntegrationOptions { packages: Map; @@ -94,7 +95,7 @@ export class ReactIntegration { diff --git a/src/samples/chakra-sample/chakra-app/app.ts b/src/samples/chakra-sample/chakra-app/app.ts index b452d55b..70c537ee 100644 --- a/src/samples/chakra-sample/chakra-app/app.ts +++ b/src/samples/chakra-sample/chakra-app/app.ts @@ -2,12 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 import { createCustomElement } from "@open-pioneer/runtime"; import * as appMetadata from "open-pioneer:app"; -import { SampleUI } from "./SampleUI"; import { theme } from "./theme/theme"; +import { SampleUI } from "./SampleUI"; const Element = createCustomElement({ component: SampleUI, - theme: theme, + theme, appMetadata }); diff --git a/src/samples/chakra-sample/chakra-app/package.json b/src/samples/chakra-sample/chakra-app/package.json index 1a5fa71b..05e321f5 100644 --- a/src/samples/chakra-sample/chakra-app/package.json +++ b/src/samples/chakra-sample/chakra-app/package.json @@ -3,6 +3,7 @@ "version": "0.0.6", "private": true, "dependencies": { + "@open-pioneer/base-theme": "workspace:^", "@open-pioneer/chakra-integration": "workspace:^", "@open-pioneer/runtime": "workspace:^" } diff --git a/src/samples/chakra-sample/chakra-app/theme/theme.ts b/src/samples/chakra-sample/chakra-app/theme/theme.ts index 2945a196..f4fb9dc1 100644 --- a/src/samples/chakra-sample/chakra-app/theme/theme.ts +++ b/src/samples/chakra-sample/chakra-app/theme/theme.ts @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: con terra GmbH and contributors // SPDX-License-Identifier: Apache-2.0 -import { extendTheme, theme as baseTheme } from "@open-pioneer/chakra-integration"; +import { extendTheme } from "@open-pioneer/chakra-integration"; +import { theme as baseTheme } from "@open-pioneer/base-theme"; export const theme = extendTheme( { diff --git a/src/samples/chakra-sample/index.html b/src/samples/chakra-sample/index.html index 0dca8a16..db227dfa 100644 --- a/src/samples/chakra-sample/index.html +++ b/src/samples/chakra-sample/index.html @@ -3,7 +3,7 @@ - Date App Demo + Chakra Demo