diff --git a/docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleDeviceVisibility.js b/docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleOnly.js similarity index 93% rename from docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleDeviceVisibility.js rename to docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleOnly.js index 0ea0e8eef0..26ca88fa27 100644 --- a/docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleDeviceVisibility.js +++ b/docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleOnly.js @@ -1,7 +1,7 @@ import React from 'react' import { Grid, Segment } from 'semantic-ui-react' -const GridExampleDeviceVisibility = () => ( +const GridExampleOnly = () => ( @@ -55,7 +55,7 @@ const GridExampleDeviceVisibility = () => ( Computer - + Tablet @@ -69,4 +69,4 @@ const GridExampleDeviceVisibility = () => ( ) -export default GridExampleDeviceVisibility +export default GridExampleOnly diff --git a/docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleOnlyMultiple.js b/docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleOnlyMultiple.js new file mode 100644 index 0000000000..5189b893fb --- /dev/null +++ b/docs/app/Examples/collections/Grid/ResponsiveVariations/GridExampleOnlyMultiple.js @@ -0,0 +1,35 @@ +import React from 'react' +import { Grid, Segment } from 'semantic-ui-react' + +const GridExampleOnlyMultiple = () => ( + + + + Mobile + + + Tablet + + + + + + Tablet + + + Computer + + + + + + Large Screen + + + Widescreen + + + +) + +export default GridExampleOnlyMultiple diff --git a/docs/app/Examples/collections/Grid/ResponsiveVariations/index.js b/docs/app/Examples/collections/Grid/ResponsiveVariations/index.js index d4fc3008d5..0bd44b5357 100644 --- a/docs/app/Examples/collections/Grid/ResponsiveVariations/index.js +++ b/docs/app/Examples/collections/Grid/ResponsiveVariations/index.js @@ -40,8 +40,9 @@ const GridResponsiveVariationsExamples = () => ( + *
*/ -import { numberToWord } from './numberToWord' /** * Props where only the prop key is used in the className. @@ -51,36 +52,25 @@ export const useKeyOrValueAndKey = (val, key) => val && (val === true ? key : `$ // /** - * Create "X", "X wide" and "equal width" classNames. - * "X" is a numberToWord value and "wide" is configurable. - * @param {*} val The prop value - * @param {string} [widthClass=''] The class - * @param {boolean} [canEqual=false] Flag that indicates possibility of "equal" value + * The "only" prop implements control of visibility classes for Grid subcomponents. * - * @example - * - *
- * - *
- *
- * - * - *
+ * @param {*} val The value of the "only" prop * * @example - * - *
+ * + * + *
+ *
*/ -export const useWidthProp = (val, widthClass = '', canEqual = false) => { - if (canEqual && val === 'equal') { - return 'equal width' - } - const valType = typeof val - if ((valType === 'string' || valType === 'number') && widthClass) { - return `${numberToWord(val)} ${widthClass}` - } - return numberToWord(val) +export const useOnlyProp = val => { + if (!val || val === true) return null + + return val.replace('large screen', 'large-screen') + .split(' ') + .map(prop => `${prop.replace('-', ' ')} only`) + .join(' ') } + /** * The "textAlign" prop follows the useValueAndKey except when the value is "justified'. * In this case, only the class "justified" is used, ignoring the "aligned" class. @@ -106,3 +96,35 @@ export const useTextAlignProp = val => val === 'justified' ? 'justified' : useVa *
*/ export const useVerticalAlignProp = val => useValueAndKey(val, 'aligned') + +/** + * Create "X", "X wide" and "equal width" classNames. + * "X" is a numberToWord value and "wide" is configurable. + * @param {*} val The prop value + * @param {string} [widthClass=''] The class + * @param {boolean} [canEqual=false] Flag that indicates possibility of "equal" value + * + * @example + * + *
+ * + * + *
+ * + * + *
+ * + * @example + * + *
+ */ +export const useWidthProp = (val, widthClass = '', canEqual = false) => { + if (canEqual && val === 'equal') { + return 'equal width' + } + const valType = typeof val + if ((valType === 'string' || valType === 'number') && widthClass) { + return `${numberToWord(val)} ${widthClass}` + } + return numberToWord(val) +} diff --git a/src/lib/customPropTypes.js b/src/lib/customPropTypes.js index 9645ef0e9b..ccff3a17a9 100644 --- a/src/lib/customPropTypes.js +++ b/src/lib/customPropTypes.js @@ -236,6 +236,37 @@ export const demand = (requiredProps) => { } } +/** + * Ensure an only prop contains a string with only possible values. + * @param {string[]} possible An array of possible values to prop. + */ +export const onlyProp = possible => { + return (props, propName, componentName) => { + if (!Array.isArray(possible)) { + throw new Error([ + 'Invalid argument supplied to some, expected an instance of array.', + `See \`${propName}\` prop in \`${componentName}\`.`, + ].join(' ')) + } + + const propValue = props[propName] + + // skip if prop is undefined + if (_.isNil(propValue) || propValue === false) return + + const values = propValue + .replace('large screen', 'large-screen') + .split(' ') + .map(val => _.trim(val).replace('-', ' ')) + const invalid = _.difference(values, possible) + + // fail only if there are invalid values + if (invalid.length > 0) { + return new Error(`\`${propName}\` prop in \`${componentName}\` has invalid values: \`${invalid.join('`, `')}\`.`) + } + } +} + /** * Ensure a component can render as a node passed as a prop value in place of children. */ diff --git a/src/lib/index.js b/src/lib/index.js index a241be811d..810a913c06 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -3,11 +3,13 @@ export * as childrenUtils from './childrenUtils' export { useKeyOnly, - useValueAndKey, useKeyOrValueAndKey, - useWidthProp, + useValueAndKey, + + useOnlyProp, useTextAlignProp, useVerticalAlignProp, + useWidthProp, } from './classNameBuilders' export * as customPropTypes from './customPropTypes' diff --git a/test/specs/collections/Grid/GridColumn-test.js b/test/specs/collections/Grid/GridColumn-test.js index 6d2df8e6ca..942171be72 100644 --- a/test/specs/collections/Grid/GridColumn-test.js +++ b/test/specs/collections/Grid/GridColumn-test.js @@ -6,6 +6,7 @@ describe('GridColumn', () => { common.isConformant(GridColumn) common.rendersChildren(GridColumn) + common.implementsOnlyProp(GridColumn) common.implementsTextAlignProp(GridColumn) common.implementsVerticalAlignProp(GridColumn) @@ -41,9 +42,6 @@ describe('GridColumn', () => { }) common.propKeyAndValueToClassName(GridColumn, 'floated', SUI.FLOATS) - common.propKeyAndValueToClassName(GridColumn, 'only', [ - 'computer', 'large screen', 'mobile', 'tablet mobile', 'tablet', 'widescreen', - ]) common.propKeyOnlyToClassName(GridColumn, 'stretched') diff --git a/test/specs/collections/Grid/GridRow-test.js b/test/specs/collections/Grid/GridRow-test.js index c7bf77bf74..85d5d468de 100644 --- a/test/specs/collections/Grid/GridRow-test.js +++ b/test/specs/collections/Grid/GridRow-test.js @@ -6,6 +6,7 @@ describe('GridRow', () => { common.isConformant(GridRow) common.rendersChildren(GridRow) + common.implementsOnlyProp(GridRow) common.implementsTextAlignProp(GridRow) common.implementsVerticalAlignProp(GridRow) common.implementsWidthProp(GridRow, SUI.WIDTHS, { @@ -13,9 +14,6 @@ describe('GridRow', () => { widthClass: 'column', }) - common.propKeyAndValueToClassName(GridRow, 'only', [ - 'computer', 'large screen', 'mobile', 'tablet mobile', 'tablet', 'widescreen', - ]) common.propKeyAndValueToClassName(GridRow, 'reversed', [ ['computer', 'computer vertically', 'mobile', 'mobile vertically', 'tablet', 'tablet vertically'], ]) diff --git a/test/specs/commonTests.js b/test/specs/commonTests.js index 41113e0571..ae583bd65d 100644 --- a/test/specs/commonTests.js +++ b/test/specs/commonTests.js @@ -856,6 +856,35 @@ export const implementsImageProp = (Component, options = {}) => { }) } +/** + * Assert that a Component correctly implements the "only" prop. + * @param {React.Component|Function} Component The component to test. + */ +export const implementsOnlyProp = Component => { + const { assertRequired } = commonTestHelpers('propKeyAndValueToClassName', Component) + const propValues = SUI.VISIBILITY + + describe('only (common)', () => { + assertRequired(Component, 'a `Component`') + + _noDefaultClassNameFromProp(Component, 'only', propValues) + _noClassNameFromBoolProps(Component, 'only', propValues) + + propValues.forEach(propVal => { + it(`adds "${propVal} only" to className`, () => { + shallow(createElement(Component, { only: propVal })).should.have.className(`${propVal} only`) + }) + }) + + it('adds all possible values to className', () => { + const className = propValues.map(prop => `${prop} only`).join(' ') + const propValue = propValues.join(' ') + + shallow(createElement(Component, { only: propValue })).should.have.className(className) + }) + }) +} + /** * Assert that a Component correctly implements the "textAlign" prop. * @param {React.Component|Function} Component The component to test.