Docs Project: Add documentation parser + inline documentation (https://github.com/woocommerce/woocommerce-admin/pull/336)
* Switch all components to default exports This ensures we can read all components for documentation generation (plus, standardizing is good) * Add documentation to component file * Fix table exports * Move readme docs into inline docs Includes updating new props, adding prop shapes * Add doc-generation scripts to pull exported component docs into folder * Remove key propType, causing react special-keys warning * Fix proptype * Update incorrect comment * Remove template import, we can just use string concat * Fix typo, update docs
This commit is contained in:
parent
7711da472a
commit
d36511479e
|
@ -1 +1,2 @@
|
|||
bin
|
||||
bin/*
|
||||
!bin/generate-docs
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/** @format */
|
||||
/* eslint-disable no-console */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
const { parse } = require( 'react-docgen' );
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { getDescription, getProps, getTitle } = require( './lib/formatting' );
|
||||
const {
|
||||
COMPONENTS_FOLDER,
|
||||
deleteExistingDocs,
|
||||
getExportedFileList,
|
||||
getMdFileName,
|
||||
getRealFilePaths,
|
||||
writeTableOfContents,
|
||||
} = require( './lib/file-system' );
|
||||
|
||||
const filePath = path.resolve( COMPONENTS_FOLDER, 'index.js' );
|
||||
|
||||
// Start by wiping the existing docs. **Change this if we end up manually editing docs**
|
||||
deleteExistingDocs();
|
||||
|
||||
// Read components file to get a list of exported files, convert that to a list of absolute paths to public components.
|
||||
const files = getRealFilePaths( getExportedFileList( filePath ) );
|
||||
|
||||
// Build the documentation by reading each file.
|
||||
files.forEach( file => {
|
||||
try {
|
||||
const content = fs.readFileSync( file );
|
||||
buildDocs( file, content );
|
||||
} catch ( readErr ) {
|
||||
console.warn( file, readErr );
|
||||
}
|
||||
} );
|
||||
|
||||
writeTableOfContents( files );
|
||||
|
||||
console.log( `Wrote docs for ${ files.length } files.` );
|
||||
|
||||
/**
|
||||
* Parse each file's content & build up a markdown file.
|
||||
*
|
||||
* @param { string } fileName The absolute path of this file.
|
||||
* @param { string } content Content of this file.
|
||||
*/
|
||||
function buildDocs( fileName, content ) {
|
||||
try {
|
||||
const docObject = parse( content );
|
||||
const mdFileName = getMdFileName( fileName );
|
||||
const markdown = generateMarkdown( docObject );
|
||||
fs.appendFileSync( mdFileName, markdown );
|
||||
} catch ( parseErr ) {
|
||||
console.warn( fileName, parseErr );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert documentation object to a markdown string.
|
||||
*
|
||||
* @param { object } docObject The parsed documentation object.
|
||||
* @return { string } Generated markdown.
|
||||
*/
|
||||
function generateMarkdown( docObject ) {
|
||||
let markdownString = getTitle( docObject.displayName ) + '\n';
|
||||
markdownString += getDescription( docObject.description ) + '\n';
|
||||
markdownString += getProps( docObject.props );
|
||||
return markdownString;
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const { isArray, uniq } = require( 'lodash' );
|
||||
const fs = require( 'fs' );
|
||||
const path = require( 'path' );
|
||||
const recast = require( 'recast' );
|
||||
const types = require( 'ast-types' );
|
||||
const { namedTypes } = types;
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
const { camelCaseDash } = require( './formatting' );
|
||||
|
||||
const COMPONENTS_FOLDER = path.resolve( __dirname, '../../../client/components/' );
|
||||
const DOCS_FOLDER = path.resolve( __dirname, '../../../docs/components/' );
|
||||
|
||||
/**
|
||||
* Remove all files in existing docs folder.
|
||||
*/
|
||||
function deleteExistingDocs() {
|
||||
if ( ! isDirectory( DOCS_FOLDER ) ) {
|
||||
fs.mkdirSync( DOCS_FOLDER );
|
||||
return;
|
||||
}
|
||||
|
||||
const files = fs.readdirSync( DOCS_FOLDER );
|
||||
files.map( file => fs.unlinkSync( path.resolve( DOCS_FOLDER, file ) ) );
|
||||
fs.rmdirSync( DOCS_FOLDER );
|
||||
fs.mkdirSync( DOCS_FOLDER );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of files exported from in the given file
|
||||
*
|
||||
* @param { string } filePath The file to parse for exports.
|
||||
* @return { string } Formatted string.
|
||||
*/
|
||||
function getExportedFileList( filePath ) {
|
||||
const content = fs.readFileSync( filePath );
|
||||
const ast = recast.parse( content );
|
||||
const files = [];
|
||||
types.visit( ast, {
|
||||
// This method will be called for any node with .type "ExportNamedDeclaration":
|
||||
visitExportNamedDeclaration: function( nodePath ) {
|
||||
const { node } = nodePath;
|
||||
if (
|
||||
namedTypes.Literal.check( node.source ) &&
|
||||
isArray( node.specifiers ) &&
|
||||
namedTypes.ExportSpecifier.check( node.specifiers[ 0 ] )
|
||||
) {
|
||||
if ( -1 === node.source.value.indexOf( 'use-filters' ) ) {
|
||||
files.push( node.source.value );
|
||||
}
|
||||
}
|
||||
// Keep traversing this path…
|
||||
this.traverse( nodePath );
|
||||
},
|
||||
} );
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the markdown file name for a given component file.
|
||||
*
|
||||
* @param { string } filepath File path for this component.
|
||||
* @param { boolean } absolute Whether to return full path (true) or just filename (false).
|
||||
* @return { string } Markdown file name.
|
||||
*/
|
||||
function getMdFileName( filepath, absolute = true ) {
|
||||
const fileParts = filepath.split( '/components/' );
|
||||
if ( ! fileParts || ! fileParts[ 1 ] ) {
|
||||
return;
|
||||
}
|
||||
const name = fileParts[ 1 ].split( '/' )[ 0 ];
|
||||
if ( ! absolute ) {
|
||||
return name + '.md';
|
||||
}
|
||||
return path.resolve( DOCS_FOLDER, name + '.md' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of files exported from in the given file
|
||||
*
|
||||
* @param { array } files A list of files, presumably in the components directory.
|
||||
* @param { string } basePath The absolute path to the components directory.
|
||||
* @return { array } Updated array with absolute paths to all files.
|
||||
*/
|
||||
function getRealFilePaths( files, basePath = COMPONENTS_FOLDER ) {
|
||||
files.sort();
|
||||
return files.map( file => {
|
||||
const fullPath = path.resolve( basePath, file );
|
||||
if ( isFile( fullPath ) ) {
|
||||
return fullPath;
|
||||
}
|
||||
if ( isFile( `${ fullPath }.js` ) ) {
|
||||
return `${ fullPath }.js`;
|
||||
}
|
||||
if ( isFile( `${ fullPath }/index.js` ) ) {
|
||||
return `${ fullPath }/index.js`;
|
||||
}
|
||||
const folderName = path.basename( fullPath );
|
||||
if ( isFile( `${ fullPath }/${ folderName }.js` ) ) {
|
||||
return `${ fullPath }/${ folderName }.js`;
|
||||
}
|
||||
|
||||
return fullPath;
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directory exists and is not a file.
|
||||
*
|
||||
* @param { string } dir A directory path to test.
|
||||
* @return { boolean } True if this path exists and is a directory.
|
||||
*/
|
||||
function isDirectory( dir ) {
|
||||
if ( ! fs.existsSync( dir ) ) {
|
||||
return false;
|
||||
}
|
||||
const stats = fs.statSync( dir );
|
||||
return stats && stats.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file exists and is not a directory.
|
||||
*
|
||||
* @param { string } file A file path to test.
|
||||
* @return { boolean } True if this path exists and is a file.
|
||||
*/
|
||||
function isFile( file ) {
|
||||
if ( ! fs.existsSync( file ) ) {
|
||||
return false;
|
||||
}
|
||||
const stats = fs.statSync( file );
|
||||
return stats && stats.isFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a table of contents given a list of markdown files.
|
||||
*
|
||||
* @param { array } files A list of readme files.
|
||||
*/
|
||||
function writeTableOfContents( files ) {
|
||||
const mdFiles = files.map( f => getMdFileName( f, false ) );
|
||||
const TOC = uniq( mdFiles ).map( doc => {
|
||||
const name = camelCaseDash( doc.replace( '.md', '' ) );
|
||||
return ` * [${ name }](components/${ doc })`;
|
||||
} ).join( '\n' );
|
||||
|
||||
const TocFile = path.resolve( DOCS_FOLDER, '../_sidebar.md' );
|
||||
fs.writeFileSync( TocFile, '* Home\n\n * [Overview](/)\n\n* Components\n\n' + TOC );
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
COMPONENTS_FOLDER,
|
||||
DOCS_FOLDER,
|
||||
deleteExistingDocs,
|
||||
getExportedFileList,
|
||||
getMdFileName,
|
||||
getRealFilePaths,
|
||||
writeTableOfContents,
|
||||
};
|
|
@ -0,0 +1,169 @@
|
|||
/** @format */
|
||||
/**
|
||||
* External dependencies
|
||||
*/
|
||||
const { map } = require( 'lodash' );
|
||||
|
||||
/**
|
||||
* Given a string, returns a new string with dash separators converted to
|
||||
* camel-case equivalent. This is not as aggressive as `_.camelCase` in
|
||||
* converting to uppercase, where Lodash will convert letters following
|
||||
* numbers.
|
||||
*
|
||||
* @param {string} string Input dash-delimited string.
|
||||
*
|
||||
* @return {string} Camel-cased string.
|
||||
*/
|
||||
function camelCaseDash( string ) {
|
||||
return string
|
||||
.replace( /^([a-z])/g, ( match, letter ) => letter.toUpperCase() )
|
||||
.replace( /-([a-z])/g, ( match, letter ) => letter.toUpperCase() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted description string.
|
||||
*
|
||||
* @param { string } description Component description as retrieved from component docs.
|
||||
* @return { string } Formatted string.
|
||||
*/
|
||||
function getDescription( description = '' ) {
|
||||
// eslint requires valid jsdoc, but we can remove this because it's implicit.
|
||||
return description.replace( '@return { object } -', '' ) + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single prop's details formatted for markdown.
|
||||
*
|
||||
* @param { string } propName Prop name.
|
||||
* @param { object } prop Prop details as retrieved from component docs.
|
||||
* @return { string } Formatted string.
|
||||
*/
|
||||
function getProp( propName, prop ) {
|
||||
const lines = [ '### `' + propName + '`\n' ];
|
||||
prop.required && lines.push( '- **Required**' );
|
||||
lines.push( '- Type: ' + getPropType( prop.type, propName ) );
|
||||
lines.push( '- Default: ' + getPropDefaultValue( prop.defaultValue ) );
|
||||
lines.push( '\n' );
|
||||
lines.push( prop.description );
|
||||
lines.push( '\n' );
|
||||
|
||||
return lines.filter( Boolean ).join( '\n' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single prop's default value.
|
||||
*
|
||||
* @param { object } value Default value as retrieved from component docs.
|
||||
* @return { string } Formatted string.
|
||||
*/
|
||||
function getPropDefaultValue( value ) {
|
||||
if ( value && value.value ) {
|
||||
return '`' + value.value + '`';
|
||||
}
|
||||
return 'null';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get props and prop details formatted for markdown.
|
||||
*
|
||||
* @param { object } props Component props as retrieved from component docs.
|
||||
* @return { string } Formatted string.
|
||||
*/
|
||||
function getProps( props = {} ) {
|
||||
const title = 'Props';
|
||||
const lines = [ title, stringOfLength( '-', title.length ), '' ];
|
||||
Object.keys( props ).map( key => {
|
||||
lines.push( getProp( key, props[ key ] ) );
|
||||
} );
|
||||
|
||||
return lines.join( '\n' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single prop's type.
|
||||
*
|
||||
* @param { object } type Prop type as retrieved from component docs.
|
||||
* @return { string } Formatted string.
|
||||
*/
|
||||
function getPropType( type ) {
|
||||
if ( ! type ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = {
|
||||
func: 'Function',
|
||||
array: 'Array',
|
||||
object: 'Object',
|
||||
string: 'String',
|
||||
number: 'Number',
|
||||
bool: 'Boolean',
|
||||
node: 'ReactNode',
|
||||
element: 'ReactElement',
|
||||
any: '*',
|
||||
custom: '(custom validator)',
|
||||
};
|
||||
|
||||
let value = '';
|
||||
switch ( type.name ) {
|
||||
case 'arrayOf':
|
||||
// replacing "Object" is a hack for shape proptypes.
|
||||
value = 'Array \n' + getPropType( type.value ).replace( 'Object \n', '' );
|
||||
break;
|
||||
case 'objectOf':
|
||||
// replacing "Object" is a hack for shape proptypes.
|
||||
value = 'Object \n' + getPropType( type.value ).replace( 'Object \n', '' );
|
||||
break;
|
||||
case 'shape':
|
||||
value = map( type.value, ( v, key ) => `\n - ${ key }: ` + getPropType( v ) ).join( '' );
|
||||
value = 'Object \n' + value.replace( /^\n/, '' );
|
||||
break;
|
||||
case 'enum':
|
||||
value = 'One of: ' + type.value.map( v => v.value ).join( ', ' );
|
||||
break;
|
||||
case 'union':
|
||||
value = 'One of type: ' + type.value.map( v => v.name ).join( ', ' );
|
||||
break;
|
||||
default:
|
||||
value = labels[ type.name ] || type.name;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a formatted title string.
|
||||
*
|
||||
* @param { string } name Component title as retrieved from component docs.
|
||||
* @return { string } Formatted string.
|
||||
*/
|
||||
function getTitle( name ) {
|
||||
const title = '`' + name + '` (component)';
|
||||
return title + '\n' + stringOfLength( '=', title.length ) + '\n';
|
||||
}
|
||||
|
||||
/**
|
||||
* Repeat a string n times. If the string is 1 character long,
|
||||
* this will return a string of length n.
|
||||
*
|
||||
* @param { string } string String to repeat.
|
||||
* @param { number } n Number to repeat the string.
|
||||
* @return { string } New string.
|
||||
*/
|
||||
function stringOfLength( string, n ) {
|
||||
let newString = '';
|
||||
for ( let i = 0; i < n; i++ ) {
|
||||
newString += string;
|
||||
}
|
||||
return newString;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
camelCaseDash,
|
||||
getDescription,
|
||||
getProp,
|
||||
getPropDefaultValue,
|
||||
getProps,
|
||||
getPropType,
|
||||
getTitle,
|
||||
stringOfLength,
|
||||
};
|
|
@ -12,6 +12,10 @@ import PropTypes from 'prop-types';
|
|||
import { CSSTransition, TransitionGroup } from 'react-transition-group';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* This component creates slideable content controlled by an animate prop to direct the contents to slide left or right.
|
||||
* All other props are passed to `CSSTransition`. More info at http://reactcommunity.org/react-transition-group/css-transition
|
||||
*/
|
||||
class AnimationSlider extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
@ -62,9 +66,21 @@ class AnimationSlider extends Component {
|
|||
}
|
||||
|
||||
AnimationSlider.propTypes = {
|
||||
/**
|
||||
* A function returning rendered content with argument status, reflecting `CSSTransition` status.
|
||||
*/
|
||||
children: PropTypes.func.isRequired,
|
||||
/**
|
||||
* A unique identifier for each slideable page.
|
||||
*/
|
||||
animationKey: PropTypes.any.isRequired,
|
||||
/**
|
||||
* null, 'left', 'right', to designate which direction to slide on a change.
|
||||
*/
|
||||
animate: PropTypes.oneOf( [ null, 'left', 'right' ] ),
|
||||
/**
|
||||
* When set to true, the first focusable element will be focused after an animation has finished.
|
||||
*/
|
||||
focusOnChange: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ import phrases from './phrases';
|
|||
import { validateDateInputForRange } from 'lib/date';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* This is wrapper for a [react-dates](https://github.com/airbnb/react-dates) powered calendar.
|
||||
*/
|
||||
class DateRange extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -170,18 +173,49 @@ class DateRange extends Component {
|
|||
}
|
||||
|
||||
DateRange.propTypes = {
|
||||
/**
|
||||
* A moment date object representing the selected start. `null` for no selection.
|
||||
*/
|
||||
after: PropTypes.object,
|
||||
/**
|
||||
* A string error message, shown to the user.
|
||||
*/
|
||||
afterError: PropTypes.string,
|
||||
/**
|
||||
* The start date in human-readable format. Displayed in the text input.
|
||||
*/
|
||||
afterText: PropTypes.string,
|
||||
/**
|
||||
* A moment date object representing the selected end. `null` for no selection.
|
||||
*/
|
||||
before: PropTypes.object,
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
/**
|
||||
* A string error message, shown to the user.
|
||||
*/
|
||||
beforeError: PropTypes.string,
|
||||
/**
|
||||
* The end date in human-readable format. Displayed in the text input.
|
||||
*/
|
||||
beforeText: PropTypes.string,
|
||||
/**
|
||||
* String identifying which is the currently focused input (start or end).
|
||||
*/
|
||||
focusedInput: PropTypes.string,
|
||||
/**
|
||||
* Optionally invalidate certain days. `past`, `future`, `none`, or function are accepted.
|
||||
* A function will be passed to react-dates' `isOutsideRange` prop
|
||||
*/
|
||||
invalidDays: PropTypes.oneOfType( [
|
||||
PropTypes.oneOf( [ 'past', 'future', 'none' ] ),
|
||||
PropTypes.func,
|
||||
] ),
|
||||
focusedInput: PropTypes.string,
|
||||
afterText: PropTypes.string,
|
||||
beforeText: PropTypes.string,
|
||||
afterError: PropTypes.string,
|
||||
beforeError: PropTypes.string,
|
||||
/**
|
||||
* A function called upon selection of a date.
|
||||
*/
|
||||
onUpdate: PropTypes.func.isRequired,
|
||||
/**
|
||||
* The date format in moment.js-style tokens.
|
||||
*/
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,9 @@ import EllipsisMenu from 'components/ellipsis-menu';
|
|||
import { H, Section } from 'layout/section';
|
||||
import { validateComponent } from 'lib/proptype-validator';
|
||||
|
||||
/**
|
||||
* A basic card component with a header. The header can contain a title, an action, and an `EllipsisMenu` menu.
|
||||
*/
|
||||
class Card extends Component {
|
||||
render() {
|
||||
const { action, children, menu, title } = this.props;
|
||||
|
@ -35,9 +38,21 @@ class Card extends Component {
|
|||
}
|
||||
|
||||
Card.propTypes = {
|
||||
/**
|
||||
* One "primary" action for this card, appears in the card header.
|
||||
*/
|
||||
action: PropTypes.node,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* An `EllipsisMenu`, with filters used to control the content visible in this card
|
||||
*/
|
||||
menu: validateComponent( EllipsisMenu ),
|
||||
/**
|
||||
* The title to use for this card.
|
||||
*/
|
||||
title: PropTypes.oneOfType( [ PropTypes.string, PropTypes.node ] ).isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -34,10 +34,15 @@ import {
|
|||
getYTickOffset,
|
||||
} from './utils';
|
||||
|
||||
/**
|
||||
* A simple D3 line and bar chart component for timeseries data in React.
|
||||
*/
|
||||
class D3Chart extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
this.drawChart = this.drawChart.bind( this );
|
||||
this.getAllData = this.getAllData.bind( this );
|
||||
this.getParams = this.getParams.bind( this );
|
||||
this.state = {
|
||||
allData: this.getAllData( props ),
|
||||
width: props.width,
|
||||
|
@ -64,7 +69,7 @@ class D3Chart extends Component {
|
|||
return [ ...props.data, ...orderedKeys ];
|
||||
}
|
||||
|
||||
drawChart = ( node, params ) => {
|
||||
drawChart( node, params ) {
|
||||
const { data, margin, type } = this.props;
|
||||
const g = node
|
||||
.attr( 'id', 'chart' )
|
||||
|
@ -81,9 +86,9 @@ class D3Chart extends Component {
|
|||
type === 'bar' && drawBars( g, data, adjParams );
|
||||
|
||||
return node;
|
||||
};
|
||||
}
|
||||
|
||||
getParams = node => {
|
||||
getParams( node ) {
|
||||
const { colorScheme, data, height, margin, orderedKeys, type, xFormat, yFormat } = this.props;
|
||||
const { width } = this.state;
|
||||
const calculatedWidth = width || node.offsetWidth;
|
||||
|
@ -121,7 +126,7 @@ class D3Chart extends Component {
|
|||
yTickOffset: getYTickOffset( adjHeight, scale, yMax ),
|
||||
yFormat: d3Format( yFormat ),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
if ( ! this.props.data ) {
|
||||
|
@ -146,21 +151,54 @@ class D3Chart extends Component {
|
|||
}
|
||||
|
||||
D3Chart.propTypes = {
|
||||
colorScheme: PropTypes.func,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* A chromatic color function to be passed down to d3.
|
||||
*/
|
||||
colorScheme: PropTypes.func,
|
||||
/**
|
||||
* An array of data.
|
||||
*/
|
||||
data: PropTypes.array.isRequired,
|
||||
/**
|
||||
* Relative viewpoirt height of the `svg`.
|
||||
*/
|
||||
height: PropTypes.number,
|
||||
/**
|
||||
* @todo Remove – not used?
|
||||
*/
|
||||
legend: PropTypes.array,
|
||||
/**
|
||||
* Margins for axis and chart padding.
|
||||
*/
|
||||
margin: PropTypes.shape( {
|
||||
bottom: PropTypes.number,
|
||||
left: PropTypes.number,
|
||||
right: PropTypes.number,
|
||||
top: PropTypes.number,
|
||||
} ),
|
||||
/**
|
||||
* The list of labels for this chart.
|
||||
*/
|
||||
orderedKeys: PropTypes.array,
|
||||
/**
|
||||
* Chart type of either `line` or `bar`.
|
||||
*/
|
||||
type: PropTypes.oneOf( [ 'bar', 'line' ] ),
|
||||
/**
|
||||
* Relative viewport width of the `svg`.
|
||||
*/
|
||||
width: PropTypes.number,
|
||||
/**
|
||||
* A datetime formatting string, passed to d3TimeFormat.
|
||||
*/
|
||||
xFormat: PropTypes.string,
|
||||
/**
|
||||
* A number formatting string, passed to d3Format.
|
||||
*/
|
||||
yFormat: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -35,6 +35,9 @@ function getOrderedKeys( data ) {
|
|||
.sort( ( a, b ) => b.total - a.total );
|
||||
}
|
||||
|
||||
/**
|
||||
* A chart container using d3, to display timeseries data with an interactive legend.
|
||||
*/
|
||||
class Chart extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -166,7 +169,13 @@ class Chart extends Component {
|
|||
}
|
||||
|
||||
Chart.propTypes = {
|
||||
/**
|
||||
* An array of data.
|
||||
*/
|
||||
data: PropTypes.array.isRequired,
|
||||
/**
|
||||
* A title describing this chart.
|
||||
*/
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,9 @@ import './style.scss';
|
|||
import { formatCurrency } from 'lib/currency';
|
||||
import { getColor } from './utils';
|
||||
|
||||
/**
|
||||
* A legend specifically designed for the WooCommerce admin charts.
|
||||
*/
|
||||
class Legend extends Component {
|
||||
render() {
|
||||
const {
|
||||
|
@ -69,11 +72,29 @@ class Legend extends Component {
|
|||
}
|
||||
|
||||
Legend.propTypes = {
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* A chromatic color function to be passed down to d3.
|
||||
*/
|
||||
colorScheme: PropTypes.func,
|
||||
/**
|
||||
* An array of `orderedKeys`.
|
||||
*/
|
||||
data: PropTypes.array.isRequired,
|
||||
/**
|
||||
* Handles `onClick` event.
|
||||
*/
|
||||
handleLegendToggle: PropTypes.func,
|
||||
/**
|
||||
* Handles `onMouseEnter`/`onMouseLeave` events.
|
||||
*/
|
||||
handleLegendHover: PropTypes.func,
|
||||
/**
|
||||
* Display legend items as a `row` or `column` inside a flex-box.
|
||||
*/
|
||||
legendDirection: PropTypes.oneOf( [ 'row', 'column' ] ),
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,11 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Display a number with a styled border.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const Count = ( { count, label } ) => {
|
||||
if ( ! label ) {
|
||||
label = sprintf( __( 'Total %d', 'wc-admin' ), count );
|
||||
|
@ -22,7 +27,13 @@ const Count = ( { count, label } ) => {
|
|||
};
|
||||
|
||||
Count.propTypes = {
|
||||
/**
|
||||
* Value of the number to be displayed.
|
||||
*/
|
||||
count: PropTypes.number.isRequired,
|
||||
/**
|
||||
* A translated label with the number in context, used for screen readers.
|
||||
*/
|
||||
label: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,13 @@ import classnames from 'classnames';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* A button useful for a launcher of a dropdown component. The button is 100% width of its container and displays
|
||||
* single or multiple lines rendered as `<span/>` elments.
|
||||
*
|
||||
* @param { object } props Props passed to component.
|
||||
* @return { object } -
|
||||
*/
|
||||
const DropdownButton = props => {
|
||||
const { labels, isOpen, ...otherProps } = props;
|
||||
const buttonClasses = classnames( 'woocommerce-dropdown-button', {
|
||||
|
@ -27,7 +34,13 @@ const DropdownButton = props => {
|
|||
};
|
||||
|
||||
DropdownButton.propTypes = {
|
||||
/**
|
||||
* An array of elements to be rendered as the content of the button.
|
||||
*/
|
||||
labels: PropTypes.array,
|
||||
/**
|
||||
* Boolean describing if the dropdown in open or not.
|
||||
*/
|
||||
isOpen: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* This is a dropdown menu hidden behind a vertical ellipsis icon. When clicked, the inner MenuItems are displayed.
|
||||
*/
|
||||
class EllipsisMenu extends Component {
|
||||
render() {
|
||||
const { children, label } = this.props;
|
||||
|
@ -53,7 +56,14 @@ class EllipsisMenu extends Component {
|
|||
}
|
||||
|
||||
EllipsisMenu.propTypes = {
|
||||
/**
|
||||
* The label shown when hovering/focusing on the icon button.
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* A list of `MenuTitle`/`MenuItem` components
|
||||
*/
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
export default EllipsisMenu;
|
||||
|
|
|
@ -6,6 +6,11 @@ import { Component } from '@wordpress/element';
|
|||
import { ENTER, SPACE } from '@wordpress/keycodes';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* `MenuItem` is used to give the item an accessible wrapper, with the `menuitem` role and added keyboard functionality (`onInvoke`).
|
||||
* `MenuItem`s can also be deemed "clickable", though this is disabled by default because generally the inner component handles
|
||||
* the click event.
|
||||
*/
|
||||
class MenuItem extends Component {
|
||||
constructor() {
|
||||
super( ...arguments );
|
||||
|
@ -46,8 +51,19 @@ class MenuItem extends Component {
|
|||
}
|
||||
|
||||
MenuItem.propTypes = {
|
||||
/**
|
||||
* A renderable component (or string) which will be displayed as the content of this item. Generally a `ToggleControl`.
|
||||
*/
|
||||
children: PropTypes.node,
|
||||
/**
|
||||
* Boolean to control whether the MenuItem should handle the click event. Defaults to false, assuming your child component
|
||||
* handles the click event.
|
||||
*/
|
||||
isClickable: PropTypes.bool,
|
||||
/**
|
||||
* A function called when this item is activated via keyboard ENTER or SPACE; or when the item is clicked
|
||||
* (only if `isClickable` is set).
|
||||
*/
|
||||
onInvoke: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -4,11 +4,20 @@
|
|||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* `MenuTitle` is another valid Menu child, but this does not have any accessibility attributes associated
|
||||
* (so this should not be used in place of the `EllipsisMenu` prop `label`).
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const MenuTitle = ( { children } ) => {
|
||||
return <div className="woocommerce-ellipsis-menu__title">{ children }</div>;
|
||||
};
|
||||
|
||||
MenuTitle.propTypes = {
|
||||
/**
|
||||
* A renderable component (or string) which will be displayed as the content of this item.
|
||||
*/
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,9 @@ const matches = [
|
|||
{ value: 'any', label: __( 'Any', 'wc-admin' ) },
|
||||
];
|
||||
|
||||
/**
|
||||
* Displays a configurable set of filters which can modify query parameters.
|
||||
*/
|
||||
class AdvancedFilters extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -231,9 +234,28 @@ class AdvancedFilters extends Component {
|
|||
}
|
||||
|
||||
AdvancedFilters.propTypes = {
|
||||
config: PropTypes.object.isRequired,
|
||||
/**
|
||||
* The configuration object required to render filters.
|
||||
*/
|
||||
config: PropTypes.objectOf(
|
||||
PropTypes.shape( {
|
||||
label: PropTypes.string,
|
||||
addLabel: PropTypes.string,
|
||||
rules: PropTypes.arrayOf( PropTypes.object ),
|
||||
input: PropTypes.object,
|
||||
} )
|
||||
).isRequired,
|
||||
/**
|
||||
* Name of this filter, used in translations.
|
||||
*/
|
||||
filterTitle: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The `path` parameter supplied by React-Router.
|
||||
*/
|
||||
path: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The query string represented in object form.
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -18,6 +18,9 @@ import './style.scss';
|
|||
|
||||
const shortDateFormat = __( 'MM/DD/YYYY', 'wc-admin' );
|
||||
|
||||
/**
|
||||
* Select a range of dates or single dates.
|
||||
*/
|
||||
class DatePicker extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -148,7 +151,13 @@ class DatePicker extends Component {
|
|||
}
|
||||
|
||||
DatePicker.propTypes = {
|
||||
/**
|
||||
* The `path` parameter supplied by React-Router.
|
||||
*/
|
||||
path: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The query string represented in object form.
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@ import './style.scss';
|
|||
|
||||
export const DEFAULT_FILTER = 'all';
|
||||
|
||||
/**
|
||||
* Modify a url query parameter via a dropdown selection of configurable options.
|
||||
* This component manipulates the `filter` query parameter.
|
||||
*/
|
||||
class FilterPicker extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -176,8 +180,41 @@ class FilterPicker extends Component {
|
|||
}
|
||||
|
||||
FilterPicker.propTypes = {
|
||||
filters: PropTypes.array.isRequired,
|
||||
/**
|
||||
* An array of filters and subFilters to construct the menu.
|
||||
*/
|
||||
filters: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
/**
|
||||
* A custom component used instead of a button, might have special handling for filtering. TBD, not yet implemented.
|
||||
*/
|
||||
component: PropTypes.string,
|
||||
/**
|
||||
* The label for this filter. Optional only for custom component filters.
|
||||
*/
|
||||
label: PropTypes.string,
|
||||
/**
|
||||
* An array representing the "path" to this filter, if nested.
|
||||
*/
|
||||
path: PropTypes.string,
|
||||
/**
|
||||
* An array of more filter objects that act as "children" to this item.
|
||||
* This set of filters is shown if the parent filter is clicked.
|
||||
*/
|
||||
subFilters: PropTypes.array,
|
||||
/**
|
||||
* The value for this filter, used to set the `filter` query param when clicked, if there are no `subFilters`.
|
||||
*/
|
||||
value: PropTypes.string.isRequired,
|
||||
} )
|
||||
).isRequired,
|
||||
/**
|
||||
* The `path` parameter supplied by React-Router.
|
||||
*/
|
||||
path: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The query string represented in object form.
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -18,6 +18,12 @@ import FilterPicker from './filter';
|
|||
import { H, Section } from 'layout/section';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Add a collection of report filters to a page. This uses `DatePicker` & `FilterPicker` for the "basic" filters, and `AdvancedFilters`
|
||||
* or a comparison card if "advanced" or "compare" are picked from `FilterPicker`.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const ReportFilters = ( { advancedConfig, filters, query, path } ) => {
|
||||
let advancedCard = false;
|
||||
switch ( query.filter ) {
|
||||
|
@ -70,9 +76,21 @@ const ReportFilters = ( { advancedConfig, filters, query, path } ) => {
|
|||
};
|
||||
|
||||
ReportFilters.propTypes = {
|
||||
/**
|
||||
* Config option passed through to `AdvancedFilters`
|
||||
*/
|
||||
advancedConfig: PropTypes.object,
|
||||
/**
|
||||
* Config option passed through to `FilterPicker` - if not used, `FilterPicker` is not displayed.
|
||||
*/
|
||||
filters: PropTypes.array,
|
||||
/**
|
||||
* The `path` parameter supplied by React-Router
|
||||
*/
|
||||
path: PropTypes.string.isRequired,
|
||||
/**
|
||||
* The query string represented in object form
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,11 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Use the `Flag` component to display a country's flag.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const Flag = ( { code, order, round, height, width, className } ) => {
|
||||
const classes = classnames( 'woocommerce-flag', className, {
|
||||
'is-round': round,
|
||||
|
@ -42,11 +47,29 @@ const Flag = ( { code, order, round, height, width, className } ) => {
|
|||
};
|
||||
|
||||
Flag.propTypes = {
|
||||
/**
|
||||
* Two letter, three letter or three digit country code.
|
||||
*/
|
||||
code: PropTypes.string,
|
||||
/**
|
||||
* An order can be passed instead of `code` and the code will automatically be pulled from the billing or shipping data.
|
||||
*/
|
||||
order: PropTypes.object,
|
||||
/**
|
||||
* True to display a rounded flag.
|
||||
*/
|
||||
round: PropTypes.bool,
|
||||
/**
|
||||
* Flag image height.
|
||||
*/
|
||||
height: PropTypes.number,
|
||||
/**
|
||||
* Flag image width.
|
||||
*/
|
||||
width: PropTypes.number,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -24,5 +24,4 @@ render: function() {
|
|||
* `size`: Default 60. The size of Gravatar to request.
|
||||
* `alt`: Text to display as the image alt attribute.
|
||||
* `title`: Text to use for the image's title
|
||||
* `fallback`: Default `mp`. Gravatar default fallback mode. See https://en.gravatar.com/site/implement/images/.
|
||||
* `className`: Additional CSS classes.
|
|
@ -14,6 +14,11 @@ import crypto from 'crypto';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Display a users Gravatar.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const Gravatar = ( { alt, title, size, user, className } ) => {
|
||||
const classes = classnames( 'woocommerce-gravatar', className, {
|
||||
'is-placeholder': ! user,
|
||||
|
@ -62,10 +67,25 @@ const Gravatar = ( { alt, title, size, user, className } ) => {
|
|||
};
|
||||
|
||||
Gravatar.propTypes = {
|
||||
/**
|
||||
* The address to hash for displaying a Gravatar. Can be an email address or WP-API user object.
|
||||
*/
|
||||
user: PropTypes.oneOfType( [ PropTypes.object, PropTypes.string ] ),
|
||||
/**
|
||||
* Text to display as the image alt attribute.
|
||||
*/
|
||||
alt: PropTypes.string,
|
||||
/**
|
||||
* Text to use for the image's title
|
||||
*/
|
||||
title: PropTypes.string,
|
||||
/**
|
||||
* Default 60. The size of Gravatar to request.
|
||||
*/
|
||||
size: PropTypes.number,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ export { default as SegmentedSelection } from './segmented-selection';
|
|||
export { default as SplitButton } from './split-button';
|
||||
export { default as SummaryList } from './summary';
|
||||
export { default as SummaryNumber } from './summary/item';
|
||||
export { default as TableCard } from './table';
|
||||
export { default as Table } from './table/table';
|
||||
export { default as TableCard } from './table';
|
||||
export { default as TablePlaceholder } from './table/placeholder';
|
||||
export { default as TableSummary } from './table/summary';
|
||||
export { default as Tag } from './tag';
|
||||
|
|
|
@ -11,6 +11,10 @@ import { Link as RouterLink } from 'react-router-dom';
|
|||
*/
|
||||
import { getAdminLink } from 'lib/nav-utils';
|
||||
|
||||
/**
|
||||
* Use `Link` to create a link to another resource. It accepts a type to automatically
|
||||
* create wp-admin links, wc-admin links, and external links.
|
||||
*/
|
||||
class Link extends Component {
|
||||
render() {
|
||||
const { children, href, type, ...props } = this.props;
|
||||
|
@ -40,7 +44,13 @@ class Link extends Component {
|
|||
}
|
||||
|
||||
Link.propTypes = {
|
||||
/**
|
||||
* The resource to link to.
|
||||
*/
|
||||
href: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Type of link. For wp-admin and wc-admin, the correct prefix is appended.
|
||||
*/
|
||||
type: PropTypes.oneOf( [ 'wp-admin', 'wc-admin', 'external' ] ).isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,11 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Use `OrderStatus` to display a badge with human-friendly text describing the current order status.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const OrderStatus = ( { order, className } ) => {
|
||||
const { status } = order;
|
||||
const { orderStatuses } = wcSettings;
|
||||
|
@ -28,7 +33,13 @@ const OrderStatus = ( { order, className } ) => {
|
|||
};
|
||||
|
||||
OrderStatus.propTypes = {
|
||||
/**
|
||||
* The order to display a status for.
|
||||
*/
|
||||
order: PropTypes.object.isRequired,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ import { isFinite, noop, uniqueId } from 'lodash';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Use `Pagination` to allow navigation between pages that represent a collection of items.
|
||||
* The component allows for selecting a new page and items per page options.
|
||||
*/
|
||||
class Pagination extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -182,11 +186,29 @@ class Pagination extends Component {
|
|||
}
|
||||
|
||||
Pagination.propTypes = {
|
||||
/**
|
||||
* The current page of the collection.
|
||||
*/
|
||||
page: PropTypes.number.isRequired,
|
||||
/**
|
||||
* A function to execute when the page is changed.
|
||||
*/
|
||||
onPageChange: PropTypes.func,
|
||||
/**
|
||||
* The amount of results that are being displayed per page.
|
||||
*/
|
||||
perPage: PropTypes.number.isRequired,
|
||||
/**
|
||||
* A function to execute when the per page option is changed.
|
||||
*/
|
||||
onPerPageChange: PropTypes.func,
|
||||
/**
|
||||
* The total number of results.
|
||||
*/
|
||||
total: PropTypes.number.isRequired,
|
||||
/**
|
||||
* Additional classNames.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,12 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Use `ProductImage` to display a product's featured image. If no image can be found, a placeholder matching the front-end image
|
||||
* placeholder will be displayed.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const ProductImage = ( { product, alt, width, height, className, ...props } ) => {
|
||||
// The first returned image from the API is the featured/product image.
|
||||
const productImage = product && product.images && product.images[ 0 ];
|
||||
|
@ -34,10 +40,26 @@ const ProductImage = ( { product, alt, width, height, className, ...props } ) =>
|
|||
};
|
||||
|
||||
ProductImage.propTypes = {
|
||||
/**
|
||||
* The width of image to display.
|
||||
*/
|
||||
width: PropTypes.number,
|
||||
/**
|
||||
* The height of image to display.
|
||||
*/
|
||||
height: PropTypes.number,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* Product object. The image to display will be pulled from `product.images`.
|
||||
* See https://woocommerce.github.io/woocommerce-rest-api-docs/#product-properties
|
||||
*/
|
||||
product: PropTypes.object,
|
||||
/**
|
||||
* Text to use as the image alt attribute.
|
||||
*/
|
||||
alt: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,10 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Use `Rating` to display a set of stars, filled, empty or half-filled, that represents a
|
||||
* rating in a scale between 0 and the prop `totalStars` (default 5).
|
||||
*/
|
||||
class Rating extends Component {
|
||||
stars() {
|
||||
const { size, totalStars } = this.props;
|
||||
|
@ -52,9 +56,21 @@ class Rating extends Component {
|
|||
}
|
||||
|
||||
Rating.propTypes = {
|
||||
/**
|
||||
* Number of stars that should be filled. You can pass a partial number of stars like `2.5`.
|
||||
*/
|
||||
rating: PropTypes.number,
|
||||
/**
|
||||
* The total number of stars the rating is out of.
|
||||
*/
|
||||
totalStars: PropTypes.number,
|
||||
/**
|
||||
* The size in pixels the stars should be rendered at.
|
||||
*/
|
||||
size: PropTypes.number,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -10,12 +10,21 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import Rating from './index';
|
||||
|
||||
/**
|
||||
* Display a set of stars representing the product's average rating.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const ProductRating = ( { product, ...props } ) => {
|
||||
const rating = ( product && product.average_rating ) || 0;
|
||||
return <Rating rating={ rating } { ...props } />;
|
||||
};
|
||||
|
||||
ProductRating.propTypes = {
|
||||
/**
|
||||
* A product object containing a `average_rating`.
|
||||
* See https://woocommerce.github.io/woocommerce-rest-api-docs/#products.
|
||||
*/
|
||||
product: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -10,12 +10,21 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import Rating from './index';
|
||||
|
||||
/**
|
||||
* Display a set of stars representing the review's rating.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const ReviewRating = ( { review, ...props } ) => {
|
||||
const rating = ( review && review.rating ) || 0;
|
||||
return <Rating rating={ rating } { ...props } />;
|
||||
};
|
||||
|
||||
ReviewRating.propTypes = {
|
||||
/**
|
||||
* A review object containing a `rating`.
|
||||
* See https://woocommerce.github.io/woocommerce-rest-api-docs/#retrieve-product-reviews.
|
||||
*/
|
||||
review: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@ import { product } from './autocompleters';
|
|||
import Tag from 'components/tag';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* A search box which autocompletes results while typing, allowing for the user to select an existing object
|
||||
* (product, order, customer, etc). Currently only products are supported.
|
||||
*/
|
||||
class Search extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -116,7 +120,13 @@ class Search extends Component {
|
|||
}
|
||||
|
||||
Search.propTypes = {
|
||||
/**
|
||||
* Function called when selected results change, passed result list.
|
||||
*/
|
||||
onChange: PropTypes.func,
|
||||
/**
|
||||
* The object type to be used in searching.
|
||||
*/
|
||||
type: PropTypes.oneOf( [ 'products', 'orders', 'customers' ] ).isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -12,6 +12,9 @@ import { partial, uniqueId } from 'lodash';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* Create a panel of styled selectable options rendering stylized checkboxes and labels
|
||||
*/
|
||||
class SegmentedSelection extends Component {
|
||||
render() {
|
||||
const { className, options, selected, onSelect, name, legend } = this.props;
|
||||
|
@ -49,16 +52,34 @@ class SegmentedSelection extends Component {
|
|||
}
|
||||
|
||||
SegmentedSelection.propTypes = {
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* An Array of options to render. The array needs to be composed of objects with properties `label` and `value`.
|
||||
*/
|
||||
options: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
value: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
} )
|
||||
).isRequired,
|
||||
/**
|
||||
* Value of selected item.
|
||||
*/
|
||||
selected: PropTypes.string,
|
||||
/**
|
||||
* Callback to be executed after selection
|
||||
*/
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
/**
|
||||
* This will be the key in the key and value arguments supplied to `onSelect`.
|
||||
*/
|
||||
name: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Create a legend visible to screen readers.
|
||||
*/
|
||||
legend: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -13,6 +13,11 @@ import { noop } from 'lodash';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* A component for displaying a button with a main action plus a secondary set of actions behind a menu toggle.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const SplitButton = ( {
|
||||
isPrimary,
|
||||
mainIcon,
|
||||
|
@ -100,18 +105,48 @@ const SplitButton = ( {
|
|||
};
|
||||
|
||||
SplitButton.propTypes = {
|
||||
/**
|
||||
* Whether the button is styled as a primary button.
|
||||
*/
|
||||
isPrimary: PropTypes.bool,
|
||||
/**
|
||||
* Icon for the main button.
|
||||
*/
|
||||
mainIcon: PropTypes.node,
|
||||
/**
|
||||
* Label for the main button.
|
||||
*/
|
||||
mainLabel: PropTypes.string,
|
||||
/**
|
||||
* Function to activate when the the main button is clicked.
|
||||
*/
|
||||
onClick: PropTypes.func,
|
||||
/**
|
||||
* Label to display for the menu of actions, used as a heading on the mobile popover and for accessible text.
|
||||
*/
|
||||
menuLabel: PropTypes.string,
|
||||
/**
|
||||
* An array of additional actions. Accepts additional icon, label, and onClick props.
|
||||
*/
|
||||
controls: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
icon: PropTypes.node,
|
||||
label: PropTypes.string,
|
||||
/**
|
||||
* Icon used in button, passed to `IconButton`. Can be either string (dashicon name) or Gridicon.
|
||||
*/
|
||||
icon: PropTypes.oneOfType( [ PropTypes.string, PropTypes.element ] ),
|
||||
/**
|
||||
* Label displayed for this button.
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Click handler for this button.
|
||||
*/
|
||||
onClick: PropTypes.func,
|
||||
} )
|
||||
).isRequired,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,12 @@ import { uniqueId } from 'lodash';
|
|||
import { isMobileViewport, isTabletViewport } from 'lib/ui';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* A container element for a list of SummaryNumbers. This component handles detecting & switching to
|
||||
* the mobile format on smaller screens.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const SummaryList = ( { children, label } ) => {
|
||||
if ( ! label ) {
|
||||
label = __( 'Performance Indicators', 'wc-admin' );
|
||||
|
@ -70,7 +76,13 @@ const SummaryList = ( { children, label } ) => {
|
|||
};
|
||||
|
||||
SummaryList.propTypes = {
|
||||
/**
|
||||
* A list of `<SummaryNumber />`s
|
||||
*/
|
||||
children: PropTypes.node.isRequired,
|
||||
/**
|
||||
* An optional label of this group, read to screen reader users. Defaults to "Performance Indicators".
|
||||
*/
|
||||
label: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,11 @@ import PropTypes from 'prop-types';
|
|||
*/
|
||||
import Link from 'components/link';
|
||||
|
||||
/**
|
||||
* A component to show a value, label, and an optional change percentage. Can also act as a link to a specific report focus.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const SummaryNumber = ( {
|
||||
delta,
|
||||
href,
|
||||
|
@ -95,15 +100,49 @@ const SummaryNumber = ( {
|
|||
};
|
||||
|
||||
SummaryNumber.propTypes = {
|
||||
/**
|
||||
* A number to represent the percentage change since the last comparison period - positive numbers will show
|
||||
* a green up arrow, negative numbers will show a red down arrow, and zero will show a flat right arrow.
|
||||
* If omitted, no change value will display.
|
||||
*/
|
||||
delta: PropTypes.number,
|
||||
/**
|
||||
* An internal link to the report focused on this number.
|
||||
*/
|
||||
href: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Boolean describing whether the menu list is open. Only applies in mobile view,
|
||||
* and only applies to the toggle-able item (first in the list).
|
||||
*/
|
||||
isOpen: PropTypes.bool,
|
||||
/**
|
||||
* A string description of this value, ex "Revenue", or "New Customers"
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* A function used to switch the given SummaryNumber to a button, and called on click.
|
||||
*/
|
||||
onToggle: PropTypes.func,
|
||||
/**
|
||||
* A string description of the previous value's timeframe, ex "Previous Year:".
|
||||
*/
|
||||
prevLabel: PropTypes.string,
|
||||
/**
|
||||
* A string or number value to display - a string is allowed so we can accept currency formatting.
|
||||
* If omitted, this section won't display.
|
||||
*/
|
||||
prevValue: PropTypes.oneOfType( [ PropTypes.number, PropTypes.string ] ),
|
||||
/**
|
||||
* A boolean used to indicate that a negative delta is "good", and should be styled like a positive (and vice-versa).
|
||||
*/
|
||||
reverseTrend: PropTypes.bool,
|
||||
/**
|
||||
* A boolean used to show a highlight style on this number.
|
||||
*/
|
||||
selected: PropTypes.bool,
|
||||
/**
|
||||
* A string or number value to display - a string is allowed so we can accept currency formatting.
|
||||
*/
|
||||
value: PropTypes.oneOfType( [ PropTypes.number, PropTypes.string ] ).isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,14 @@ import Pagination from 'components/pagination';
|
|||
import Table from './table';
|
||||
import TableSummary from './summary';
|
||||
|
||||
/**
|
||||
* This is an accessible, sortable, and scrollable table for displaying tabular data (like revenue and other analytics data).
|
||||
* It accepts `headers` for column headers, and `rows` for the table content.
|
||||
* `rowHeader` can be used to define the index of the row header (or false if no header).
|
||||
*
|
||||
* `TableCard` serves as Card wrapper & contains a card header, `<Table />`, `<TableSummary />`, and `<Pagination />`.
|
||||
* This includes filtering and comparison functionality for report pages.
|
||||
*/
|
||||
class TableCard extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -122,6 +130,9 @@ class TableCard extends Component {
|
|||
}
|
||||
|
||||
TableCard.propTypes = {
|
||||
/**
|
||||
* An array of column headers (see `Table` props).
|
||||
*/
|
||||
headers: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
defaultSort: PropTypes.bool,
|
||||
|
@ -131,10 +142,25 @@ TableCard.propTypes = {
|
|||
required: PropTypes.bool,
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* A function which returns a callback function to update the query string for a given `param`.
|
||||
*/
|
||||
onQueryChange: PropTypes.func,
|
||||
/**
|
||||
* A callback function which handles then "download" button press. Optional, if not used, the button won't appear.
|
||||
*/
|
||||
onClickDownload: PropTypes.func,
|
||||
/**
|
||||
* An object of the query parameters passed to the page, ex `{ page: 2, per_page: 5 }`.
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
/**
|
||||
* An array of arrays of display/value object pairs (see `Table` props).
|
||||
*/
|
||||
rowHeader: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
|
||||
/**
|
||||
* Which column should be the row header, defaults to the first item (`0`) (see `Table` props).
|
||||
*/
|
||||
rows: PropTypes.arrayOf(
|
||||
PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
|
@ -143,12 +169,19 @@ TableCard.propTypes = {
|
|||
} )
|
||||
)
|
||||
).isRequired,
|
||||
/**
|
||||
* An array of objects with `label` & `value` properties, which display in a line under the table.
|
||||
* Optional, can be left off to show no summary.
|
||||
*/
|
||||
summary: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
label: PropTypes.node,
|
||||
value: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number ] ),
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* The title used in the card header, also used as the caption for the content in this table.
|
||||
*/
|
||||
title: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,11 @@
|
|||
*/
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
/**
|
||||
* A component to display summarized table data - the list of data passed in on a single line.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const TableSummary = ( { data } ) => {
|
||||
return (
|
||||
<ul className="woocommerce-table__summary">
|
||||
|
@ -18,6 +23,9 @@ const TableSummary = ( { data } ) => {
|
|||
};
|
||||
|
||||
TableSummary.propTypes = {
|
||||
/**
|
||||
* An array of objects with `label` & `value` properties, which display on a single line.
|
||||
*/
|
||||
data: PropTypes.array,
|
||||
};
|
||||
|
||||
|
|
|
@ -15,6 +15,9 @@ const DESC = 'desc';
|
|||
|
||||
const getDisplay = cell => cell.display || null;
|
||||
|
||||
/**
|
||||
* A table component, without the Card wrapper. This is a basic table display, sortable, but no default filtering.
|
||||
*/
|
||||
class Table extends Component {
|
||||
constructor( props ) {
|
||||
super( props );
|
||||
|
@ -149,28 +152,79 @@ class Table extends Component {
|
|||
}
|
||||
|
||||
Table.propTypes = {
|
||||
/**
|
||||
* Controls whether this component is hidden from screen readers. Used by the loading state, before there is data to read.
|
||||
* Don't use this on real tables unless the table data is loaded elsewhere on the page.
|
||||
*/
|
||||
ariaHidden: PropTypes.bool,
|
||||
/**
|
||||
* A label for the content in this table
|
||||
*/
|
||||
caption: PropTypes.string.isRequired,
|
||||
/**
|
||||
* Additional CSS classes.
|
||||
*/
|
||||
className: PropTypes.string,
|
||||
/**
|
||||
* An array of column headers, as objects.
|
||||
*/
|
||||
headers: PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
/**
|
||||
* Boolean, true if this column is the default for sorting. Only one column should have this set.
|
||||
*/
|
||||
defaultSort: PropTypes.bool,
|
||||
/**
|
||||
* Boolean, true if this column is a number value.
|
||||
*/
|
||||
isNumeric: PropTypes.bool,
|
||||
/**
|
||||
* Boolean, true if this column is sortable.
|
||||
*/
|
||||
isSortable: PropTypes.bool,
|
||||
/**
|
||||
* The API parameter name for this column, passed to `orderby` when sorting via API.
|
||||
*/
|
||||
key: PropTypes.string,
|
||||
/**
|
||||
* The display label for this column.
|
||||
*/
|
||||
label: PropTypes.string,
|
||||
/**
|
||||
* Boolean, true if this column should always display in the table (not shown in toggle-able list).
|
||||
*/
|
||||
required: PropTypes.bool,
|
||||
} )
|
||||
),
|
||||
/**
|
||||
* A function called when sortable table headers are clicked, gets the `header.key` as argument.
|
||||
*/
|
||||
onSort: PropTypes.func,
|
||||
/**
|
||||
* The query string represented in object form
|
||||
*/
|
||||
query: PropTypes.object,
|
||||
/**
|
||||
* An array of arrays of display/value object pairs.
|
||||
*/
|
||||
rows: PropTypes.arrayOf(
|
||||
PropTypes.arrayOf(
|
||||
PropTypes.shape( {
|
||||
/**
|
||||
* Display value, used for rendering- strings or elements are best here.
|
||||
*/
|
||||
display: PropTypes.node,
|
||||
/**
|
||||
* "Real" value used for sorting, and should be a string or number. A column with `false` value will not be sortable.
|
||||
*/
|
||||
value: PropTypes.oneOfType( [ PropTypes.string, PropTypes.number, PropTypes.bool ] ),
|
||||
} )
|
||||
)
|
||||
).isRequired,
|
||||
/**
|
||||
* Which column should be the row header, defaults to the first item (`0`) (but could be set to `1`, if the first col
|
||||
* is checkboxes, for example). Set to false to disable row headers.
|
||||
*/
|
||||
rowHeader: PropTypes.oneOfType( [ PropTypes.number, PropTypes.bool ] ),
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,12 @@ import { withInstanceId } from '@wordpress/compose';
|
|||
*/
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* This component can be used to show an item styled as a "tag", optionally with an `X` + "remove".
|
||||
* Generally this is used in a collection of selected items, see the Search component.
|
||||
*
|
||||
* @return { object } -
|
||||
*/
|
||||
const Tag = ( { id, instanceId, label, remove, removeLabel, screenReaderLabel, className } ) => {
|
||||
screenReaderLabel = screenReaderLabel || label;
|
||||
const classes = classnames( 'woocommerce-tag', className, {
|
||||
|
@ -41,10 +47,25 @@ const Tag = ( { id, instanceId, label, remove, removeLabel, screenReaderLabel, c
|
|||
};
|
||||
|
||||
Tag.propTypes = {
|
||||
/**
|
||||
* The ID for this item, used in the remove function.
|
||||
*/
|
||||
id: PropTypes.number.isRequired,
|
||||
/**
|
||||
* The name for this item, displayed as the tag's text.
|
||||
*/
|
||||
label: PropTypes.string.isRequired,
|
||||
/**
|
||||
* A function called when the remove X is clicked. If not used, no X icon will display.
|
||||
*/
|
||||
remove: PropTypes.func,
|
||||
/**
|
||||
* The label for removing this item (shown when hovering on X, or read to screen reader users). Defaults to "Remove tag".
|
||||
*/
|
||||
removeLabel: PropTypes.string,
|
||||
/**
|
||||
* A more descriptive label for screen reader users. Defaults to the `name` prop.
|
||||
*/
|
||||
screenReaderLabel: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
|
@ -15570,6 +15570,69 @@
|
|||
"react-with-styles-interface-css": "^4.0.1"
|
||||
}
|
||||
},
|
||||
"react-docgen": {
|
||||
"version": "2.21.0",
|
||||
"resolved": "https://registry.npmjs.org/react-docgen/-/react-docgen-2.21.0.tgz",
|
||||
"integrity": "sha512-8xNPTrmvHLGNfqlsCYPdXmSkagP1njI5unP3t8WrjTJ4/5hHuP5nb3XH69CnF67HPV5zTkPoafcRBDGSQO6S6A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "^2.1.4",
|
||||
"babel-runtime": "^6.9.2",
|
||||
"babylon": "~5.8.3",
|
||||
"commander": "^2.9.0",
|
||||
"doctrine": "^2.0.0",
|
||||
"node-dir": "^0.1.10",
|
||||
"recast": "^0.12.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"ast-types": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.1.tgz",
|
||||
"integrity": "sha512-UY7+9DPzlJ9VM8eY0b2TUZcZvF+1pO0hzMtAyjBYKhOmnvRlqYNYnWdtsMj0V16CGaMlpL0G1jnLbLo4AyotuQ==",
|
||||
"dev": true
|
||||
},
|
||||
"babylon": {
|
||||
"version": "5.8.38",
|
||||
"resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.38.tgz",
|
||||
"integrity": "sha1-7JsSCxG/bM1Bc6GL8hfmC3mFn/0=",
|
||||
"dev": true
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
},
|
||||
"node-dir": {
|
||||
"version": "0.1.17",
|
||||
"resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz",
|
||||
"integrity": "sha1-X1Zl2TNRM1yqvvjxxVRRbPXx5OU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimatch": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"recast": {
|
||||
"version": "0.12.9",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.12.9.tgz",
|
||||
"integrity": "sha512-y7ANxCWmMW8xLOaiopiRDlyjQ9ajKRENBH+2wjntIbk3A6ZR1+BLQttkmSHMY7Arl+AAZFwJ10grg2T6f1WI8A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ast-types": "0.10.1",
|
||||
"core-js": "^2.4.1",
|
||||
"esprima": "~4.0.0",
|
||||
"private": "~0.1.5",
|
||||
"source-map": "~0.6.1"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "16.4.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.4.1.tgz",
|
||||
|
@ -15873,9 +15936,9 @@
|
|||
}
|
||||
},
|
||||
"recast": {
|
||||
"version": "0.15.2",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.15.2.tgz",
|
||||
"integrity": "sha512-L4f/GqxjlEJ5IZ+tdll/l+6dVi2ylysWbkgFJbMuldD6Jklgfv6zJnCpuAZDfjwHhfcd/De0dDKelsTEPQ29qA==",
|
||||
"version": "0.15.3",
|
||||
"resolved": "https://registry.npmjs.org/recast/-/recast-0.15.3.tgz",
|
||||
"integrity": "sha512-xqnagxQH7mL4+UpcCVMObPPdjCEE2dmfGcTwcdpyNgZOd9W0rfdLRF3+smoA+AQqMw6xK6G4021dAQK8XfPYIQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ast-types": "0.11.5",
|
||||
|
|
|
@ -44,6 +44,7 @@
|
|||
"@wordpress/keycodes": "^1.0.1",
|
||||
"@wordpress/postcss-themes": "^1.0.1",
|
||||
"@wordpress/scripts": "^2.0.2",
|
||||
"ast-types": "^0.11.5",
|
||||
"autoprefixer": "9.0.1",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^8.2.6",
|
||||
|
@ -74,10 +75,12 @@
|
|||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.3.2",
|
||||
"react-click-outside": "2.3.1",
|
||||
"react-docgen": "^2.21.0",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-router-dom": "^4.2.2",
|
||||
"react-world-flags": "^1.2.4",
|
||||
"readline-sync": "^1.4.9",
|
||||
"recast": "^0.15.3",
|
||||
"sass-loader": "^7.0.1",
|
||||
"style-loader": "^0.21.0",
|
||||
"webpack": "^4.8.3",
|
||||
|
|
Loading…
Reference in New Issue