From f72b21d1602ab0fb35ef3e7d84f6c8ebd7e18b08 Mon Sep 17 00:00:00 2001 From: Wang Sijie Date: Wed, 29 Jun 2022 15:56:05 +0800 Subject: [PATCH] feat(console,ui): generate dark mode color in console (#1231) * feat(console,ui): generate dark mode color in console * fix: cr fix Co-authored-by: Gao Sun Co-authored-by: Gao Sun --- packages/console/package.json | 2 + .../SignInExperience/components/ColorForm.tsx | 59 +++++++++++++++---- .../components/index.module.scss | 7 +++ .../src/pages/SignInExperience/utilities.ts | 6 -- packages/phrases/src/locales/en.ts | 2 + packages/phrases/src/locales/zh-cn.ts | 4 +- .../schemas/src/foundations/jsonb-types.ts | 2 +- packages/shared/package.json | 6 +- packages/shared/src/utilities/color.ts | 14 +++++ packages/shared/src/utilities/index.ts | 1 + packages/ui/package.json | 5 +- packages/ui/src/hooks/use-color-theme.ts | 18 +----- pnpm-lock.yaml | 20 +++++-- 13 files changed, 103 insertions(+), 43 deletions(-) create mode 100644 packages/shared/src/utilities/color.ts diff --git a/packages/console/package.json b/packages/console/package.json index 583a19f22..4bf51c9c8 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -32,6 +32,7 @@ "@silverhand/ts-config": "^0.14.0", "@silverhand/ts-config-react": "^0.14.0", "@tsconfig/docusaurus": "^1.0.5", + "@types/color": "^3.0.3", "@types/lodash.kebabcase": "^4.1.6", "@types/mdx": "^2.0.1", "@types/mdx-js__react": "^1.5.5", @@ -40,6 +41,7 @@ "@types/react-modal": "^3.13.1", "@types/react-syntax-highlighter": "^15.5.1", "classnames": "^2.3.1", + "color": "^4.2.3", "cross-env": "^7.0.3", "csstype": "^3.0.11", "dayjs": "^1.10.5", diff --git a/packages/console/src/pages/SignInExperience/components/ColorForm.tsx b/packages/console/src/pages/SignInExperience/components/ColorForm.tsx index 0a1f12791..03da48fb0 100644 --- a/packages/console/src/pages/SignInExperience/components/ColorForm.tsx +++ b/packages/console/src/pages/SignInExperience/components/ColorForm.tsx @@ -1,7 +1,10 @@ -import React from 'react'; +import { absoluteLighten } from '@logto/shared'; +import color from 'color'; +import React, { useCallback, useEffect } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import Button from '@/components/Button'; import ColorPicker from '@/components/ColorPicker'; import FormField from '@/components/FormField'; import Switch from '@/components/Switch'; @@ -11,9 +14,32 @@ import * as styles from './index.module.scss'; const ColorForm = () => { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); - const { watch, register, control } = useFormContext(); + const { + watch, + register, + control, + setValue, + formState: { isDirty }, + } = useFormContext(); const isDarkModeEnabled = watch('color.isDarkModeEnabled'); + const primaryColor = watch('color.primaryColor'); + + const handleResetColor = useCallback(() => { + const darkPrimaryColor = absoluteLighten(color(primaryColor), 10); + setValue('color.darkPrimaryColor', darkPrimaryColor.hex()); + }, [primaryColor, setValue]); + + useEffect(() => { + if (!isDirty) { + return; + } + + // If it's enabled, the original dark mode color won't change, users need to click "reset". + if (!isDarkModeEnabled) { + handleResetColor(); + } + }, [handleResetColor, isDarkModeEnabled, isDirty, primaryColor, setValue]); return ( <> @@ -34,15 +60,26 @@ const ColorForm = () => { /> {isDarkModeEnabled && ( - - ( - - )} - /> - + <> + + ( + + )} + /> + +
+ {t('sign_in_exp.color.dark_mode_reset_tip')} +
+ )} ); diff --git a/packages/console/src/pages/SignInExperience/components/index.module.scss b/packages/console/src/pages/SignInExperience/components/index.module.scss index 10613ef53..28a9d0eb7 100644 --- a/packages/console/src/pages/SignInExperience/components/index.module.scss +++ b/packages/console/src/pages/SignInExperience/components/index.module.scss @@ -21,3 +21,10 @@ .primarySocial { margin-top: _.unit(2); } + +.darkModeTip { + display: flex; + align-items: baseline; + font: var(--font-body-medium); + color: var(--color-caption); +} diff --git a/packages/console/src/pages/SignInExperience/utilities.ts b/packages/console/src/pages/SignInExperience/utilities.ts index 06931c531..1b0cfe7e7 100644 --- a/packages/console/src/pages/SignInExperience/utilities.ts +++ b/packages/console/src/pages/SignInExperience/utilities.ts @@ -58,18 +58,12 @@ export const signInExperienceParser = { }, toRemoteModel: (setup: SignInExperienceForm): SignInExperience => { const { - color, branding, languageInfo: { mode, fallbackLanguage, fixedLanguage }, } = setup; return { ...setup, - color: { - ...color, - // Transform empty string to undefined - darkPrimaryColor: conditional(color.darkPrimaryColor?.length && color.darkPrimaryColor), - }, branding: { ...branding, // Transform empty string to undefined diff --git a/packages/phrases/src/locales/en.ts b/packages/phrases/src/locales/en.ts index ad861add4..9158e8a6b 100644 --- a/packages/phrases/src/locales/en.ts +++ b/packages/phrases/src/locales/en.ts @@ -329,6 +329,8 @@ const translation = { dark_mode: 'Enable dark mode', dark_mode_description: 'Your app will have an auto-generated dark mode theme based on your brand color and Logto algorithm. You are free to customize.', + dark_mode_reset_tip: 'Reset to auto-generated dark mode color based on brand color.', + reset: 'Reset', }, branding: { title: 'BRANDING AREA', diff --git a/packages/phrases/src/locales/zh-cn.ts b/packages/phrases/src/locales/zh-cn.ts index 7ae518e1b..be522232c 100644 --- a/packages/phrases/src/locales/zh-cn.ts +++ b/packages/phrases/src/locales/zh-cn.ts @@ -318,7 +318,9 @@ const translation = { dark_primary_color: '品牌颜色 (深色)', dark_mode: '开启深色模式', dark_mode_description: - '基于你的品牌颜色和 Logto 算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。', + '基于你的品牌颜色和 Logto 的算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。', + dark_mode_reset_tip: '重置为基于品牌颜色自动生成的深色模式颜色。', + reset: '重置', }, branding: { title: '品牌定制区', diff --git a/packages/schemas/src/foundations/jsonb-types.ts b/packages/schemas/src/foundations/jsonb-types.ts index 3105c69fe..76dcb98df 100644 --- a/packages/schemas/src/foundations/jsonb-types.ts +++ b/packages/schemas/src/foundations/jsonb-types.ts @@ -75,7 +75,7 @@ export type Identities = z.infer; export const colorGuard = z.object({ primaryColor: z.string().regex(hexColorRegEx), isDarkModeEnabled: z.boolean(), - darkPrimaryColor: z.string().regex(hexColorRegEx).optional(), + darkPrimaryColor: z.string().regex(hexColorRegEx), }); export type Color = z.infer; diff --git a/packages/shared/package.json b/packages/shared/package.json index 492eaf701..cc8e6a1df 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -27,6 +27,7 @@ "@silverhand/essentials": "^1.1.6", "@silverhand/ts-config": "^0.14.0", "@silverhand/ts-config-react": "^0.14.0", + "@types/color": "^3.0.3", "@types/node": "^16.3.1", "eslint": "^8.10.0", "lint-staged": "^13.0.0", @@ -41,5 +42,8 @@ "stylelint": { "extends": "@silverhand/eslint-config-react/.stylelintrc" }, - "prettier": "@silverhand/eslint-config/.prettierrc" + "prettier": "@silverhand/eslint-config/.prettierrc", + "dependencies": { + "color": "^4.2.3" + } } diff --git a/packages/shared/src/utilities/color.ts b/packages/shared/src/utilities/color.ts new file mode 100644 index 000000000..1fe0e2c02 --- /dev/null +++ b/packages/shared/src/utilities/color.ts @@ -0,0 +1,14 @@ +import color from 'color'; + +// Color hsl lighten/darken takes percentage value only, need to implement absolute value update +export const absoluteLighten = (baseColor: color, delta: number) => { + const hslArray = baseColor.hsl().round().array() as [number, number, number]; + + return color([hslArray[0], hslArray[1], hslArray[2] + delta], 'hsl'); +}; + +export const absoluteDarken = (baseColor: color, delta: number) => { + const hslArray = baseColor.hsl().round().array() as [number, number, number]; + + return color([hslArray[0], hslArray[1], hslArray[2] - delta], 'hsl'); +}; diff --git a/packages/shared/src/utilities/index.ts b/packages/shared/src/utilities/index.ts index 4ac7e873a..3ea528766 100644 --- a/packages/shared/src/utilities/index.ts +++ b/packages/shared/src/utilities/index.ts @@ -1,2 +1,3 @@ export * from './file'; export * from './react-router'; +export * from './color'; diff --git a/packages/ui/package.json b/packages/ui/package.json index a52d705d0..54aa42660 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -97,5 +97,8 @@ ] } }, - "prettier": "@silverhand/eslint-config/.prettierrc" + "prettier": "@silverhand/eslint-config/.prettierrc", + "dependencies": { + "@logto/shared": "^0.1.0" + } } diff --git a/packages/ui/src/hooks/use-color-theme.ts b/packages/ui/src/hooks/use-color-theme.ts index fbfc3b098..5d5e8efab 100644 --- a/packages/ui/src/hooks/use-color-theme.ts +++ b/packages/ui/src/hooks/use-color-theme.ts @@ -1,19 +1,7 @@ +import { absoluteDarken, absoluteLighten } from '@logto/shared'; import color from 'color'; import { useEffect } from 'react'; -// Color hsl lighten/darken takes percentage value only, need to implement absolute value update -const absoluteLighten = (baseColor: color, delta: number) => { - const hslArray = baseColor.hsl().round().array() as [number, number, number]; - - return color([hslArray[0], hslArray[1], hslArray[2] + delta], 'hsl'); -}; - -const absoluteDarken = (baseColor: color, delta: number) => { - const hslArray = baseColor.hsl().round().array() as [number, number, number]; - - return color([hslArray[0], hslArray[1], hslArray[2] - delta], 'hsl'); -}; - const generateLightColorLibrary = (primaryColor: color) => ({ [`--light-primary-color`]: primaryColor.hex(), [`--light-focused-variant`]: primaryColor.alpha(0.16).string(), @@ -39,9 +27,7 @@ const useColorTheme = (primaryColor?: string, darkPrimaryColor?: string) => { } const lightPrimary = color(primaryColor); - const darkPrimary = darkPrimaryColor - ? color(darkPrimaryColor) - : absoluteLighten(lightPrimary, 10); + const darkPrimary = color(darkPrimaryColor); const lightColorLibrary = generateLightColorLibrary(lightPrimary); const darkColorLibrary = generateDarkColorLibrary(darkPrimary); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5f244f699..2d46623ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -682,6 +682,7 @@ importers: '@silverhand/ts-config': ^0.14.0 '@silverhand/ts-config-react': ^0.14.0 '@tsconfig/docusaurus': ^1.0.5 + '@types/color': ^3.0.3 '@types/lodash.kebabcase': ^4.1.6 '@types/mdx': ^2.0.1 '@types/mdx-js__react': ^1.5.5 @@ -690,6 +691,7 @@ importers: '@types/react-modal': ^3.13.1 '@types/react-syntax-highlighter': ^15.5.1 classnames: ^2.3.1 + color: ^4.2.3 cross-env: ^7.0.3 csstype: ^3.0.11 dayjs: ^1.10.5 @@ -742,6 +744,7 @@ importers: '@silverhand/ts-config': 0.14.0_typescript@4.6.2 '@silverhand/ts-config-react': 0.14.0_typescript@4.6.2 '@tsconfig/docusaurus': 1.0.5 + '@types/color': 3.0.3 '@types/lodash.kebabcase': 4.1.6 '@types/mdx': 2.0.1 '@types/mdx-js__react': 1.5.5 @@ -750,6 +753,7 @@ importers: '@types/react-modal': 3.13.1 '@types/react-syntax-highlighter': 15.5.1 classnames: 2.3.1 + color: 4.2.3 cross-env: 7.0.3 csstype: 3.0.11 dayjs: 1.10.7 @@ -1135,19 +1139,24 @@ importers: '@silverhand/essentials': ^1.1.6 '@silverhand/ts-config': ^0.14.0 '@silverhand/ts-config-react': ^0.14.0 + '@types/color': ^3.0.3 '@types/node': ^16.3.1 + color: ^4.2.3 eslint: ^8.10.0 lint-staged: ^13.0.0 postcss: ^8.4.6 prettier: ^2.3.2 stylelint: ^14.8.2 typescript: ^4.6.2 + dependencies: + color: 4.2.3 devDependencies: '@silverhand/eslint-config': 0.14.0_xpq2m6kgodzytx4bqbpsfgmxbe '@silverhand/eslint-config-react': 0.14.0_wfs3lj7jctdcr2gsfi4lvs3yoa '@silverhand/essentials': 1.1.7 '@silverhand/ts-config': 0.14.0_typescript@4.6.3 '@silverhand/ts-config-react': 0.14.0_typescript@4.6.3 + '@types/color': 3.0.3 '@types/node': 16.11.12 eslint: 8.10.0 lint-staged: 13.0.0 @@ -1161,6 +1170,7 @@ importers: '@logto/phrases': ^0.1.0 '@logto/phrases-ui': ^0.1.0 '@logto/schemas': ^0.1.0 + '@logto/shared': ^0.1.0 '@parcel/core': 2.6.2 '@parcel/transformer-sass': 2.6.2 '@parcel/transformer-svg-react': 2.6.2 @@ -1208,6 +1218,8 @@ importers: stylelint: ^14.8.2 typescript: ^4.6.2 use-debounced-loader: ^0.1.1 + dependencies: + '@logto/shared': link:../shared devDependencies: '@logto/phrases': link:../phrases '@logto/phrases-ui': link:../phrases-ui @@ -6630,7 +6642,7 @@ packages: color-name: 1.1.4 /color-name/1.1.3: - resolution: {integrity: sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=} + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -6640,7 +6652,6 @@ packages: dependencies: color-name: 1.1.4 simple-swizzle: 0.2.2 - dev: true /color-support/1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} @@ -6652,7 +6663,6 @@ packages: dependencies: color-convert: 2.0.1 color-string: 1.9.1 - dev: true /colord/2.9.2: resolution: {integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==} @@ -9260,7 +9270,6 @@ packages: /is-arrayish/0.3.2: resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} - dev: true /is-bigint/1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -14006,10 +14015,9 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} /simple-swizzle/0.2.2: - resolution: {integrity: sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=} + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: is-arrayish: 0.3.2 - dev: true /sisteransi/1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}