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

fix(console): dragging anchor in the color picker on application branding page (#6340)

This commit is contained in:
Xiao Yijun 2024-07-26 15:54:58 +08:00 committed by GitHub
parent 05082b56a7
commit 556f7e43a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 135 additions and 142 deletions

View file

@ -93,7 +93,7 @@
"property-information": "^6.2.0",
"react": "^18.3.1",
"react-animate-height": "^3.0.4",
"react-color": "^2.19.3",
"react-color-palette": "^7.2.1",
"react-confetti": "^6.1.0",
"react-dnd": "^16.0.0",
"react-dnd-html5-backend": "^16.0.0",

View file

@ -16,6 +16,8 @@ import './scss/normalized.scss';
import './scss/overlayscrollbars.scss';
// eslint-disable-next-line import/no-unassigned-import
import '@fontsource/roboto-mono';
// eslint-disable-next-line import/no-unassigned-import
import 'react-color-palette/css';
import CloudAppRoutes from '@/cloud/AppRoutes';
import AppLoading from '@/components/AppLoading';

View file

@ -28,3 +28,7 @@
margin-right: _.unit(2);
}
}
.palette {
width: 400px;
}

View file

@ -1,6 +1,6 @@
import classNames from 'classnames';
import { useRef, useState } from 'react';
import { ChromePicker } from 'react-color';
import { ColorPicker as ColorPalette, useColor } from 'react-color-palette';
import { onKeyDownHandler } from '@/utils/a11y';
@ -17,6 +17,7 @@ type Props = {
function ColorPicker({ name, onChange, value = '#000000' }: Props) {
const anchorRef = useRef<HTMLSpanElement>(null);
const [isOpen, setIsOpen] = useState(false);
const [color] = useColor(value);
return (
<div
@ -41,12 +42,14 @@ function ColorPicker({ name, onChange, value = '#000000' }: Props) {
setIsOpen(false);
}}
>
<ChromePicker
color={value}
onChange={({ hex }) => {
onChange(hex);
}}
/>
<div className={styles.palette}>
<ColorPalette
color={color}
onChange={({ hex }) => {
onChange(hex);
}}
/>
</div>
</Dropdown>
</div>
);

View file

@ -0,0 +1,94 @@
import { generateDarkColor } from '@logto/core-kit';
import { Theme } from '@logto/schemas';
import { useMemo, useCallback } from 'react';
import { Controller, useFormContext } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import LogoAndFavicon from '@/components/ImageInputs/LogoAndFavicon';
import Button from '@/ds-components/Button';
import ColorPicker from '@/ds-components/ColorPicker';
import FormField from '@/ds-components/FormField';
import styles from './index.module.scss';
import { type ApplicationSignInExperienceForm } from './utils';
function NonThirdPartyBrandingForm() {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const {
control,
register,
formState: { errors },
watch,
setValue,
} = useFormContext<ApplicationSignInExperienceForm>();
const [primaryColor, darkPrimaryColor] = watch(['color.primaryColor', 'color.darkPrimaryColor']);
const calculatedDarkPrimaryColor = useMemo(() => {
return primaryColor && generateDarkColor(primaryColor);
}, [primaryColor]);
const handleResetColor = useCallback(() => {
setValue('color.darkPrimaryColor', calculatedDarkPrimaryColor, { shouldDirty: true });
}, [calculatedDarkPrimaryColor, setValue]);
return (
<>
<LogoAndFavicon
control={control}
register={register}
theme={Theme.Light}
type="app_logo"
logo={{ name: 'branding.logoUrl', error: errors.branding?.logoUrl }}
favicon={{
name: 'branding.favicon',
error: errors.branding?.favicon,
}}
/>
<LogoAndFavicon
control={control}
register={register}
theme={Theme.Dark}
type="app_logo"
logo={{ name: 'branding.darkLogoUrl', error: errors.branding?.darkLogoUrl }}
favicon={{
name: 'branding.darkFavicon',
error: errors.branding?.darkFavicon,
}}
/>
<div className={styles.colors}>
<Controller
control={control}
name="color.primaryColor"
render={({ field: { name, value, onChange } }) => (
<FormField title="application_details.branding.brand_color">
<ColorPicker name={name} value={value} onChange={onChange} />
</FormField>
)}
/>
<Controller
control={control}
name="color.darkPrimaryColor"
render={({ field: { name, value, onChange } }) => (
<FormField title="application_details.branding.brand_color_dark">
<ColorPicker name={name} value={value} onChange={onChange} />
</FormField>
)}
/>
{calculatedDarkPrimaryColor !== darkPrimaryColor && (
<div className={styles.darkModeTip}>
{t('sign_in_exp.color.dark_mode_reset_tip')}
<Button
type="text"
size="small"
title="sign_in_exp.color.reset"
onClick={handleResetColor}
/>
</div>
)}
</div>
</>
);
}
export default NonThirdPartyBrandingForm;

View file

@ -5,20 +5,17 @@ import {
type Application,
type ApplicationSignInExperience,
} from '@logto/schemas';
import { useCallback, useEffect, useMemo } from 'react';
import { useForm, FormProvider, Controller } from 'react-hook-form';
import { useCallback, useEffect } from 'react';
import { useForm, FormProvider } from 'react-hook-form';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import DetailsForm from '@/components/DetailsForm';
import FormCard, { FormCardSkeleton } from '@/components/FormCard';
import ImageInputs, { themeToLogoName } from '@/components/ImageInputs';
import LogoAndFavicon from '@/components/ImageInputs/LogoAndFavicon';
import RequestDataError from '@/components/RequestDataError';
import UnsavedChangesAlertModal from '@/components/UnsavedChangesAlertModal';
import { appSpecificBrandingLink, logtoThirdPartyAppBrandingLink } from '@/consts';
import Button from '@/ds-components/Button';
import ColorPicker from '@/ds-components/ColorPicker';
import FormField from '@/ds-components/FormField';
import Switch from '@/ds-components/Switch';
import TextInput from '@/ds-components/TextInput';
@ -28,7 +25,7 @@ import { emptyBranding } from '@/types/sign-in-experience';
import { trySubmitSafe } from '@/utils/form';
import { uriValidator } from '@/utils/validator';
import styles from './index.module.scss';
import NonThirdPartyBrandingForm from './NonThirdPartyBrandingForm';
import useApplicationSignInExperienceSWR from './use-application-sign-in-experience-swr';
import useSignInExperienceSWR from './use-sign-in-experience-swr';
import { type ApplicationSignInExperienceForm, formatFormToSubmitData } from './utils';
@ -119,84 +116,6 @@ function Branding({ application, isActive }: Props) {
}
}, [color, isBrandingEnabled, setValue]);
const [primaryColor, darkPrimaryColor] = watch(['color.primaryColor', 'color.darkPrimaryColor']);
const calculatedDarkPrimaryColor = useMemo(() => {
return primaryColor && generateDarkColor(primaryColor);
}, [primaryColor]);
const handleResetColor = useCallback(() => {
setValue('color.darkPrimaryColor', calculatedDarkPrimaryColor);
}, [calculatedDarkPrimaryColor, setValue]);
const NonThirdPartyBrandingForm = useCallback(
() => (
<>
<LogoAndFavicon
control={control}
register={register}
theme={Theme.Light}
type="app_logo"
logo={{ name: 'branding.logoUrl', error: errors.branding?.logoUrl }}
favicon={{
name: 'branding.favicon',
error: errors.branding?.favicon,
}}
/>
<LogoAndFavicon
control={control}
register={register}
theme={Theme.Dark}
type="app_logo"
logo={{ name: 'branding.darkLogoUrl', error: errors.branding?.darkLogoUrl }}
favicon={{
name: 'branding.darkFavicon',
error: errors.branding?.darkFavicon,
}}
/>
<div className={styles.colors}>
<Controller
control={control}
name="color.primaryColor"
render={({ field: { name, value, onChange } }) => (
<FormField title="application_details.branding.brand_color">
<ColorPicker name={name} value={value} onChange={onChange} />
</FormField>
)}
/>
<Controller
control={control}
name="color.darkPrimaryColor"
render={({ field: { name, value, onChange } }) => (
<FormField title="application_details.branding.brand_color_dark">
<ColorPicker name={name} value={value} onChange={onChange} />
</FormField>
)}
/>
{calculatedDarkPrimaryColor !== darkPrimaryColor && (
<div className={styles.darkModeTip}>
{t('sign_in_exp.color.dark_mode_reset_tip')}
<Button
type="text"
size="small"
title="sign_in_exp.color.reset"
onClick={handleResetColor}
/>
</div>
)}
</div>
</>
),
[
control,
errors.branding,
register,
calculatedDarkPrimaryColor,
darkPrimaryColor,
handleResetColor,
t,
]
);
if (isLoading) {
return <FormCardSkeleton />;
}

View file

@ -35,7 +35,7 @@ function BrandingForm() {
}, [primaryColor]);
const handleResetColor = useCallback(() => {
setValue('color.darkPrimaryColor', calculatedDarkPrimaryColor);
setValue('color.darkPrimaryColor', calculatedDarkPrimaryColor, { shouldDirty: true });
}, [calculatedDarkPrimaryColor, setValue]);
useEffect(() => {
@ -56,8 +56,8 @@ function BrandingForm() {
<Controller
name="color.primaryColor"
control={control}
render={({ field: { onChange, value } }) => (
<ColorPicker value={value} onChange={onChange} />
render={({ field: { name, onChange, value } }) => (
<ColorPicker name={name} value={value} onChange={onChange} />
)}
/>
</FormField>

View file

@ -31,7 +31,7 @@ export const expectToSelectColor = async (
await expect(colorField).toClick('div[role=button]');
await expect(page).toFill('input[id^=rc-editable-input]', color);
await expect(page).toFill('input[id=hex][class=rcp-field-input]', color);
// Close the color input
await page.keyboard.press('Escape');

View file

@ -3058,9 +3058,9 @@ importers:
react-animate-height:
specifier: ^3.0.4
version: 3.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-color:
specifier: ^2.19.3
version: 2.19.3(react@18.3.1)
react-color-palette:
specifier: ^7.2.1
version: 7.2.1(react@18.3.1)
react-confetti:
specifier: ^6.1.0
version: 6.1.0(react@18.3.1)
@ -4453,6 +4453,7 @@ packages:
'@azure/core-http@3.0.4':
resolution: {integrity: sha512-Fok9VVhMdxAFOtqiiAtg74fL0UJkt0z3D+ouUUxcRLzZNBioPRAMJFVxiWoJljYpXsRi4GDQHzQHDc9AiYaIUQ==}
engines: {node: '>=14.0.0'}
deprecated: deprecating as we migrated to core v2
'@azure/core-lro@2.5.1':
resolution: {integrity: sha512-JHQy/bA3NOz2WuzOi5zEk6n/TJdAropupxUT521JIJvW7EXV2YN2SFYZrf/2RHeD28QAClGdynYadZsbmP+nyQ==}
@ -5282,11 +5283,6 @@ packages:
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
deprecated: Use @eslint/object-schema instead
'@icons/material@0.2.4':
resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==}
peerDependencies:
react: '*'
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@ -10319,9 +10315,6 @@ packages:
markdown-table@3.0.2:
resolution: {integrity: sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA==}
material-colors@1.2.6:
resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==}
mathml-tag-names@2.1.3:
resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==}
@ -11480,6 +11473,7 @@ packages:
puppeteer@22.6.5:
resolution: {integrity: sha512-YuoRKGj3MxHhUwrey7vmNvU4odGdUdNsj1ee8pfcqQlLWIXfMOXZCAXh8xdzpZESHH3tCGWp2xmPZE8E6iUEWg==}
engines: {node: '>=18'}
deprecated: < 22.8.2 is no longer supported
hasBin: true
pure-rand@6.0.0:
@ -11545,10 +11539,11 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
react-color@2.19.3:
resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==}
react-color-palette@7.2.1:
resolution: {integrity: sha512-wulPqPc6qblr/bsjbW50t/je9keTRrouzPhimktCzBleNUxVJNN84cOZX5XzuS8ubQPg3h57gUT6vAKlBUDhUQ==}
engines: {node: '>=10'}
peerDependencies:
react: '*'
react: '>=16.8'
react-confetti@6.1.0:
resolution: {integrity: sha512-7Ypx4vz0+g8ECVxr88W9zhcQpbeujJAVqL14ZnXJ3I23mOI9/oBVTQ3dkJhUmB0D6XOtCZEM6N0Gm9PMngkORw==}
@ -11751,11 +11746,6 @@ packages:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
reactcss@1.2.3:
resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==}
peerDependencies:
react: '*'
read-pkg-up@7.0.1:
resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==}
engines: {node: '>=8'}
@ -12555,9 +12545,6 @@ packages:
tinybench@2.8.0:
resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==}
tinycolor2@1.6.0:
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
tinypool@1.0.0:
resolution: {integrity: sha512-KIKExllK7jp3uvrNtvRBYBWBOAXSX8ZvoaD8T+7KB/QHIuoJW3Pmr60zucywjAlMb5TeXUkcs/MWeWLu0qvuAQ==}
engines: {node: ^18.0.0 || >=20.0.0}
@ -14833,10 +14820,6 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
'@icons/material@0.2.4(react@18.3.1)':
dependencies:
react: 18.3.1
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@ -18197,6 +18180,10 @@ snapshots:
debounce@1.2.1: {}
debug@3.2.7:
dependencies:
ms: 2.1.3
debug@3.2.7(supports-color@5.5.0):
dependencies:
ms: 2.1.3
@ -18672,7 +18659,7 @@ snapshots:
eslint-import-resolver-node@0.3.9:
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7
is-core-module: 2.13.1
resolve: 1.22.8
transitivePeerDependencies:
@ -18697,7 +18684,7 @@ snapshots:
eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
dependencies:
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7
optionalDependencies:
'@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.5.3)
eslint: 8.57.0
@ -18730,7 +18717,7 @@ snapshots:
array.prototype.findlastindex: 1.2.5
array.prototype.flat: 1.3.2
array.prototype.flatmap: 1.3.2
debug: 3.2.7(supports-color@5.5.0)
debug: 3.2.7
doctrine: 2.1.0
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@ -21264,8 +21251,6 @@ snapshots:
markdown-table@3.0.2: {}
material-colors@1.2.6: {}
mathml-tag-names@2.1.3: {}
mdast-util-find-and-replace@3.0.1:
@ -22887,16 +22872,9 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-color@2.19.3(react@18.3.1):
react-color-palette@7.2.1(react@18.3.1):
dependencies:
'@icons/material': 0.2.4(react@18.3.1)
lodash: 4.17.21
lodash-es: 4.17.21
material-colors: 1.2.6
prop-types: 15.8.1
react: 18.3.1
reactcss: 1.2.3(react@18.3.1)
tinycolor2: 1.6.0
react-confetti@6.1.0(react@18.3.1):
dependencies:
@ -23099,11 +23077,6 @@ snapshots:
dependencies:
loose-envify: 1.4.0
reactcss@1.2.3(react@18.3.1):
dependencies:
lodash: 4.17.21
react: 18.3.1
read-pkg-up@7.0.1:
dependencies:
find-up: 4.1.0
@ -24135,8 +24108,6 @@ snapshots:
tinybench@2.8.0: {}
tinycolor2@1.6.0: {}
tinypool@1.0.0: {}
tinyspy@3.0.0: {}