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:
parent
a60835f9ad
commit
f72b21d160
13 changed files with 103 additions and 43 deletions
|
@ -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",
|
||||
|
|
|
@ -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,15 +60,26 @@ const ColorForm = () => {
|
|||
/>
|
||||
</FormField>
|
||||
{isDarkModeEnabled && (
|
||||
<FormField title="admin_console.sign_in_exp.color.dark_primary_color">
|
||||
<Controller
|
||||
name="color.darkPrimaryColor"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ColorPicker value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</FormField>
|
||||
<>
|
||||
<FormField isRequired title="admin_console.sign_in_exp.color.dark_primary_color">
|
||||
<Controller
|
||||
name="color.darkPrimaryColor"
|
||||
control={control}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<ColorPicker value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -21,3 +21,10 @@
|
|||
.primarySocial {
|
||||
margin-top: _.unit(2);
|
||||
}
|
||||
|
||||
.darkModeTip {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
font: var(--font-body-medium);
|
||||
color: var(--color-caption);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -318,7 +318,9 @@ const translation = {
|
|||
dark_primary_color: '品牌颜色 (深色)',
|
||||
dark_mode: '开启深色模式',
|
||||
dark_mode_description:
|
||||
'基于你的品牌颜色和 Logto 算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。',
|
||||
'基于你的品牌颜色和 Logto 的算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。',
|
||||
dark_mode_reset_tip: '重置为基于品牌颜色自动生成的深色模式颜色。',
|
||||
reset: '重置',
|
||||
},
|
||||
branding: {
|
||||
title: '品牌定制区',
|
||||
|
|
|
@ -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>;
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
14
packages/shared/src/utilities/color.ts
Normal file
14
packages/shared/src/utilities/color.ts
Normal 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');
|
||||
};
|
|
@ -1,2 +1,3 @@
|
|||
export * from './file';
|
||||
export * from './react-router';
|
||||
export * from './color';
|
||||
|
|
|
@ -97,5 +97,8 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc"
|
||||
"prettier": "@silverhand/eslint-config/.prettierrc",
|
||||
"dependencies": {
|
||||
"@logto/shared": "^0.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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==}
|
||||
|
|
Loading…
Reference in a new issue