mirror of
https://github.com/plankanban/planka.git
synced 2025-07-19 13:19:44 +02:00
Allow creating label without name
This commit is contained in:
parent
b9d4024957
commit
4911816734
9 changed files with 31 additions and 51 deletions
|
@ -48,7 +48,7 @@ const Label = React.memo(({
|
||||||
className={classNames(styles.wrapper, onClick && styles.hoverable)}
|
className={classNames(styles.wrapper, onClick && styles.hoverable)}
|
||||||
style={style}
|
style={style}
|
||||||
>
|
>
|
||||||
{name}
|
{name || '\u00A0'}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -62,14 +62,15 @@ const Label = React.memo(({
|
||||||
});
|
});
|
||||||
|
|
||||||
Label.propTypes = {
|
Label.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string,
|
||||||
color: PropTypes.oneOf(LabelColors.KEYS).isRequired, // TODO: without color
|
color: PropTypes.oneOf(LabelColors.KEYS).isRequired,
|
||||||
size: PropTypes.oneOf(Object.values(SIZES)),
|
size: PropTypes.oneOf(Object.values(SIZES)),
|
||||||
isDisabled: PropTypes.bool,
|
isDisabled: PropTypes.bool,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
Label.defaultProps = {
|
Label.defaultProps = {
|
||||||
|
name: undefined,
|
||||||
size: SIZES.MEDIUM,
|
size: SIZES.MEDIUM,
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
onClick: undefined,
|
onClick: undefined,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useRef } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Form } from 'semantic-ui-react';
|
import { Button, Form } from 'semantic-ui-react';
|
||||||
|
@ -18,19 +18,12 @@ const AddStep = React.memo(({ onCreate, onBack }) => {
|
||||||
color: LabelColors.KEYS[0],
|
color: LabelColors.KEYS[0],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const editor = useRef(null);
|
|
||||||
|
|
||||||
const handleSubmit = useDeepCompareCallback(() => {
|
const handleSubmit = useDeepCompareCallback(() => {
|
||||||
const cleanData = {
|
const cleanData = {
|
||||||
...data,
|
...data,
|
||||||
name: data.name.trim(),
|
name: data.name.trim() || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!cleanData.name) {
|
|
||||||
editor.current.selectNameField();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
onCreate(cleanData);
|
onCreate(cleanData);
|
||||||
onBack();
|
onBack();
|
||||||
}, [data, onCreate, onBack]);
|
}, [data, onCreate, onBack]);
|
||||||
|
@ -44,7 +37,7 @@ const AddStep = React.memo(({ onCreate, onBack }) => {
|
||||||
</Popup.Header>
|
</Popup.Header>
|
||||||
<Popup.Content>
|
<Popup.Content>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<Editor ref={editor} data={data} onFieldChange={handleFieldChange} />
|
<Editor data={data} onFieldChange={handleFieldChange} />
|
||||||
<Button positive content={t('action.createLabel')} className={styles.submitButton} />
|
<Button positive content={t('action.createLabel')} className={styles.submitButton} />
|
||||||
</Form>
|
</Form>
|
||||||
</Popup.Content>
|
</Popup.Content>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import dequal from 'dequal';
|
import dequal from 'dequal';
|
||||||
import React, { useCallback, useRef } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Button, Form } from 'semantic-ui-react';
|
import { Button, Form } from 'semantic-ui-react';
|
||||||
|
@ -22,26 +22,19 @@ const EditStep = React.memo(({
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const [data, handleFieldChange] = useForm(() => ({
|
const [data, handleFieldChange] = useForm(() => ({
|
||||||
name: '',
|
|
||||||
color: LabelColors.KEYS[0],
|
color: LabelColors.KEYS[0],
|
||||||
...defaultData,
|
...defaultData,
|
||||||
|
name: defaultData.name || '',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const [step, openStep, handleBack] = useSteps();
|
const [step, openStep, handleBack] = useSteps();
|
||||||
|
|
||||||
const editor = useRef(null);
|
|
||||||
|
|
||||||
const handleSubmit = useDeepCompareCallback(() => {
|
const handleSubmit = useDeepCompareCallback(() => {
|
||||||
const cleanData = {
|
const cleanData = {
|
||||||
...data,
|
...data,
|
||||||
name: data.name.trim(),
|
name: data.name.trim() || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!cleanData.name) {
|
|
||||||
editor.current.selectNameField();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!dequal(cleanData, defaultData)) {
|
if (!dequal(cleanData, defaultData)) {
|
||||||
onUpdate(cleanData);
|
onUpdate(cleanData);
|
||||||
}
|
}
|
||||||
|
@ -76,7 +69,7 @@ const EditStep = React.memo(({
|
||||||
</Popup.Header>
|
</Popup.Header>
|
||||||
<Popup.Content>
|
<Popup.Content>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<Editor ref={editor} data={data} onFieldChange={handleFieldChange} />
|
<Editor data={data} onFieldChange={handleFieldChange} />
|
||||||
<Button positive content={t('action.save')} />
|
<Button positive content={t('action.save')} />
|
||||||
</Form>
|
</Form>
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import React, {
|
import React, { useEffect, useRef } from 'react';
|
||||||
useCallback, useEffect, useImperativeHandle, useRef,
|
|
||||||
} from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -11,27 +9,15 @@ import LabelColors from '../../constants/LabelColors';
|
||||||
|
|
||||||
import styles from './Editor.module.css';
|
import styles from './Editor.module.css';
|
||||||
|
|
||||||
const Editor = React.forwardRef(({ data, onFieldChange }, ref) => {
|
const Editor = React.memo(({ data, onFieldChange }) => {
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
|
|
||||||
const nameField = useRef(null);
|
const nameField = useRef(null);
|
||||||
|
|
||||||
const selectNameField = useCallback(() => {
|
useEffect(() => {
|
||||||
nameField.current.select();
|
nameField.current.select();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useImperativeHandle(
|
|
||||||
ref,
|
|
||||||
() => ({
|
|
||||||
selectNameField,
|
|
||||||
}),
|
|
||||||
[selectNameField],
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
selectNameField();
|
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.text}>{t('common.title')}</div>
|
<div className={styles.text}>{t('common.title')}</div>
|
||||||
|
@ -71,4 +57,4 @@ Editor.propTypes = {
|
||||||
onFieldChange: PropTypes.func.isRequired,
|
onFieldChange: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default React.memo(Editor);
|
export default Editor;
|
||||||
|
|
|
@ -43,7 +43,7 @@ const Item = React.memo(({
|
||||||
});
|
});
|
||||||
|
|
||||||
Item.propTypes = {
|
Item.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string,
|
||||||
color: PropTypes.string.isRequired,
|
color: PropTypes.string.isRequired,
|
||||||
isPersisted: PropTypes.bool.isRequired,
|
isPersisted: PropTypes.bool.isRequired,
|
||||||
isActive: PropTypes.bool.isRequired,
|
isActive: PropTypes.bool.isRequired,
|
||||||
|
@ -52,4 +52,8 @@ Item.propTypes = {
|
||||||
onEdit: PropTypes.func.isRequired,
|
onEdit: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Item.defaultProps = {
|
||||||
|
name: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
export default Item;
|
export default Item;
|
||||||
|
|
|
@ -12,12 +12,13 @@ module.exports = {
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true
|
isNotEmptyString: true,
|
||||||
|
allowNull: true
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isIn: Label.COLORS,
|
isIn: Label.COLORS,
|
||||||
allowNull: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,13 @@ module.exports = {
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isNotEmptyString: true
|
isNotEmptyString: true,
|
||||||
|
allowNull: true
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isIn: Label.COLORS,
|
isIn: Label.COLORS,
|
||||||
allowNull: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,13 @@ module.exports = {
|
||||||
|
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
required: true
|
isNotEmptyString: true,
|
||||||
|
allowNull: true
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
isIn: COLORS,
|
isIn: COLORS,
|
||||||
allowNull: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
||||||
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
||||||
|
|
|
@ -6,8 +6,8 @@ module.exports.up = knex =>
|
||||||
|
|
||||||
table.integer('board_id').notNullable();
|
table.integer('board_id').notNullable();
|
||||||
|
|
||||||
table.text('name').notNullable();
|
table.text('name');
|
||||||
table.text('color');
|
table.text('color').notNullable();
|
||||||
|
|
||||||
table.timestamp('created_at', true);
|
table.timestamp('created_at', true);
|
||||||
table.timestamp('updated_at', true);
|
table.timestamp('updated_at', true);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue