Skip to content

Commit

Permalink
feat(console,ui): generate dark mode color in console (#1231)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
wangsijie and gao-sun authored Jun 29, 2022
1 parent a60835f commit f72b21d
Show file tree
Hide file tree
Showing 13 changed files with 103 additions and 43 deletions.
2 changes: 2 additions & 0 deletions packages/console/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
<>
Expand All @@ -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>
</>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@
.primarySocial {
margin-top: _.unit(2);
}

.darkModeTip {
display: flex;
align-items: baseline;
font: var(--font-body-medium);
color: var(--color-caption);
}
6 changes: 0 additions & 6 deletions packages/console/src/pages/SignInExperience/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions packages/phrases/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
4 changes: 3 additions & 1 deletion packages/phrases/src/locales/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,9 @@ const translation = {
dark_primary_color: '品牌颜色 (深色)',
dark_mode: '开启深色模式',
dark_mode_description:
'基于你的品牌颜色和 Logto 算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。',
'基于你的品牌颜色和 Logto 的算法,你的应用将会有一个自动生成的深色模式。当然,你可以自定义和修改。',
dark_mode_reset_tip: '重置为基于品牌颜色自动生成的深色模式颜色。',
reset: '重置',
},
branding: {
title: '品牌定制区',
Expand Down
2 changes: 1 addition & 1 deletion packages/schemas/src/foundations/jsonb-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down
6 changes: 5 additions & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 changes: 14 additions & 0 deletions packages/shared/src/utilities/color.ts
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions packages/shared/src/utilities/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './file';
export * from './react-router';
export * from './color';
5 changes: 4 additions & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,8 @@
]
}
},
"prettier": "@silverhand/eslint-config/.prettierrc"
"prettier": "@silverhand/eslint-config/.prettierrc",
"dependencies": {
"@logto/shared": "^0.1.0"
}
}
18 changes: 2 additions & 16 deletions packages/ui/src/hooks/use-color-theme.ts
Original file line number Diff line number Diff line change
@@ -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(),
Expand All @@ -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);
Expand Down
20 changes: 14 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit f72b21d

Please sign in to comment.