diff --git a/packages/keychain/package.json b/packages/keychain/package.json index a487eacb6..709894ff6 100644 --- a/packages/keychain/package.json +++ b/packages/keychain/package.json @@ -20,6 +20,7 @@ "@cartridge/controller": "workspace:^", "@cartridge/penpal": "^6.2.3", "@cartridge/ui": "workspace:^", + "@cartridge/ui-next": "workspace:^", "@cartridge/utils": "workspace:^", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", @@ -66,6 +67,7 @@ "@types/js-cookie": "^3.0.2", "@types/node": "^20.6.0", "@types/react-dom": "^18.2.7", + "autoprefixer": "^10.4.18", "concurrently": "^9.0.1", "eslint": "^8.23.0", "eslint-config-next": "^12.2.5", @@ -74,8 +76,10 @@ "jest": "^29.3.1", "jest-environment-jsdom": "^29.3.1", "playwright": "^1.47.1", + "postcss": "^8.4.35", "prettier": "^2.7.1", "storybook": "^8.2.9", + "tailwindcss": "^3.4.3", "typescript": "^5.4.5", "wait-on": "^8.0.1" }, diff --git a/packages/keychain/postcss.config.js b/packages/keychain/postcss.config.js new file mode 100644 index 000000000..a5a6ff858 --- /dev/null +++ b/packages/keychain/postcss.config.js @@ -0,0 +1,7 @@ +module.exports = { + plugins: { + "postcss-import": {}, + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/keychain/src/components/Provider/index.tsx b/packages/keychain/src/components/Provider/index.tsx index 2f800fec0..815e320d2 100644 --- a/packages/keychain/src/components/Provider/index.tsx +++ b/packages/keychain/src/components/Provider/index.tsx @@ -35,7 +35,7 @@ export function Provider({ children }: PropsWithChildren) { - + {children} diff --git a/packages/keychain/src/components/connect/Upgrade.tsx b/packages/keychain/src/components/connect/Upgrade.tsx index fda38be6a..90917751b 100644 --- a/packages/keychain/src/components/connect/Upgrade.tsx +++ b/packages/keychain/src/components/connect/Upgrade.tsx @@ -18,9 +18,9 @@ export const Upgrade = () => { executionError={upgrade.error} > - +
Install the latest to continue - +
(undefined); export function ControllerThemeProvider({ + theme, value, children, -}: ProviderProps) { +}: ProviderProps & { theme: ControllerThemePreset }) { const { setColorMode } = useColorMode(); useEffect(() => { setColorMode(value.colorMode); }, [setColorMode, value.colorMode]); + useThemeEffect({ theme, assetUrl: "/" }); + return ( {children} diff --git a/packages/keychain/src/index.css b/packages/keychain/src/index.css new file mode 100644 index 000000000..6f3cf1660 --- /dev/null +++ b/packages/keychain/src/index.css @@ -0,0 +1,7 @@ +@import "@cartridge/ui-next/dist/themes/default.css"; +@import "@cartridge/ui-next/dist/themes/dark.css"; +@import "@cartridge/ui-next/dist/themes/fonts.css"; + +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/packages/keychain/src/pages/_app.tsx b/packages/keychain/src/pages/_app.tsx index 6b0cb55bc..025532271 100644 --- a/packages/keychain/src/pages/_app.tsx +++ b/packages/keychain/src/pages/_app.tsx @@ -6,6 +6,8 @@ import { useEffect } from "react"; import { Provider } from "components/Provider"; import { ErrorBoundary } from "components/ErrorBoundary"; +import "../index.css"; + const inter = Inter({ subsets: ["latin"] }); const ibmPlexMono = IBM_Plex_Mono({ weight: "600", diff --git a/packages/keychain/tailwind.config.ts b/packages/keychain/tailwind.config.ts new file mode 100644 index 000000000..6f7721297 --- /dev/null +++ b/packages/keychain/tailwind.config.ts @@ -0,0 +1,22 @@ +import { cartridgeTWPreset } from "@cartridge/ui-next/preset"; +import { Config } from "tailwindcss"; + +const config: Config = { + content: [ + "./src/**/*.{html,ts,tsx}", + "./node_modules/@cartridge/ui-next/dist/**/*.{js,jsx}", + ], + presets: [cartridgeTWPreset], + theme: { + extend: { + width: { + desktop: "432px", + }, + height: { + desktop: "600px", + }, + }, + }, +}; + +export default config; diff --git a/packages/profile/src/components/context/theme.tsx b/packages/profile/src/components/context/theme.tsx index b63a7e7b7..4b0815e28 100644 --- a/packages/profile/src/components/context/theme.tsx +++ b/packages/profile/src/components/context/theme.tsx @@ -1,12 +1,7 @@ import { ControllerThemePreset, defaultPresets } from "@cartridge/controller"; +import { useThemeEffect } from "@cartridge/ui-next"; import { hexToHsl } from "@cartridge/utils"; -import { - createContext, - useCallback, - useEffect, - useMemo, - useState, -} from "react"; +import { createContext, useCallback, useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; type ColorScheme = "dark" | "light" | "system"; @@ -64,13 +59,6 @@ export function ThemeProvider({ }, [storageKey], ); - - const appliedColorMode = useMemo(() => { - return document.documentElement.className.includes("dark") - ? "dark" - : "light"; - }, []); - const [theme, setTheme] = useState(initialState.theme); const themeParam = searchParams.get("theme"); @@ -80,7 +68,13 @@ export function ThemeProvider({ setTheme(JSON.parse(decodeURIComponent(themeParam))); }, [themeParam]); + useThemeEffect({ theme, assetUrl: import.meta.env.VITE_KEYCHAIN_URL }); + useEffect(() => { + const appliedColorMode = document.documentElement.className.includes("dark") + ? "dark" + : "light"; + document.documentElement.style.setProperty( "--theme-icon-url", `url("${import.meta.env.VITE_KEYCHAIN_URL}${theme.icon}")`, @@ -113,7 +107,7 @@ export function ThemeProvider({ hexToHsl(val), ); } - }, [theme, appliedColorMode]); + }, [theme]); const value = { colorScheme, diff --git a/packages/ui-next/src/hooks.ts b/packages/ui-next/src/hooks.ts new file mode 100644 index 000000000..03f1fd16b --- /dev/null +++ b/packages/ui-next/src/hooks.ts @@ -0,0 +1,71 @@ +import { useEffect } from "react"; +import { hexToHsl } from "@cartridge/utils"; + +export function useThemeEffect({ + theme, + assetUrl, +}: { + theme: ControllerThemePreset; + assetUrl: string; +}) { + useEffect(() => { + const appliedColorMode = document.documentElement.className.includes("dark") + ? "dark" + : "light"; + + document.documentElement.style.setProperty( + "--theme-icon-url", + `url("${assetUrl}${theme.icon}")`, + ); + const coverUrl = + typeof theme.cover === "string" + ? `url("${assetUrl}${theme.cover}")` + : `url("${assetUrl}${theme.cover[appliedColorMode]}")`; + document.documentElement.style.setProperty("--theme-cover-url", coverUrl); + + if (!theme.colors) return; + + if (theme.colors?.primary) { + const val = + typeof theme.colors.primary === "string" + ? theme.colors?.primary + : theme.colors?.primary[appliedColorMode]; + document.documentElement.style.setProperty("--primary", hexToHsl(val)); + } + + if (theme.colors?.primaryForeground) { + const val = + typeof theme.colors.primaryForeground === "string" + ? theme.colors?.primaryForeground + : theme.colors?.primaryForeground[appliedColorMode]; + document.documentElement.style.setProperty( + "--primary-foreground", + hexToHsl(val), + ); + } + }, [theme, assetUrl]); +} + +// dup of @cartridge/controller/types +type ColorMode = "light" | "dark"; +type ControllerTheme = { + id: string; + name: string; + icon: string; + cover: ThemeValue; + colorMode: ColorMode; +}; +type ControllerThemePreset = Omit & { + colors?: ControllerColors; +}; +type ControllerColors = { + primary?: ControllerColor; + primaryForeground?: ControllerColor; +}; +type ControllerColor = ThemeValue; +type ThemeValue = + | T + | { + dark: T; + light: T; + }; diff --git a/packages/ui-next/src/index.ts b/packages/ui-next/src/index.ts index 89e38c8b6..fc82a0093 100644 --- a/packages/ui-next/src/index.ts +++ b/packages/ui-next/src/index.ts @@ -1,2 +1,3 @@ export * from "./components"; +export * from "./hooks"; export * from "./utils"; diff --git a/packages/ui-next/tsconfig.json b/packages/ui-next/tsconfig.json index 5ff3b5689..41054fd79 100644 --- a/packages/ui-next/tsconfig.json +++ b/packages/ui-next/tsconfig.json @@ -11,6 +11,7 @@ }, "include": [ "src/components/**/*", + "src/hooks.ts", "src/index.ts", "src/preset.ts", "src/utils.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97b319634..051e31c1f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -255,6 +255,9 @@ importers: '@cartridge/ui': specifier: workspace:^ version: link:../ui + '@cartridge/ui-next': + specifier: workspace:^ + version: link:../ui-next '@cartridge/utils': specifier: workspace:^ version: link:../utils @@ -400,6 +403,9 @@ importers: '@types/react-dom': specifier: ^18.2.7 version: 18.3.1 + autoprefixer: + specifier: ^10.4.18 + version: 10.4.20(postcss@8.4.47) concurrently: specifier: ^9.0.1 version: 9.0.1 @@ -424,12 +430,18 @@ importers: playwright: specifier: ^1.47.1 version: 1.48.0 + postcss: + specifier: ^8.4.35 + version: 8.4.47 prettier: specifier: ^2.7.1 version: 2.8.8 storybook: specifier: ^8.2.9 version: 8.3.5 + tailwindcss: + specifier: ^3.4.3 + version: 3.4.13(ts-node@10.9.2(@swc/core@1.7.35(@swc/helpers@0.5.5))(@types/node@20.16.11)(typescript@5.5.4)) typescript: specifier: ^5.4.5 version: 5.5.4 @@ -22360,8 +22372,8 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.5.4) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 2.7.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.0(eslint@8.57.1) eslint-plugin-react: 7.37.1(eslint@8.57.1) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.1) @@ -22387,11 +22399,11 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1): dependencies: debug: 4.3.7 eslint: 8.57.1 - eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1) glob: 7.2.3 is-glob: 4.0.3 resolve: 1.22.8 @@ -22406,11 +22418,11 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.5.4) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 2.7.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 2.7.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-typescript@2.7.1(eslint-plugin-import@2.31.0(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.5.4))(eslint-import-resolver-typescript@2.7.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8