0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2024-12-30 20:33:54 -05:00

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 <gao@silverhand.io>

Co-authored-by: Gao Sun <gao@silverhand.io>
This commit is contained in:
Wang Sijie 2022-06-29 15:56:05 +08:00 committed by GitHub
parent a60835f9ad
commit f72b21d160
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 103 additions and 43 deletions

View file

@ -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",

View file

@ -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<SignInExperienceForm>();
const {
watch,
register,
control,
setValue,
formState: { isDirty },
} = useFormContext<SignInExperienceForm>();
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,7 +60,8 @@ const ColorForm = () => {
/>
</FormField>
{isDarkModeEnabled && (
<FormField title="admin_console.sign_in_exp.color.dark_primary_color">
<>
<FormField isRequired title="admin_console.sign_in_exp.color.dark_primary_color">
<Controller
name="color.darkPrimaryColor"
control={control}
@ -43,6 +70,16 @@ const ColorForm = () => {
)}
/>
</FormField>
<div className={styles.darkModeTip}>
{t('sign_in_exp.color.dark_mode_reset_tip')}
<Button
type="plain"
size="small"
title="admin_console.sign_in_exp.color.reset"
onClick={handleResetColor}
/>
</div>
</>
)}
</>
);

View file

@ -21,3 +21,10 @@
.primarySocial {
margin-top: _.unit(2);
}
.darkModeTip {
display: flex;
align-items: baseline;
font: var(--font-body-medium);
color: var(--color-caption);
}

View file

@ -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

View file

@ -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',

View file

@ -318,7 +318,9 @@ const translation = {
dark_primary_color: '品牌颜色 (深色)',
dark_mode: '开启深色模式',
dark_mode_description:
'基于你的品牌颜色和 Logto 算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。',
'基于你的品牌颜色和 Logto 的算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。',
dark_mode_reset_tip: '重置为基于品牌颜色自动生成的深色模式颜色。',
reset: '重置',
},
branding: {
title: '品牌定制区',

View file

@ -75,7 +75,7 @@ export type Identities = z.infer<typeof identitiesGuard>;
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<typeof colorGuard>;

View file

@ -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"
}
}

View file

@ -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');
};

View file

@ -1,2 +1,3 @@
export * from './file';
export * from './react-router';
export * from './color';

View file

@ -97,5 +97,8 @@
]
}
},
"prettier": "@silverhand/eslint-config/.prettierrc"
"prettier": "@silverhand/eslint-config/.prettierrc",
"dependencies": {
"@logto/shared": "^0.1.0"
}
}

View file

@ -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);

View file

@ -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==}