Skip to content

Commit

Permalink
I18N settings modal, creation (#10749)
Browse files Browse the repository at this point in the history
  • Loading branch information
Marvin Frachet committed Sep 8, 2021
1 parent 81aa2d8 commit b77feb9
Show file tree
Hide file tree
Showing 18 changed files with 403 additions and 296 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import { Form } from '@strapi/helper-plugin';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { Formik } from 'formik';

import { Column, LayoutContent } from '../../../../layouts/UnauthenticatedLayout';
import Logo from '../Logo';
import FieldActionWrapper from '../FieldActionWrapper';
Expand Down
3 changes: 2 additions & 1 deletion packages/core/admin/admin/src/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -605,5 +605,6 @@
"notification.version.update.message": "A new version of Strapi is available!",
"or": "OR",
"request.error.model.unknown": "This model doesn't exist",
"skipToContent": "Skip to content"
"skipToContent": "Skip to content",
"clearLabel": "Clear"
}
4 changes: 2 additions & 2 deletions packages/core/admin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
"@fortawesome/react-fontawesome": "^0.1.14",
"@strapi/babel-plugin-switch-ee-ce": "1.0.0",
"@strapi/helper-plugin": "3.6.8",
"@strapi/icons": "0.0.1-alpha.22",
"@strapi/parts": "0.0.1-alpha.22",
"@strapi/icons": "0.0.1-alpha.23",
"@strapi/parts": "0.0.1-alpha.23",
"@strapi/utils": "3.6.8",
"axios": "^0.21.1",
"babel-loader": "8.2.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<!--- EmptyStateLayout.stories.mdx --->

import { Meta, ArgsTable, Canvas, Story } from '@storybook/addon-docs';
import { Main, Row } from '@strapi/parts';
import { TextInput } from '@strapi/parts/TextInput';
import { Button } from '@strapi/parts/Button';
import { Box } from '@strapi/parts/Box';
import { Text } from '@strapi/parts/Text';
import { Formik } from 'formik';
import * as Yup from 'yup';
import Form from './';

<Meta title="components/Form" />

# Form

This component is used to display a Formik form with additionnal error handling on fields

## Per field error

<Canvas>
<Story name="Per field error">
<Main>
<Formik initialValues={{ email: '' }} validationSchema={Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required')})}>
{({ handleSubmit, values, errors, handleChange }) => (
<Form onSubmit={handleSubmit}>
<TextInput
name="email"
label="Email"
hint="Enter a valid email"
value={values.email}
error={errors.email}
value={values.displayName}
onChange={handleChange}
/>
<Button type="submit">Submit</Button>
</Form>
)}
</Formik>
</Main>
</Story>
</Canvas>

## Per form error

Make sure to run this story in detached mode. The embeded iframe breaks the focus behaviour.

<Canvas>
<Story name="Per form error">
<Main>
<Formik initialValues={{ email: '' }} validationSchema={Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required')})} validateOnChange={false}>
{({ handleSubmit, values, errors, handleChange }) => (
<Form onSubmit={handleSubmit} noValidate>
{errors.email ? <Box padding={3} hasRadius background="danger100" color="danger600" tabIndex={-1} id="global-form-error">
<Text>The email is invalid</Text>
</Box> : null}
<TextInput
name="email"
label="Email"
hint="Enter a valid email"
value={values.email}
value={values.displayName}
onChange={handleChange}
/>
<Button type="submit">Submit</Button>
</Form>
)}
</Formik>
</Main>
</Story>
</Canvas>

### Props

<ArgsTable of={EmptyStateLayout} />
37 changes: 37 additions & 0 deletions packages/core/helper-plugin/lib/src/components/Form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useEffect, useRef } from 'react';
import { Form, useFormikContext } from 'formik';

const FormWithFocus = props => {
const formRef = useRef(null);
const { isSubmitting, isValidating, errors, touched } = useFormikContext();

useEffect(() => {
if (isSubmitting && !isValidating) {
const errorsInForm = formRef.current.querySelectorAll('[data-strapi-field-error]');

if (errorsInForm.length > 0) {
const firstError = errorsInForm[0];
const describingId = firstError.getAttribute('id');
const formElementInError = formRef.current.querySelector(
`[aria-describedby="${describingId}"]`
);

if (formElementInError) {
formElementInError.focus();
}
}
}

if (!isSubmitting && !isValidating && Object.keys(errors).length) {
const el = document.getElementById('global-form-error');

if (el) {
el.focus();
}
}
}, [errors, isSubmitting, isValidating, touched]);

return <Form ref={formRef} {...props} noValidate />;
};

export default FormWithFocus;
2 changes: 1 addition & 1 deletion packages/core/helper-plugin/lib/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ export { default as HeaderSearch } from './old/components/HeaderSearch';
export { default as IcoContainer } from './old/components/IcoContainer';
export { default as InputAddon } from './old/components/InputAddon';
export { default as EmptyState } from './old/components/EmptyState';
export { default as Form } from './old/components/Form';
export * from './old/components/Tabs';
export * from './old/components/Select';

Expand Down Expand Up @@ -191,6 +190,7 @@ export { default as LoadingIndicatorPage } from './components/LoadingIndicatorPa
export { default as SettingsPageTitle } from './components/SettingsPageTitle';
export { default as Search } from './components/Search';
export { default as Status } from './components/Status';
export { default as Form } from './components/Form';

// New icons
export { default as SortIcon } from './icons/SortIcon';
Expand Down
37 changes: 0 additions & 37 deletions packages/core/helper-plugin/lib/src/old/components/Form/index.js

This file was deleted.

5 changes: 3 additions & 2 deletions packages/core/helper-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"peerDependencies": {
"immer": "9.0.5",
"qs": "6.10.1",
"formik": "^2.2.6",
"react-select": "^4.0.2"
},
"devDependencies": {
Expand All @@ -56,8 +57,8 @@
"@storybook/builder-webpack5": "^6.3.7",
"@storybook/manager-webpack5": "^6.3.7",
"@storybook/react": "^6.3.7",
"@strapi/icons": "0.0.1-alpha.22",
"@strapi/parts": "0.0.1-alpha.22",
"@strapi/icons": "0.0.1-alpha.23",
"@strapi/parts": "0.0.1-alpha.23",
"babel-loader": "^8.2.2",
"cross-env": "^7.0.3",
"enzyme": "^3.8.0",
Expand Down
30 changes: 12 additions & 18 deletions packages/plugins/i18n/admin/src/components/LocaleList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,21 @@ const LocaleList = ({ canUpdateLocale, canDeleteLocale, onToggleCreateModal, isC
onEditLocale={handleEditLocale}
/>
) : (
<ContentLayout>
<EmptyStateLayout
icon={<EmptyStateDocument width={undefined} height={undefined} />}
content={formatMessage({ id: getTrad('Settings.list.empty.title') })}
action={
onToggleCreateModal ? (
<Button variant="secondary" startIcon={<AddIcon />} onClick={onToggleCreateModal}>
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
</Button>
) : null
}
/>
</ContentLayout>
<EmptyStateLayout
icon={<EmptyStateDocument width={undefined} height={undefined} />}
content={formatMessage({ id: getTrad('Settings.list.empty.title') })}
action={
onToggleCreateModal ? (
<Button variant="secondary" startIcon={<AddIcon />} onClick={onToggleCreateModal}>
{formatMessage({ id: getTrad('Settings.list.actions.add') })}
</Button>
) : null
}
/>
)}
</ContentLayout>

<ModalCreate
isOpened={isCreating}
onClose={onToggleCreateModal}
alreadyUsedLocales={locales}
/>
{isCreating && <ModalCreate onClose={onToggleCreateModal} />}
<ModalDelete localeToDelete={localeToDelete} onClose={closeModalToDelete} />
<ModalEdit localeToEdit={localeToEdit} onClose={closeModalToEdit} locales={locales} />
</Main>
Expand Down
88 changes: 88 additions & 0 deletions packages/plugins/i18n/admin/src/components/LocaleSelect/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from 'react';
import styled from 'styled-components';
import { Select, Option } from '@strapi/parts/Select';
import { Loader } from '@strapi/parts/Loader';
import { useIntl } from 'react-intl';
import PropTypes from 'prop-types';
import useLocales from '../../hooks/useLocales';
import useDefaultLocales from '../../hooks/useDefaultLocales';
import { getTrad } from '../../utils';

const SmallLoader = styled(Loader)`
img {
height: 1rem;
width: 1rem;
}
`;

/**
* The component is memoized and needs a useCallback over the onLocaleChange and
* onClear props to prevent the Select from re-rendering N times when typing on a specific
* key in a formik form
*/
const LocaleSelect = React.memo(({ value, onLocaleChange, error, onClear }) => {
const { formatMessage } = useIntl();
const { defaultLocales, isLoading } = useDefaultLocales();
const { locales } = useLocales();

const options = (defaultLocales || [])
.map(locale => ({
label: locale.name,
value: locale.code,
}))
.filter(({ value: v }) => {
const foundLocale = locales.find(({ code }) => code === v);

return !foundLocale;
});

const computedValue = value || '';

return (
<Select
startIcon={isLoading ? <SmallLoader>Loading the locales...</SmallLoader> : undefined}
aria-busy={isLoading}
label={formatMessage({
id: getTrad('Settings.locales.modal.locales.label'),
defaultMessage: 'Locales',
})}
onClear={value ? onClear : undefined}
clearLabel={formatMessage({
id: 'clearLabel',
defaultMessage: 'Clear',
})}
error={error}
value={computedValue}
onChange={selectedLocaleKey => {
const selectedLocale = options.find(locale => locale.value === selectedLocaleKey);

if (selectedLocale) {
onLocaleChange({ code: selectedLocale.value, displayName: selectedLocale.label });
}
}}
>
{isLoading
? null
: options.map(option => (
<Option value={option.value} key={option.value}>
{option.label}
</Option>
))}
</Select>
);
});

LocaleSelect.defaultProps = {
error: undefined,
value: undefined,
onClear: () => undefined,
};

LocaleSelect.propTypes = {
error: PropTypes.string,
onClear: PropTypes.func,
onLocaleChange: PropTypes.func.isRequired,
value: PropTypes.string,
};

export default LocaleSelect;
Loading

0 comments on commit b77feb9

Please sign in to comment.