mirror of
https://github.com/logto-io/logto.git
synced 2025-02-17 22:04:19 -05:00
style(ui): implement dynamic color theme (#938)
* style(ui): implement dynamic color theme implement dynamic color theme * fix(ui): avoid precision issue avoid precision issue * fix(ui): fix typo * fix(ui): fix typo fix typo
This commit is contained in:
parent
7a17d41acf
commit
48607219f5
6 changed files with 145 additions and 34 deletions
|
@ -29,12 +29,14 @@
|
|||
"@silverhand/ts-config": "^0.14.0",
|
||||
"@silverhand/ts-config-react": "^0.14.0",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@types/color": "^3.0.3",
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/react": "^17.0.14",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-router-dom": "^5.3.2",
|
||||
"classnames": "^2.3.1",
|
||||
"color": "^4.2.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.10.0",
|
||||
"i18next": "^21.6.12",
|
||||
|
|
|
@ -4,7 +4,17 @@
|
|||
|
||||
:root {
|
||||
--light-primary-color: #5d34f2;
|
||||
--light-focused-variant: rgba(93, 52, 242, 16%);
|
||||
--light-hover-variant: rgba(93, 52, 242, 8%);
|
||||
--light-pressed-variant: rgba(93, 52, 242, 12%);
|
||||
--light-hover: #7350f4;
|
||||
--light-pressed: #4718f0;
|
||||
--dark-primary-color: #7958ff;
|
||||
--dark-focused-variant: rgba(121, 88, 255, 16%);
|
||||
--dark-hover-variant: rgba(121, 88, 255, 8%);
|
||||
--dark-pressed-variant: rgba(121, 88, 255, 12%);
|
||||
--dark-hover: #957aff;
|
||||
--dark-pressed: #5d36ff;
|
||||
}
|
||||
|
||||
.content {
|
||||
|
|
|
@ -4,6 +4,7 @@ import { useDebouncedLoader } from 'use-debounced-loader';
|
|||
|
||||
import LoadingLayer from '@/components/LoadingLayer';
|
||||
import Toast from '@/components/Toast';
|
||||
import useColorTheme from '@/hooks/use-color-theme';
|
||||
import { PageContext } from '@/hooks/use-page-context';
|
||||
import useTheme from '@/hooks/use-theme';
|
||||
|
||||
|
@ -23,22 +24,11 @@ const AppContent = ({ children }: Props) => {
|
|||
setToast('');
|
||||
}, [setToast]);
|
||||
|
||||
// Set Primary ColorTheme
|
||||
useEffect(() => {
|
||||
if (!experienceSettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
branding: { primaryColor, darkPrimaryColor },
|
||||
} = experienceSettings;
|
||||
|
||||
document.documentElement.style.setProperty('--light-primary-color', primaryColor);
|
||||
document.documentElement.style.setProperty(
|
||||
'--dark-primary-color',
|
||||
darkPrimaryColor ?? primaryColor
|
||||
);
|
||||
}, [experienceSettings]);
|
||||
// Set Primary Color
|
||||
useColorTheme(
|
||||
experienceSettings?.branding.primaryColor,
|
||||
experienceSettings?.branding.darkPrimaryColor
|
||||
);
|
||||
|
||||
// Set Theme Mode
|
||||
useEffect(() => {
|
||||
|
|
59
packages/ui/src/hooks/use-color-theme.ts
Normal file
59
packages/ui/src/hooks/use-color-theme.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
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(),
|
||||
[`--light-hover-variant`]: primaryColor.alpha(0.08).string(),
|
||||
[`--light-pressed-variant`]: primaryColor.alpha(0.12).string(),
|
||||
[`--light-hover`]: absoluteLighten(primaryColor, 10).string(),
|
||||
[`--light-pressed`]: absoluteDarken(primaryColor, 10).string(),
|
||||
});
|
||||
|
||||
const generateDarkColorLibrary = (primaryColor: color) => ({
|
||||
[`--dark-primary-color`]: primaryColor.hex(),
|
||||
[`--dark-focused-variant`]: absoluteLighten(primaryColor, 17).rgb().alpha(0.16).string(),
|
||||
[`--dark-hover-variant`]: absoluteLighten(primaryColor, 17).rgb().alpha(0.08).string(),
|
||||
[`--dark-pressed-variant`]: absoluteLighten(primaryColor, 17).rgb().alpha(0.12).string(),
|
||||
[`--dark-hover`]: absoluteLighten(primaryColor, 10).string(),
|
||||
[`--dark-pressed`]: absoluteDarken(primaryColor, 10).string(),
|
||||
});
|
||||
|
||||
const useColorTheme = (primaryColor?: string, darkPrimaryColor?: string) => {
|
||||
useEffect(() => {
|
||||
if (!primaryColor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const lightPrimary = color(primaryColor);
|
||||
const darkPrimary = darkPrimaryColor
|
||||
? color(darkPrimaryColor)
|
||||
: absoluteLighten(lightPrimary, 10);
|
||||
|
||||
const lightColorLibrary = generateLightColorLibrary(lightPrimary);
|
||||
const darkColorLibrary = generateDarkColorLibrary(darkPrimary);
|
||||
|
||||
for (const [key, value] of Object.entries(lightColorLibrary)) {
|
||||
document.documentElement.style.setProperty(key, value);
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(darkColorLibrary)) {
|
||||
document.documentElement.style.setProperty(key, value);
|
||||
}
|
||||
}, [darkPrimaryColor, primaryColor]);
|
||||
};
|
||||
|
||||
export default useColorTheme;
|
|
@ -12,20 +12,22 @@ $font-family: -apple-system,
|
|||
@mixin colors-light-theme {
|
||||
--color-base: linear-gradient(0deg, rgba(93, 52, 242, 14%), rgba(93, 52, 242, 14%)), linear-gradient(0deg, rgba(120, 118, 127, 2%), rgba(120, 118, 127, 2%)), #f7f8f8;
|
||||
--color-surface: #fff;
|
||||
--color-text: #191c1d;
|
||||
--color-text: #191c1d; // Neutral-10
|
||||
--color-text-disabled: #c4c7c7;
|
||||
--color-border: #c4c7c7;
|
||||
--color-caption: #747778;
|
||||
--color-icon: #747778;
|
||||
--color-focused: rgba(25, 28, 29, 16%); // 16% Neutral-10
|
||||
--color-pressed: rgba(25, 28, 29, 12%); // 12% Neutral-10
|
||||
--color-hover: rgba(25, 28, 29, 8%); // 8% Neutral-10
|
||||
|
||||
--color-primary: var(--light-primary-color);
|
||||
--color-focused: rgba(25, 28, 29, 16%);
|
||||
--color-focused-variant: rgba(93, 52, 242, 16%);
|
||||
--color-pressed: rgba(25, 28, 29, 12%);
|
||||
--color-primary-pressed: #4300da;
|
||||
--color-pressed-variant: rgba(93, 52, 242, 12%);
|
||||
--color-hover: rgba(25, 28, 29, 8%);
|
||||
--color-primary-hover: #7958ff;
|
||||
--color-hover-variant: rgba(93, 52, 242, 8%);
|
||||
--color-focused-variant: var(--light-focused-variant);
|
||||
--color-hover-variant: var(--light-hover-variant);
|
||||
--color-pressed-variant: var(--light-pressed-variant);
|
||||
--color-primary-pressed: var(--light-pressed);
|
||||
--color-primary-hover: var(--light-hover);
|
||||
|
||||
--color-inverse-on-surface: #f3effa;
|
||||
--color-outline: #78767f;
|
||||
--color-layer: #eff1f1;
|
||||
|
@ -37,20 +39,23 @@ $font-family: -apple-system,
|
|||
@mixin colors-dark-theme {
|
||||
--color-base: linear-gradient(0deg, rgba(202, 190, 255, 14%), rgba(202, 190, 255, 14%)), linear-gradient(0deg, rgba(196, 199, 199, 2%), rgba(196, 199, 199, 2%)), #191c1d;
|
||||
--color-surface: #191c1d;
|
||||
--color-text: #f7f8f8;
|
||||
--color-text: #f7f8f8; // Neutral-10
|
||||
--color-text-disabled: #5c5f60;
|
||||
--color-border: #5c5f60;
|
||||
--color-caption: #a9acac;
|
||||
--color-icon: #a9acac;
|
||||
--color-focused: rgba(247, 248, 248, 16%); // 16% Neutral-10
|
||||
--color-pressed: rgba(247, 248, 248, 12%); // 12% Neutral-10
|
||||
--color-hover: rgba(247, 248, 248, 8%); // 8% Neutral-10
|
||||
|
||||
--color-primary: var(--dark-primary-color);
|
||||
--color-focused: rgba(247, 248, 248, 16%);
|
||||
--color-focused-variant: rgba(202, 190, 255, 16%);
|
||||
--color-hover-variant: rgba(202, 190, 255, 8%);
|
||||
--color-hover: rgba(247, 248, 248, 8%);
|
||||
--color-primary-hover: #947dff;
|
||||
--color-pressed-variant: rgba(202, 190, 255, 12%);
|
||||
--color-pressed: rgba(247, 248, 248, 12%);
|
||||
--color-primary-pressed: #5d34f2;
|
||||
--color-focused-variant: var(--dark-focused-variant);
|
||||
--color-hover-variant: var(--dark-hover-variant);
|
||||
--color-pressed-variant: var(--dark-pressed-variant);
|
||||
--color-primary-pressed: var(--dark-pressed);
|
||||
--color-primary-hover: var(--dark-hover);
|
||||
|
||||
|
||||
--color-inverse-on-surface: #2d3132;
|
||||
--color-outline: #928f9a;
|
||||
--color-layer: linear-gradient(0deg, rgba(202, 190, 255, 14%), rgba(202, 190, 255, 14%)), linear-gradient(0deg, rgba(196, 199, 199, 2%), rgba(196, 199, 199, 2%)), #191c1d;
|
||||
|
|
45
pnpm-lock.yaml
generated
45
pnpm-lock.yaml
generated
|
@ -1002,12 +1002,14 @@ importers:
|
|||
'@silverhand/ts-config': ^0.14.0
|
||||
'@silverhand/ts-config-react': ^0.14.0
|
||||
'@testing-library/react': ^12.0.0
|
||||
'@types/color': ^3.0.3
|
||||
'@types/jest': ^27.4.1
|
||||
'@types/react': ^17.0.14
|
||||
'@types/react-dom': ^17.0.9
|
||||
'@types/react-modal': ^3.13.1
|
||||
'@types/react-router-dom': ^5.3.2
|
||||
classnames: ^2.3.1
|
||||
color: ^4.2.3
|
||||
cross-env: ^7.0.3
|
||||
eslint: ^8.10.0
|
||||
i18next: ^21.6.12
|
||||
|
@ -1049,12 +1051,14 @@ importers:
|
|||
'@silverhand/ts-config': 0.14.0_typescript@4.6.2
|
||||
'@silverhand/ts-config-react': 0.14.0_typescript@4.6.2
|
||||
'@testing-library/react': 12.1.5_sfoxds7t5ydpegc3knd667wn6m
|
||||
'@types/color': 3.0.3
|
||||
'@types/jest': 27.4.1
|
||||
'@types/react': 17.0.37
|
||||
'@types/react-dom': 17.0.11
|
||||
'@types/react-modal': 3.13.1
|
||||
'@types/react-router-dom': 5.3.2
|
||||
classnames: 2.3.1
|
||||
color: 4.2.3
|
||||
cross-env: 7.0.3
|
||||
eslint: 8.10.0
|
||||
i18next: 21.6.12
|
||||
|
@ -6232,6 +6236,22 @@ packages:
|
|||
'@types/node': 17.0.23
|
||||
'@types/responselike': 1.0.0
|
||||
|
||||
/@types/color-convert/2.0.0:
|
||||
resolution: {integrity: sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==}
|
||||
dependencies:
|
||||
'@types/color-name': 1.1.1
|
||||
dev: true
|
||||
|
||||
/@types/color-name/1.1.1:
|
||||
resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==}
|
||||
dev: true
|
||||
|
||||
/@types/color/3.0.3:
|
||||
resolution: {integrity: sha512-X//qzJ3d3Zj82J9sC/C18ZY5f43utPbAJ6PhYt/M7uG6etcF6MRpKdN880KBy43B0BMzSfeT96MzrsNjFI3GbA==}
|
||||
dependencies:
|
||||
'@types/color-convert': 2.0.0
|
||||
dev: true
|
||||
|
||||
/@types/connect-history-api-fallback/1.3.5:
|
||||
resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==}
|
||||
dependencies:
|
||||
|
@ -8414,11 +8434,26 @@ packages:
|
|||
/color-name/1.1.4:
|
||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||
|
||||
/color-string/1.9.1:
|
||||
resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==}
|
||||
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==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/color/4.2.3:
|
||||
resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==}
|
||||
engines: {node: '>=12.5.0'}
|
||||
dependencies:
|
||||
color-convert: 2.0.1
|
||||
color-string: 1.9.1
|
||||
dev: true
|
||||
|
||||
/colord/2.9.2:
|
||||
resolution: {integrity: sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==}
|
||||
dev: true
|
||||
|
@ -11827,6 +11862,10 @@ packages:
|
|||
/is-arrayish/0.2.1:
|
||||
resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=}
|
||||
|
||||
/is-arrayish/0.3.2:
|
||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||
dev: true
|
||||
|
||||
/is-bigint/1.0.4:
|
||||
resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
|
||||
dependencies:
|
||||
|
@ -17747,6 +17786,12 @@ packages:
|
|||
/signal-exit/3.0.7:
|
||||
resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
|
||||
|
||||
/simple-swizzle/0.2.2:
|
||||
resolution: {integrity: sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=}
|
||||
dependencies:
|
||||
is-arrayish: 0.3.2
|
||||
dev: true
|
||||
|
||||
/sirv/1.0.19:
|
||||
resolution: {integrity: sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==}
|
||||
engines: {node: '>= 10'}
|
||||
|
|
Loading…
Add table
Reference in a new issue