Skip to content
Open
26 changes: 23 additions & 3 deletions dev-test/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,31 @@ collections: # A list of collections the CMS should be able to edit
display_fields: ['title', 'datetime']
search_fields: ['title', 'body']
value_field: 'title'
- { label: 'Title', name: 'title', widget: 'string' }
- { label: 'Boolean', name: 'boolean', widget: 'boolean', default: true }
- {
label: 'Title',
name: 'title',
widget: 'string',
prefix: 'This string:',
suffix: 'is a title'
}
- {
label: 'Boolean',
name: 'boolean',
widget: 'boolean',
prefix: 'OFF',
suffix: 'ON',
hint: 'Toggle this to switch on/off',
default: true
}
- { label: 'Map', name: 'map', widget: 'map' }
- { label: 'Text', name: 'text', widget: 'text', hint: 'Plain text, not markdown' }
- { label: 'Number', name: 'number', widget: 'number', hint: 'To infinity and beyond!' }
- {
label: 'Number',
name: 'number',
widget: 'number',
suffix: 'px',
hint: 'To infinity and beyond!'
}
- { label: 'Markdown', name: 'markdown', widget: 'markdown' }
- { label: 'Datetime', name: 'datetime', widget: 'datetime' }
- { label: 'Image', name: 'image', widget: 'image' }
Expand Down
59 changes: 36 additions & 23 deletions packages/decap-cms-widget-boolean/src/BooleanControl.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { css } from '@emotion/react';
import { Toggle, ToggleBackground, colors } from 'decap-cms-ui-default';

const innerWrapper = css`
display: flex;
align-items: center;
`;

function BooleanBackground({ isActive, ...props }) {
return (
<ToggleBackground
Expand All @@ -16,34 +21,42 @@ function BooleanBackground({ isActive, ...props }) {
}

export default class BooleanControl extends React.Component {
static propTypes = {
field: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
classNameWrapper: PropTypes.string.isRequired,
setActiveStyle: PropTypes.func.isRequired,
setInactiveStyle: PropTypes.func.isRequired,
forID: PropTypes.string,
value: PropTypes.bool,
};

static defaultProps = {
value: false,
};

render() {
const { value, forID, onChange, classNameWrapper, setActiveStyle, setInactiveStyle } =
const { value, forID, onChange, classNameWrapper, setActiveStyle, setInactiveStyle, field } =
this.props;

const prefix = field.get('prefix', '');
const suffix = field.get('suffix', '');

return (
<div className={classNameWrapper}>
<Toggle
id={forID}
active={value}
onChange={onChange}
onFocus={setActiveStyle}
onBlur={setInactiveStyle}
Background={BooleanBackground}
/>
<div css={innerWrapper}>
{prefix && <span>{prefix}&nbsp;</span>}
<Toggle
id={forID}
active={value}
onChange={onChange}
onFocus={setActiveStyle}
onBlur={setInactiveStyle}
Background={BooleanBackground}
/>
{suffix && <span>&nbsp;{suffix}</span>}
</div>
</div>
);
}
}

BooleanControl.propTypes = {
field: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
classNameWrapper: PropTypes.string.isRequired,
setActiveStyle: PropTypes.func.isRequired,
setInactiveStyle: PropTypes.func.isRequired,
forID: PropTypes.string,
value: PropTypes.bool,
};

BooleanControl.defaultProps = {
value: false,
};
74 changes: 74 additions & 0 deletions packages/decap-cms-widget-boolean/src/__tests__/boolean.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React from 'react';
import { fromJS } from 'immutable';
import { render } from '@testing-library/react';

import { DecapCmsWidgetBoolean } from '../';

const BooleanControl = DecapCmsWidgetBoolean.controlComponent;

function setup({ field, value = false }) {
const onChangeSpy = jest.fn();
const setActiveSpy = jest.fn();
const setInactiveSpy = jest.fn();

const helpers = render(
<BooleanControl
field={field}
value={value}
onChange={onChangeSpy}
forID="test-boolean"
classNameWrapper=""
setActiveStyle={setActiveSpy}
setInactiveStyle={setInactiveSpy}
/>,
);

return {
...helpers,
onChangeSpy,
setActiveSpy,
setInactiveSpy,
};
}

describe('Boolean widget', () => {
it('should render the toggle without prefix or suffix by default', () => {
const field = fromJS({ name: 'test' });
const { queryByText } = setup({ field });

expect(queryByText('OFF')).toBeNull();
expect(queryByText('ON')).toBeNull();
});

it('should render prefix text when prefix is configured', () => {
const field = fromJS({ name: 'test', prefix: 'OFF' });
const { getByText, queryByText } = setup({ field });

expect(getByText(/OFF/)).toBeInTheDocument();
expect(queryByText('ON')).toBeNull();
});

it('should render suffix text when suffix is configured', () => {
const field = fromJS({ name: 'test', suffix: 'ON' });
const { getByText, queryByText } = setup({ field });

expect(queryByText('OFF')).toBeNull();
expect(getByText(/ON/)).toBeInTheDocument();
});

it('should render both prefix and suffix when both are configured', () => {
const field = fromJS({ name: 'test', prefix: 'OFF', suffix: 'ON' });
const { getByText } = setup({ field });

expect(getByText(/OFF/)).toBeInTheDocument();
expect(getByText(/ON/)).toBeInTheDocument();
});

it('should not render prefix or suffix for empty strings', () => {
const field = fromJS({ name: 'test', prefix: '', suffix: '' });
const { queryByText } = setup({ field });

expect(queryByText('OFF')).toBeNull();
expect(queryByText('ON')).toBeNull();
});
});
43 changes: 31 additions & 12 deletions packages/decap-cms-widget-number/src/NumberControl.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { css } from '@emotion/react';

const ValidationErrorTypes = {
PRESENCE: 'PRESENCE',
PATTERN: 'PATTERN',
RANGE: 'RANGE',
CUSTOM: 'CUSTOM',
};

const innerWrapper = css`
display: flex;
align-items: baseline;
`;

export function validateMinMax(value, min, max, field, t) {
let error;

Expand Down Expand Up @@ -105,19 +112,31 @@ export default class NumberControl extends React.Component {
const min = field.get('min', '');
const max = field.get('max', '');
const step = field.get('step', field.get('value_type') === 'int' ? 1 : '');

const prefix = field.get('prefix', '');
const suffix = field.get('suffix', '');

return (
<input
type="number"
id={forID}
className={classNameWrapper}
onFocus={setActiveStyle}
onBlur={setInactiveStyle}
value={value || (value === 0 ? value : '')}
step={step}
min={min}
max={max}
onChange={this.handleChange}
/>
<div className={classNameWrapper}>
<div css={innerWrapper}>
{prefix && <span>{prefix}&nbsp;</span>}
<input
type="number"
id={forID}
onFocus={setActiveStyle}
onBlur={setInactiveStyle}
value={value || (value === 0 ? value : '')}
step={step}
min={min}
max={max}
onChange={this.handleChange}
css={css`
flex-grow: 1;
`}
/>
{suffix && <span>&nbsp;{suffix}</span>}
</div>
</div>
);
}
}
40 changes: 40 additions & 0 deletions packages/decap-cms-widget-number/src/__tests__/number.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,4 +211,44 @@ describe('Number widget', () => {
expect(error).toBeNull();
});
});

describe('prefix and suffix', () => {
it('should not render prefix or suffix by default', () => {
const field = fromJS(fieldSettings);
const { queryByText } = setup({ field });

expect(queryByText('$')).toBeNull();
expect(queryByText('px')).toBeNull();
});

it('should render prefix text when prefix is configured', () => {
const field = fromJS({ ...fieldSettings, prefix: '$' });
const { getByText } = setup({ field });

expect(getByText(/\$/)).toBeInTheDocument();
});

it('should render suffix text when suffix is configured', () => {
const field = fromJS({ ...fieldSettings, suffix: 'px' });
const { getByText } = setup({ field });

expect(getByText(/px/)).toBeInTheDocument();
});

it('should render both prefix and suffix when both are configured', () => {
const field = fromJS({ ...fieldSettings, prefix: '$', suffix: 'USD' });
const { getByText } = setup({ field });

expect(getByText(/\$/)).toBeInTheDocument();
expect(getByText(/USD/)).toBeInTheDocument();
});

it('should not render prefix or suffix for empty strings', () => {
const field = fromJS({ ...fieldSettings, prefix: '', suffix: '' });
const { queryByText } = setup({ field });

expect(queryByText('$')).toBeNull();
expect(queryByText('USD')).toBeNull();
});
});
});
2 changes: 2 additions & 0 deletions packages/decap-cms-widget-number/src/schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@ export default {
value_type: { type: 'string' },
min: { type: 'number' },
max: { type: 'number' },
prefix: { type: 'string' },
suffix: { type: 'string' },
},
};
45 changes: 32 additions & 13 deletions packages/decap-cms-widget-string/src/StringControl.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { css } from '@emotion/react';

const innerWrapper = css`
display: flex;
align-items: baseline;
`;

export default class StringControl extends React.Component {
static propTypes = {
field: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
forID: PropTypes.string,
value: PropTypes.node,
Expand Down Expand Up @@ -46,21 +54,32 @@ export default class StringControl extends React.Component {
};

render() {
const { forID, value, classNameWrapper, setActiveStyle, setInactiveStyle } = this.props;
const { field, forID, value, classNameWrapper, setActiveStyle, setInactiveStyle } = this.props;

const prefix = field.get('prefix', '');
const suffix = field.get('suffix', '');

return (
<input
ref={el => {
this._el = el;
}}
type="text"
id={forID}
className={classNameWrapper}
value={value || ''}
onChange={this.handleChange}
onFocus={setActiveStyle}
onBlur={setInactiveStyle}
/>
<div className={classNameWrapper}>
<div css={innerWrapper}>
{prefix && <span>{prefix}&nbsp;</span>}
<input
ref={el => {
this._el = el;
}}
type="text"
id={forID}
value={value || ''}
onChange={this.handleChange}
onFocus={setActiveStyle}
onBlur={setInactiveStyle}
css={css`
flex-grow: 1;
`}
/>
{suffix && <span>&nbsp;{suffix}</span>}
</div>
</div>
);
}
}
Loading
Loading