diff --git a/src/components/ajaxloader/index.js b/src/components/ajaxloader/index.js
index cde24b0c..1ee9e0b9 100644
--- a/src/components/ajaxloader/index.js
+++ b/src/components/ajaxloader/index.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
const AjaxLoader = ({
show,
@@ -75,4 +76,17 @@ const AjaxLoader = ({
);
};
+AjaxLoader.propTypes = {
+ /** Toggles display; the overlay stays mounted either way. */
+ show: PropTypes.bool,
+ /** Positions absolute inside the nearest positioned ancestor instead of fixed to the viewport. */
+ relative: PropTypes.bool,
+ /** Background colour of the dimming layer behind the spinner. */
+ color: PropTypes.string,
+ /** Spinner font-size in px. */
+ size: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
+ /** Optional caption rendered under the spinner. */
+ children: PropTypes.node
+};
+
export default AjaxLoader;
diff --git a/src/components/bulk-actions-selector/index.js b/src/components/bulk-actions-selector/index.js
index ad670a76..3ff509f8 100644
--- a/src/components/bulk-actions-selector/index.js
+++ b/src/components/bulk-actions-selector/index.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import T from "i18n-react/dist/i18n-react";
import './styles.less';
class ScheduleAdminsBulkActionsSelector extends React.Component {
@@ -51,4 +52,18 @@ class ScheduleAdminsBulkActionsSelector extends React.Component {
}
}
+ScheduleAdminsBulkActionsSelector.propTypes = {
+ /** Renders nothing when false; the container element is always present. */
+ show: PropTypes.bool,
+ /** Actions offered alongside the built-in default option. */
+ bulkOptions: PropTypes.arrayOf(PropTypes.shape({
+ value: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired
+ })).isRequired,
+ /** Click handler for the select-all checkbox; the component tracks no selection itself. */
+ onSelectAll: PropTypes.func,
+ /** Receives the chosen action value on Go. Not called while the default option is selected. */
+ onSelectedBulkAction: PropTypes.func.isRequired
+};
+
export default ScheduleAdminsBulkActionsSelector;
diff --git a/src/components/clock.js b/src/components/clock.js
index e5fc4fa7..091f3009 100644
--- a/src/components/clock.js
+++ b/src/components/clock.js
@@ -11,6 +11,7 @@
* limitations under the License.
**/
import React from 'react';
+import PropTypes from 'prop-types';
import moment from "moment-timezone";
import FragmentParser from "./fragment-parser";
import {getTimeServiceUrl} from '../utils/methods';
@@ -155,4 +156,14 @@ class Clock extends React.Component {
}
+Clock.propTypes = {
+ /** Renders nothing until true and a timestamp has been resolved. */
+ display: PropTypes.bool,
+ /** IANA zone used to format the clock. */
+ timezone: PropTypes.string,
+ /** Called on each tick with the current epoch seconds. */
+ onTick: PropTypes.func,
+ /** Overrides the resolved time; otherwise the summit time service is queried. */
+ now: PropTypes.number
+};
export default Clock;
diff --git a/src/components/exclusive-wrapper.js b/src/components/exclusive-wrapper.js
index 5b3a4b7b..6d0b2469 100644
--- a/src/components/exclusive-wrapper.js
+++ b/src/components/exclusive-wrapper.js
@@ -12,6 +12,7 @@
**/
import React from 'react'
+import PropTypes from 'prop-types'
export default class Exclusive extends React.Component {
@@ -40,3 +41,9 @@ export default class Exclusive extends React.Component {
}
}
+
+Exclusive.propTypes = {
+ /** Children render only if window.EXCLUSIVE_SECTIONS includes this name. */
+ name: PropTypes.string.isRequired,
+ children: PropTypes.node
+};
diff --git a/src/components/forms/rsvp-form.js b/src/components/forms/rsvp-form.js
index 53d9d71f..cec65dd0 100644
--- a/src/components/forms/rsvp-form.js
+++ b/src/components/forms/rsvp-form.js
@@ -12,6 +12,7 @@
**/
import React from 'react'
+import PropTypes from 'prop-types';
import 'awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css'
import Input from '../inputs/text-input'
import Dropdown from '../inputs/dropdown'
@@ -164,4 +165,30 @@ class RsvpForm extends React.Component {
}
}
+RsvpForm.propTypes = {
+ /** Rendered by class_name, e.g. RSVPTextBoxQuestionTemplate or RSVPCheckBoxListQuestionTemplate. */
+ questions: PropTypes.arrayOf(PropTypes.shape({
+ /** Must be a number: handleChange compares answer.question_id to parseInt(id) with strict equality, which never matches a string id. */
+ id: PropTypes.number.isRequired,
+ /** Selects the widget; unknown values render nothing. */
+ class_name: PropTypes.string.isRequired,
+ name: PropTypes.string,
+ /** Injected as raw HTML. */
+ label: PropTypes.string,
+ is_mandatory: PropTypes.bool,
+ values: PropTypes.array,
+ /** RSVPLiteralContentQuestionTemplate: raw HTML content rendered via RawHTML. */
+ value: PropTypes.string,
+ /** RSVPDropDownQuestionTemplate: when true, values are remapped to {value: id, label: value}. */
+ is_country_selector: PropTypes.bool,
+ /** RSVPDropDownQuestionTemplate: passed through to the Dropdown as isMulti. */
+ is_multiselect: PropTypes.bool,
+ /** RSVPDropDownQuestionTemplate: used as the Dropdown's placeholder. */
+ empty_string: PropTypes.string
+ })).isRequired,
+ /** Receives the collected answers array on submit. Omit to render a display-only form with no submit button. */
+ onSubmit: PropTypes.func,
+ /** Keyed by question id. Read into state at mount with no fallback; hasErrors() throws if omitted. */
+ errors: PropTypes.object.isRequired
+};
export default RsvpForm;
diff --git a/src/components/forms/simple-form.js b/src/components/forms/simple-form.js
index 779bcbbf..f7e9bb3f 100644
--- a/src/components/forms/simple-form.js
+++ b/src/components/forms/simple-form.js
@@ -12,6 +12,7 @@
**/
import React from 'react'
+import PropTypes from 'prop-types';
import T from 'i18n-react/dist/i18n-react'
import 'awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css'
import Input from '../inputs/text-input'
@@ -147,4 +148,20 @@ class SimpleForm extends React.Component {
}
}
+SimpleForm.propTypes = {
+ /** Field descriptors rendered in order. */
+ fields: PropTypes.arrayOf(PropTypes.shape({
+ /** Matches a key on entity. */
+ name: PropTypes.string.isRequired,
+ /** Anything else renders nothing. */
+ type: PropTypes.oneOf(['text', 'textarea', 'checkbox']).isRequired,
+ label: PropTypes.node
+ })).isRequired,
+ /** Seeds the form. Copied into local state and re-synced when it changes. */
+ entity: PropTypes.object.isRequired,
+ /** Keyed by field name. */
+ errors: PropTypes.object,
+ /** Receives the edited entity. */
+ onSubmit: PropTypes.func.isRequired
+};
export default SimpleForm;
diff --git a/src/components/inputs/access-levels-input.js b/src/components/inputs/access-levels-input.js
index 8ae84b61..cc741f1e 100644
--- a/src/components/inputs/access-levels-input.js
+++ b/src/components/inputs/access-levels-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import {queryAccessLevels} from '../../utils/query-actions';
@@ -92,3 +93,23 @@ export default class AccessLevelsInput extends React.Component {
}
}
+AccessLevelsInput.propTypes = {
+ /** Selected access level(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Scopes the lookup. Required for results to return. */
+ summitId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ /** Shown before the user types. */
+ defaultOptions: PropTypes.oneOfType([PropTypes.bool, PropTypes.array]),
+ /** (item) => value. Defaults to item.id. */
+ getOptionValue: PropTypes.func,
+ /** (item) => label. */
+ getOptionLabel: PropTypes.func
+};
diff --git a/src/components/inputs/action-dropdown/index.js b/src/components/inputs/action-dropdown/index.js
index 848b6364..dec3662c 100644
--- a/src/components/inputs/action-dropdown/index.js
+++ b/src/components/inputs/action-dropdown/index.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import './action-dropdown.less';
import Select from 'react-select';
@@ -66,3 +67,19 @@ export default class ActionDropdown extends React.Component {
}
}
+
+ActionDropdown.propTypes = {
+ options: PropTypes.arrayOf(PropTypes.shape({
+ value: PropTypes.any.isRequired,
+ label: PropTypes.string.isRequired
+ })).isRequired,
+ /** Label on the trigger button next to the select. */
+ actionLabel: PropTypes.node,
+ placeholder: PropTypes.string,
+ /** Fires only on button click, with the selected option's value. Throws if nothing is selected. */
+ onClick: PropTypes.func.isRequired,
+ /** Seeds the initial selection only; later changes are held in local state. */
+ value: PropTypes.any,
+ /** Gated on the prop being present, so small={false} still applies the small styling. */
+ small: PropTypes.bool
+};
diff --git a/src/components/inputs/attendee-input.js b/src/components/inputs/attendee-input.js
index 67e2815a..0c16bec8 100644
--- a/src/components/inputs/attendee-input.js
+++ b/src/components/inputs/attendee-input.js
@@ -12,6 +12,7 @@
**/
import React, {useState} from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import {queryAttendees} from '../../utils/query-actions';
@@ -71,5 +72,25 @@ const AttendeeInput = ({id, value, summitId, error, multi, onChange, getOptionVa
);
}
+AttendeeInput.propTypes = {
+ /** Selected attendee(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** No effect on this component: it is destructured out of props so it never reaches ...rest, and isMulti is never computed or passed to AsyncSelect. Consumers still pass it; it does nothing here. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Scopes the lookup. Required for results to return. */
+ summitId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ /** Overrides the default queryAttendees lookup. */
+ queryFunction: PropTypes.func,
+ /** (attendee) => value. Defaults to attendee.id. */
+ getOptionValue: PropTypes.func,
+ /** (attendee) => label. */
+ getOptionLabel: PropTypes.func
+};
export default AttendeeInput;
diff --git a/src/components/inputs/company-input.js b/src/components/inputs/company-input.js
index c64766f7..d5b14a8d 100644
--- a/src/components/inputs/company-input.js
+++ b/src/components/inputs/company-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import {queryCompanies} from '../../utils/query-actions';
import AsyncCreatableSelect from "react-select/lib/AsyncCreatable";
@@ -115,3 +116,35 @@ export default class CompanyInput extends React.Component {
}
}
+
+CompanyInput.propTypes = {
+ /** Selected company or companies, read as value.id / value.name (or value.map(...) in multi mode). */
+ value: PropTypes.oneOfType([
+ PropTypes.shape({
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ name: PropTypes.string
+ }),
+ PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ name: PropTypes.string
+ }))
+ ]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Alias for multi; either being present enables multi-select. */
+ isMulti: PropTypes.bool,
+ /** Presence turns this into a creatable select. */
+ allowCreate: PropTypes.bool,
+ /** Called with the typed text when a new company is created. Required whenever allowCreate is present: AsyncCreatableSelect's create action calls it unconditionally. */
+ onCreate: PropTypes.func,
+ /** Overrides the default queryCompanies lookup. */
+ queryFunction: PropTypes.func,
+ /** Appended to the fetched option list. */
+ extraOptions: PropTypes.array
+};
diff --git a/src/components/inputs/country-dropdown.js b/src/components/inputs/country-dropdown.js
index bbab5f92..9e43c93b 100644
--- a/src/components/inputs/country-dropdown.js
+++ b/src/components/inputs/country-dropdown.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import Dropdown from './dropdown';
import {getCountryList} from '../../utils/query-actions';
@@ -67,3 +68,18 @@ export default class CountryDropdown extends React.Component {
}
}
+
+CountryDropdown.propTypes = {
+ /** Selected ISO country code. */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** No effect: forwarded via {...this.props} to Dropdown, which reads isMulti, not multi. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Forwarded via {...this.props} to Dropdown, then to react-select's Select as its placeholder text. */
+ placeholder: PropTypes.string
+};
diff --git a/src/components/inputs/country-input.js b/src/components/inputs/country-input.js
index e348f72e..3037bc43 100644
--- a/src/components/inputs/country-input.js
+++ b/src/components/inputs/country-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import Select from 'react-select';
import {getCountryList} from '../../utils/query-actions';
@@ -90,3 +91,16 @@ export default class CountryInput extends React.Component {
);
}
}
+
+CountryInput.propTypes = {
+ /** Selected ISO country code(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+};
diff --git a/src/components/inputs/datetimepicker/index.js b/src/components/inputs/datetimepicker/index.js
index 4ee0e6e4..5ab56c8c 100644
--- a/src/components/inputs/datetimepicker/index.js
+++ b/src/components/inputs/datetimepicker/index.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import './datetimepicker.less';
import Datetime from 'react-datetime';
import moment from 'moment-timezone';
@@ -113,3 +114,25 @@ export default class DateTimePicker extends React.Component {
);
}
}
+
+DateTimePicker.propTypes = {
+ id: PropTypes.string.isRequired,
+ /** A moment instance in the given timezone. */
+ value: PropTypes.object,
+ /** Receives a synthetic { target: { id, value, type } } carrying a moment. */
+ onChange: PropTypes.func.isRequired,
+ /** IANA zone; the displayed value is converted into it. */
+ timezone: PropTypes.string,
+ /** { date, time } moment format strings. Pass time: false for a date-only picker. Dereferenced with no default; required. */
+ format: PropTypes.shape({
+ date: PropTypes.string.isRequired,
+ time: PropTypes.oneOfType([PropTypes.string, PropTypes.bool])
+ }).isRequired,
+ /** Constrains selectable dates, e.g. { after, before }. */
+ validation: PropTypes.object,
+ /** Forwarded to the underlying input. */
+ inputProps: PropTypes.object,
+ disabled: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string
+};
diff --git a/src/components/inputs/dropdown.js b/src/components/inputs/dropdown.js
index 083d8880..a796dc5f 100644
--- a/src/components/inputs/dropdown.js
+++ b/src/components/inputs/dropdown.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import Select from 'react-select';
export default class Dropdown extends React.Component {
@@ -84,6 +85,32 @@ export default class Dropdown extends React.Component {
}
}
+Dropdown.propTypes = {
+ /** Echoed back as ev.target.id on change. */
+ id: PropTypes.string.isRequired,
+ options: PropTypes.arrayOf(PropTypes.shape({
+ value: PropTypes.any.isRequired,
+ /** Injected as raw HTML into the option label. */
+ label: PropTypes.string.isRequired
+ })).isRequired,
+ /** An array of option values when isMulti, otherwise a single value or option object. */
+ value: PropTypes.oneOfType([
+ PropTypes.array, PropTypes.object, PropTypes.string, PropTypes.number
+ ]),
+ /** Receives a synthetic { target: { id, value, type: 'dropdown' } }. */
+ onChange: PropTypes.func.isRequired,
+ isMulti: PropTypes.bool,
+ className: PropTypes.string,
+ /** Non-empty renders an .error-label and adds the error class. */
+ error: PropTypes.string,
+ ariaLabelledBy: PropTypes.string,
+ /** Set true to keep className as-is instead of prefixing 'dropdown'. */
+ overrideCSS: PropTypes.bool,
+ disabled: PropTypes.bool,
+ /** Gated on the prop being present, so clearable={false} still enables clearing. */
+ clearable: PropTypes.bool
+};
+
Dropdown.defaultProps = {
ariaLabelledBy : null,
}
diff --git a/src/components/inputs/editor-input/index.js b/src/components/inputs/editor-input/index.js
index 5eca9155..b3512f72 100644
--- a/src/components/inputs/editor-input/index.js
+++ b/src/components/inputs/editor-input/index.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import './editor-input.less';
@@ -128,3 +129,16 @@ export default class TextEditor extends React.Component {
}
}
+
+TextEditor.propTypes = {
+ id: PropTypes.string,
+ /** HTML string. Loaded into the editor and re-synced when it changes externally. */
+ value: PropTypes.string,
+ /** Receives a synthetic { target: { id, value, type } } carrying an HTML string. */
+ onChange: PropTypes.func.isRequired,
+ /** Shows a remaining-characters counter. */
+ maxLength: PropTypes.number,
+ className: PropTypes.string,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string
+};
diff --git a/src/components/inputs/event-input.js b/src/components/inputs/event-input.js
index acf070c3..f58aa712 100644
--- a/src/components/inputs/event-input.js
+++ b/src/components/inputs/event-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import {queryEvents} from '../../utils/query-actions';
@@ -64,3 +65,19 @@ export default class EventInput extends React.Component {
}
}
+EventInput.propTypes = {
+ /** Selected event(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Not read by this component; not destructured, so it flows through ...rest into AsyncSelect, which does not render it. */
+ error: PropTypes.string,
+ /** Scopes the lookup; summit.id is what is actually read. */
+ summit: PropTypes.shape({ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]) }).isRequired,
+ /** Restricts results to published events. */
+ onlyPublished: PropTypes.bool
+};
diff --git a/src/components/inputs/free-multi-text-input.js b/src/components/inputs/free-multi-text-input.js
index 4a779d1f..1f0f98ef 100644
--- a/src/components/inputs/free-multi-text-input.js
+++ b/src/components/inputs/free-multi-text-input.js
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import CreatableSelect from 'react-select/lib/Creatable';
import T from 'i18n-react/dist/i18n-react';
@@ -76,3 +77,13 @@ export default class FreeMultiTextInput extends React.Component {
);
}
}
+
+FreeMultiTextInput.propTypes = {
+ id: PropTypes.string.isRequired,
+ /** Current tags as react-select options. Destructured with no default and spread on Enter/Tab; required. */
+ value: PropTypes.array.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Maximum number of entries; further input is rejected once reached. */
+ limit: PropTypes.number
+};
diff --git a/src/components/inputs/group-input.js b/src/components/inputs/group-input.js
index d153d9d4..6efa0493 100644
--- a/src/components/inputs/group-input.js
+++ b/src/components/inputs/group-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import {queryGroups} from '../../utils/query-actions';
@@ -62,3 +63,15 @@ export default class GroupInput extends React.Component {
}
}
+GroupInput.propTypes = {
+ /** Selected group(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Not read by this component; not destructured, so it flows through ...rest into AsyncSelect, which does not render it. */
+ error: PropTypes.string,
+};
diff --git a/src/components/inputs/grouped-dropdown/index.js b/src/components/inputs/grouped-dropdown/index.js
index 63fd9e9f..d27ca2d6 100644
--- a/src/components/inputs/grouped-dropdown/index.js
+++ b/src/components/inputs/grouped-dropdown/index.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import { OptionGroup } from './OptionGroup';
import './optiongroup.less';
@@ -70,4 +71,23 @@ export default class GroupedDropdown extends React.Component {
);
}
-}
\ No newline at end of file
+}
+
+GroupedDropdown.propTypes = {
+ id: PropTypes.string,
+ /** An entry with a nested `options` array renders as an optgroup; otherwise a plain option. */
+ options: PropTypes.arrayOf(PropTypes.shape({
+ value: PropTypes.any,
+ label: PropTypes.string,
+ options: PropTypes.array
+ })).isRequired,
+ /** Native select value. Mirrored into local state and re-synced when the prop changes. */
+ value: PropTypes.any,
+ /** Receives the raw DOM change event. */
+ onChange: PropTypes.func.isRequired,
+ /** Rendered as a disabled first option. */
+ placeholder: PropTypes.string,
+ className: PropTypes.string,
+ /** Non-empty renders an .error-label and adds the error class. */
+ error: PropTypes.string
+};
\ No newline at end of file
diff --git a/src/components/inputs/language-input.js b/src/components/inputs/language-input.js
index 368f1dac..6dc6ae27 100644
--- a/src/components/inputs/language-input.js
+++ b/src/components/inputs/language-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import Select from 'react-select';
import {getLanguageList} from '../../utils/query-actions';
@@ -92,3 +93,18 @@ export default class LanguageInput extends React.Component {
}
}
+
+LanguageInput.propTypes = {
+ /** Selected language(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Not read by this component; not destructured, so it flows through ...rest into Select, which does not render it. */
+ error: PropTypes.string,
+ /** Presence switches option values from ISO code to numeric id. */
+ shouldUseId: PropTypes.bool
+};
diff --git a/src/components/inputs/member-input.js b/src/components/inputs/member-input.js
index d0b9d04f..1b39f953 100644
--- a/src/components/inputs/member-input.js
+++ b/src/components/inputs/member-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import {queryMembers} from '../../utils/query-actions';
@@ -89,3 +90,19 @@ export default class MemberInput extends React.Component {
}
}
+MemberInput.propTypes = {
+ /** Selected member(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** (member) => value. Defaults to member.id. */
+ getOptionValue: PropTypes.func,
+ /** (member) => label. Defaults to the member's name and id: `${first_name} ${last_name} (${id})`. */
+ getOptionLabel: PropTypes.func
+};
diff --git a/src/components/inputs/operator-input.js b/src/components/inputs/operator-input.js
index 45841e5b..a14f1224 100644
--- a/src/components/inputs/operator-input.js
+++ b/src/components/inputs/operator-input.js
@@ -12,6 +12,7 @@
**/
import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
import Select from 'react-select';
const OperatorInput = ({ error, label, value, onChange, id, multi, isMulti, className, isDisabled, isClearable, options, selectStyles, customStyle, ...rest }) => {
@@ -138,4 +139,31 @@ OperatorInput.defaultProps = {
{ value: '==', label: 'Equal' },
{ value: 'between', label: 'Between' },
],
-};
\ No newline at end of file
+};
+
+OperatorInput.propTypes = {
+ /** Either a scalar like '>10', or a two-element array for the 'between' operator. */
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.array]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** No effect on this component: destructured out of props alongside isMulti, so neither is passed to the underlying Select. Consumers still pass it; it does nothing here. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Operator choices; 'between' switches the control to two inputs. Has a defaultProps fallback above. */
+ options: PropTypes.arrayOf(PropTypes.shape({
+ value: PropTypes.string,
+ label: PropTypes.string
+ })),
+ label: PropTypes.node,
+ className: PropTypes.string,
+ /** No effect on this component: destructured out of props alongside multi, so neither is passed to the underlying Select. Consumers still pass it; it does nothing here. */
+ isMulti: PropTypes.bool,
+ isDisabled: PropTypes.bool,
+ isClearable: PropTypes.bool,
+ /** Merged into the operator select's react-select styles. */
+ selectStyles: PropTypes.object,
+ customStyle: PropTypes.object
+};
diff --git a/src/components/inputs/organization-input.js b/src/components/inputs/organization-input.js
index 3a4269ea..00600a4e 100644
--- a/src/components/inputs/organization-input.js
+++ b/src/components/inputs/organization-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import AsyncCreatableSelect from 'react-select/lib/AsyncCreatable';
import {queryOrganizations} from '../../utils/query-actions';
@@ -97,5 +98,22 @@ export default class OrganizationInput extends React.Component {
}
}
-
-
+OrganizationInput.propTypes = {
+ /** Selected organization, read as value.id / value.name. */
+ value: PropTypes.shape({
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ name: PropTypes.string
+ }),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** No effect on this component: it flows through ...rest into AsyncSelect as multi, but react-select ^2.4.3 renamed that prop to isMulti. Consumers still pass it; it does nothing here. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Presence-gated: switches to AsyncCreatableSelect regardless of value. */
+ allowCreate: PropTypes.bool,
+ /** Called with the typed text when a new organization is created. Required whenever allowCreate is present: AsyncCreatableSelect's create action calls it unconditionally. */
+ onCreate: PropTypes.func
+};
diff --git a/src/components/inputs/speaker-input.js b/src/components/inputs/speaker-input.js
index 39480087..3c9a33bf 100644
--- a/src/components/inputs/speaker-input.js
+++ b/src/components/inputs/speaker-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import { components } from 'react-select/lib/components'
import { querySpeakers } from '../../utils/query-actions';
@@ -104,3 +105,23 @@ export default class SpeakerInput extends React.Component {
}
}
+SpeakerInput.propTypes = {
+ /** Selected speaker(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Scopes the lookup to a summit; omit to search all speakers. */
+ summitId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ /** Router history, used to link out to a speaker. Required whenever multi is present: the multi-value label's click handler calls history.push with no guard. */
+ history: PropTypes.object,
+ /** (speaker) => value. Defaults to speaker.id. */
+ getOptionValue: PropTypes.func,
+ /** (speaker) => label. */
+ getOptionLabel: PropTypes.func
+};
diff --git a/src/components/inputs/sponsor-input.js b/src/components/inputs/sponsor-input.js
index f7454619..a3a1def1 100644
--- a/src/components/inputs/sponsor-input.js
+++ b/src/components/inputs/sponsor-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import { querySponsors } from '../../utils/query-actions';
@@ -58,4 +59,20 @@ const SponsorInput = ({ id, summitId, value, error, multi, onChange, queryFuncti
);
}
+SponsorInput.propTypes = {
+ /** Selected sponsor(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Enables multi-select. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Scopes the lookup. Required for results to return. */
+ summitId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ /** Overrides the default querySponsors lookup. */
+ queryFunction: PropTypes.func
+};
export default SponsorInput;
diff --git a/src/components/inputs/sponsored-project-input.js b/src/components/inputs/sponsored-project-input.js
index 24c8d95c..ed0b9170 100644
--- a/src/components/inputs/sponsored-project-input.js
+++ b/src/components/inputs/sponsored-project-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import { querySponsoredProjects } from '../../utils/query-actions';
@@ -69,3 +70,18 @@ export default class SponsoredProjectInput extends React.Component {
}
}
+
+SponsoredProjectInput.propTypes = {
+ /** Selected sponsored project(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+ /** Presence-gated: sets isClearable regardless of value. */
+ clearable: PropTypes.bool,
+};
diff --git a/src/components/inputs/stepped-select/index.jsx b/src/components/inputs/stepped-select/index.jsx
index e3f7888a..0eba2098 100644
--- a/src/components/inputs/stepped-select/index.jsx
+++ b/src/components/inputs/stepped-select/index.jsx
@@ -1,7 +1,8 @@
import React from 'react';
+import PropTypes from 'prop-types';
import styles from './index.module.less';
-export default ({value, options, onChange, ...rest}) => {
+const SteppedSelect = ({value, options, onChange, ...rest}) => {
const currentOptionKey = options.findIndex(op => op.value === value);
@@ -31,3 +32,17 @@ export default ({value, options, onChange, ...rest}) => {
);
};
+
+SteppedSelect.propTypes = {
+ /** Must match one option's value — an unmatched value throws while reading its label. */
+ value: PropTypes.any.isRequired,
+ /** Order defines the step sequence; the +/- buttons move one position at a time. */
+ options: PropTypes.arrayOf(PropTypes.shape({
+ value: PropTypes.any.isRequired,
+ label: PropTypes.node.isRequired
+ })).isRequired,
+ /** Receives the neighbouring option's value. Not called at either end of the list. */
+ onChange: PropTypes.func.isRequired
+};
+
+export default SteppedSelect;
diff --git a/src/components/inputs/summit-days-select.js b/src/components/inputs/summit-days-select.js
index 6c913b8f..145af8b9 100644
--- a/src/components/inputs/summit-days-select.js
+++ b/src/components/inputs/summit-days-select.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import Select from 'react-select';
const SummitDaysSelect = ({ days, currentValue, placeholder, onDayChanged }) => {
@@ -33,4 +34,17 @@ const SummitDaysSelect = ({ days, currentValue, placeholder, onDayChanged }) =>
);
}
+SummitDaysSelect.propTypes = {
+ /** Doubles as the option list, so each entry needs both value and label. */
+ days: PropTypes.arrayOf(PropTypes.shape({
+ value: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired
+ })).isRequired,
+ /** Matched against day.value. Anything unmatched shows the placeholder. */
+ currentValue: PropTypes.string,
+ placeholder: PropTypes.string,
+ /** Receives the selected day value, or null when cleared. */
+ onDayChanged: PropTypes.func.isRequired
+};
+
export default SummitDaysSelect;
diff --git a/src/components/inputs/summit-input.js b/src/components/inputs/summit-input.js
index 3b66be8f..dab5407b 100644
--- a/src/components/inputs/summit-input.js
+++ b/src/components/inputs/summit-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/lib/Async';
import {querySummits} from '../../utils/query-actions';
@@ -73,3 +74,15 @@ export default class SummitInput extends React.Component {
}
}
+SummitInput.propTypes = {
+ /** Selected summit(s). */
+ value: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string, PropTypes.number]),
+ /** Echoed back as ev.target.id on the synthetic change event. */
+ id: PropTypes.string.isRequired,
+ /** Receives a synthetic { target: { id, value, type } }. */
+ onChange: PropTypes.func.isRequired,
+ /** Gated on the prop being present, so multi={false} still enables multi-select. */
+ multi: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string,
+};
diff --git a/src/components/inputs/summit-venues-select.js b/src/components/inputs/summit-venues-select.js
index 8c1b82c1..87289846 100644
--- a/src/components/inputs/summit-venues-select.js
+++ b/src/components/inputs/summit-venues-select.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import Select from 'react-select';
const SummitVenuesSelect = ({venues, currentValue, placeholder, onVenueChanged, ...rest}) => {
@@ -41,4 +42,24 @@ const SummitVenuesSelect = ({venues, currentValue, placeholder, onVenueChanged,
);
}
+SummitVenuesSelect.propTypes = {
+ /** Flat list of venues and their rooms; rooms are indented in the option list. */
+ venues: PropTypes.arrayOf(PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ value: PropTypes.shape({
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+ name: PropTypes.string,
+ /** 'SummitVenue' renders as a venue; anything else renders as a room. */
+ class_name: PropTypes.string
+ }).isRequired
+ })).isRequired,
+ /** Matched by id against venues[].value.id, not by identity. */
+ currentValue: PropTypes.shape({
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
+ }),
+ placeholder: PropTypes.string,
+ /** Receives the selected location object, or null when cleared. */
+ onVenueChanged: PropTypes.func.isRequired
+};
+
export default SummitVenuesSelect;
diff --git a/src/components/inputs/text-input.js b/src/components/inputs/text-input.js
index 281a31e1..15d47aa9 100644
--- a/src/components/inputs/text-input.js
+++ b/src/components/inputs/text-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
export default class Input extends React.Component {
@@ -56,6 +57,20 @@ export default class Input extends React.Component {
}
}
+Input.propTypes = {
+ /** Applied as defaultValue — the input is uncontrolled and only re-synced when this prop changes. */
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ /** Receives the raw DOM change event. */
+ onChange: PropTypes.func.isRequired,
+ /** Replaces the default 'form-control' class on the input. */
+ className: PropTypes.string,
+ /** Wrapper class; defaults to 'container-form-control'. */
+ containerClassName: PropTypes.string,
+ /** Non-empty renders an .error-label and adds the error class. */
+ error: PropTypes.string,
+ ariaLabelledBy: PropTypes.string
+};
+
Input.defaultProps = {
ariaLabelledBy : null,
}
\ No newline at end of file
diff --git a/src/components/inputs/textarea-input.js b/src/components/inputs/textarea-input.js
index 49909d8c..bde8d470 100644
--- a/src/components/inputs/textarea-input.js
+++ b/src/components/inputs/textarea-input.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
const TextArea = ({ onChange, value, className, error, maxLength, ...rest }) => {
const has_error = error && error !== '';
@@ -47,6 +48,19 @@ const TextArea = ({ onChange, value, className, error, maxLength, ...rest }) =>
);
}
+TextArea.propTypes = {
+ /** Textarea contents. Its length drives the character counter. */
+ value: PropTypes.string,
+ /** Receives the raw change event. Suppressed once maxLength is reached, except on delete. */
+ onChange: PropTypes.func.isRequired,
+ /** Replaces the default 'form-control' class. */
+ className: PropTypes.string,
+ /** Non-empty renders an .error-label and marks the field. */
+ error: PropTypes.string,
+ /** Omit to hide the "characters left" counter. */
+ maxLength: PropTypes.number
+};
+
TextArea.defaultProps = {
value: ""
};
diff --git a/src/components/inputs/upload-input-v2/index.js b/src/components/inputs/upload-input-v2/index.js
index dc2c6b19..2ee2dfb0 100644
--- a/src/components/inputs/upload-input-v2/index.js
+++ b/src/components/inputs/upload-input-v2/index.js
@@ -12,6 +12,7 @@
**/
import React from 'react'
+import PropTypes from 'prop-types';
import DropzoneJS from '../dropzone'
import './index.less';
import file_icon from '../upload-input/file.png';
@@ -177,3 +178,34 @@ export default class UploadInputV2 extends React.Component {
);
}
}
+
+UploadInputV2.propTypes = {
+ id: PropTypes.string,
+ /** Already-uploaded files. Destructured with no default and read as value.length; required. */
+ value: PropTypes.array.isRequired,
+ /** Endpoint the dropzone POSTs to. */
+ postUrl: PropTypes.string,
+ /** Drives allowed extensions and max size unless the getters below override them. */
+ mediaType: PropTypes.shape({
+ max_size: PropTypes.number,
+ type: PropTypes.shape({ allowed_extensions: PropTypes.array })
+ }),
+ /** Upload is blocked once value reaches this count. */
+ maxFiles: PropTypes.number,
+ canAdd: PropTypes.bool,
+ onRemove: PropTypes.func,
+ onUploadComplete: PropTypes.func,
+ onError: PropTypes.func,
+ /** Extra Dropzone config, merged last. */
+ djsConfig: PropTypes.object,
+ timeOut: PropTypes.number,
+ parallelChunkUploads: PropTypes.bool,
+ maxConcurrentChunks: PropTypes.number,
+ /** Returns a comma-separated extension list, overriding mediaType. */
+ getAllowedExtensions: PropTypes.func,
+ /** Returns max size in MB, overriding mediaType. */
+ getMaxSize: PropTypes.func,
+ canDelete: PropTypes.bool,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string
+};
diff --git a/src/components/inputs/upload-input-v3/index.js b/src/components/inputs/upload-input-v3/index.js
index 46a7766c..6e361263 100644
--- a/src/components/inputs/upload-input-v3/index.js
+++ b/src/components/inputs/upload-input-v3/index.js
@@ -12,6 +12,7 @@
**/
import React, { useState, useRef, useMemo, useCallback, useLayoutEffect, useEffect } from 'react';
+import PropTypes from 'prop-types';
import T from "i18n-react/dist/i18n-react";
import {
Box,
@@ -501,4 +502,38 @@ const UploadInputV3 = ({
);
};
+UploadInputV3.propTypes = {
+ id: PropTypes.string,
+ /** Already-uploaded files. */
+ value: PropTypes.array,
+ /** Endpoint the dropzone POSTs to. */
+ postUrl: PropTypes.string,
+ /** Drives allowed extensions and max size unless the getters below override them. */
+ mediaType: PropTypes.shape({
+ max_size: PropTypes.number,
+ type: PropTypes.shape({ allowed_extensions: PropTypes.array })
+ }),
+ /** Upload is blocked once value reaches this count. */
+ maxFiles: PropTypes.number,
+ canAdd: PropTypes.bool,
+ onRemove: PropTypes.func,
+ onUploadComplete: PropTypes.func,
+ onError: PropTypes.func,
+ /** Extra Dropzone config, merged last. */
+ djsConfig: PropTypes.object,
+ timeOut: PropTypes.number,
+ parallelChunkUploads: PropTypes.bool,
+ maxConcurrentChunks: PropTypes.number,
+ /** Returns a comma-separated extension list, overriding mediaType. */
+ getAllowedExtensions: PropTypes.func,
+ /** Returns max size in MB, overriding mediaType. */
+ getMaxSize: PropTypes.func,
+ canDelete: PropTypes.bool,
+ /** Fired when an upload begins. */
+ onUploadStart: PropTypes.func,
+ label: PropTypes.node,
+ helpText: PropTypes.node,
+ /** Non-empty renders an MUI Alert (severity="error"), not an .error-label. */
+ error: PropTypes.string
+};
export default UploadInputV3;
diff --git a/src/components/inputs/upload-input/index.js b/src/components/inputs/upload-input/index.js
index c552952a..597286c0 100644
--- a/src/components/inputs/upload-input/index.js
+++ b/src/components/inputs/upload-input/index.js
@@ -12,6 +12,7 @@
**/
import React, {useEffect, useState} from 'react';
+import PropTypes from 'prop-types';
import Dropzone from 'react-dropzone';
import T from 'i18n-react/dist/i18n-react';
import './upload.less';
@@ -120,4 +121,16 @@ const UploadInput = ({value, error, handleRemove, handleUpload, handleError, ...
)
}
+UploadInput.propTypes = {
+ /** Existing file URL. An image renders as a preview, anything else as a file icon. */
+ value: PropTypes.string,
+ /** Called with the accepted file(s). Called unconditionally on drop, unlike handleError below; required. */
+ handleUpload: PropTypes.func.isRequired,
+ /** Called when the existing file is cleared. Called unconditionally on remove-icon click, with no guard; required. */
+ handleRemove: PropTypes.func.isRequired,
+ /** Called with rejected files, e.g. wrong type or too large. */
+ handleError: PropTypes.func,
+ /** Non-empty renders an .error-label. */
+ error: PropTypes.string
+};
export default UploadInput;
diff --git a/src/components/raw-html/index.js b/src/components/raw-html/index.js
index 1cb2fa15..1d787696 100644
--- a/src/components/raw-html/index.js
+++ b/src/components/raw-html/index.js
@@ -1,7 +1,16 @@
import React from 'react';
+import PropTypes from 'prop-types';
const RawHTML = ({children, replaceNewLine = false, className = "", ...rest}) =>
') : children}} {...rest}/>
+RawHTML.propTypes = {
+ /** HTML string, injected unescaped via dangerouslySetInnerHTML. Never pass untrusted input. */
+ children: PropTypes.string,
+ /** Converts newlines to
before injecting. */
+ replaceNewLine: PropTypes.bool,
+ className: PropTypes.string
+};
+
export default RawHTML;
\ No newline at end of file
diff --git a/src/components/schedule-builder-view/index.js b/src/components/schedule-builder-view/index.js
index 89875f08..0e7f332c 100644
--- a/src/components/schedule-builder-view/index.js
+++ b/src/components/schedule-builder-view/index.js
@@ -1,4 +1,5 @@
import React, {useEffect, useMemo} from 'react';
+import PropTypes from 'prop-types';
import SummitDaysSelect from "../inputs/summit-days-select";
import SummitVenuesSelect from "../inputs/summit-venues-select";
import SteppedSelect from "../inputs/stepped-select/index.jsx";
@@ -176,4 +177,47 @@ const ScheduleBuilderView = ({
);
}
+ScheduleBuilderView.propTypes = {
+ /** Provides the day range, locations and timezone for the grid. */
+ summit: PropTypes.shape({
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ start_date: PropTypes.number.isRequired,
+ end_date: PropTypes.number.isRequired,
+ time_zone_id: PropTypes.string,
+ /** Required: schedule-event-list reads time_zone.name. */
+ time_zone: PropTypes.shape({ name: PropTypes.string.isRequired }).isRequired,
+ locations: PropTypes.array.isRequired
+ }).isRequired,
+ /** Restricts selectable days and venues per track. Null allows everything. */
+ trackSpaceTime: PropTypes.array,
+ /** Events placed on the grid. */
+ scheduleEvents: PropTypes.array.isRequired,
+ selectedEvents: PropTypes.array,
+ /** YYYY-MM-DD. Reset via onDayChanged when the venue no longer allows it. */
+ currentDay: PropTypes.string,
+ /** The location object, matched by id. */
+ currentVenue: PropTypes.object,
+ /** Slot height in minutes. */
+ slotSize: PropTypes.number,
+ hideBulkSelect: PropTypes.bool,
+ allowResize: PropTypes.bool,
+ allowDrag: PropTypes.bool,
+ /** Renders the print button when provided. */
+ showPrint: PropTypes.bool,
+ /** Called unconditionally (no guard) when the venue change invalidates currentDay; required. */
+ onDayChanged: PropTypes.func.isRequired,
+ onVenueChanged: PropTypes.func,
+ onSlotSizeChange: PropTypes.func,
+ onScheduleEvent: PropTypes.func,
+ onUnPublishEvent: PropTypes.func,
+ onEditEvent: PropTypes.func,
+ onClickSelected: PropTypes.func,
+ onMoveSingleEvent: PropTypes.func,
+ onSelectAll: PropTypes.func,
+ onSelectedBulkAction: PropTypes.func,
+ /** Overrides the default bulkOptions passed to BulkActionsSelector. */
+ customBulkOptions: PropTypes.array,
+ /** Passed through to ScheduleEventList to gate drop targets. */
+ canDropEvent: PropTypes.func
+};
export default ScheduleBuilderView;
diff --git a/src/components/sections/panel.js b/src/components/sections/panel.js
index d30c8818..205fea6e 100644
--- a/src/components/sections/panel.js
+++ b/src/components/sections/panel.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
export default class Panel extends React.Component {
@@ -44,4 +45,16 @@ export default class Panel extends React.Component {
);
}
-}
\ No newline at end of file
+}
+
+Panel.propTypes = {
+ /** Heading text. Also seeds the fallback DOM id when `id` is omitted. */
+ title: PropTypes.node,
+ /** Body is only mounted while true; the heading stays visible either way. */
+ show: PropTypes.bool,
+ /** Click handler on the heading. The component holds no open/closed state itself. */
+ handleClick: PropTypes.func,
+ children: PropTypes.node,
+ className: PropTypes.string,
+ id: PropTypes.string
+};
\ No newline at end of file
diff --git a/src/components/summit-dropdown/index.js b/src/components/summit-dropdown/index.js
index 7a2a4f44..775a93a6 100644
--- a/src/components/summit-dropdown/index.js
+++ b/src/components/summit-dropdown/index.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import './summit-dropdown.less';
import Select from 'react-select';
import T from 'i18n-react/dist/i18n-react';
@@ -73,3 +74,18 @@ export default class SummitDropdown extends React.Component {
}
}
+
+SummitDropdown.propTypes = {
+ /** Sorted by start_date descending before display. */
+ summits: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+ name: PropTypes.string.isRequired,
+ start_date: PropTypes.number
+ })).isRequired,
+ /** Fires with the selected summit id. The button stays disabled until a choice is made. */
+ onClick: PropTypes.func.isRequired,
+ actionLabel: PropTypes.node,
+ actionClass: PropTypes.string,
+ /** Gated on presence, so big={false} still applies the large styling. */
+ big: PropTypes.bool
+};
diff --git a/src/components/table-editable/EditableTable.js b/src/components/table-editable/EditableTable.js
index 11af19ae..39925b42 100644
--- a/src/components/table-editable/EditableTable.js
+++ b/src/components/table-editable/EditableTable.js
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import EditableTableHeading from './EditableTableHeading';
import EditableTableCell from './EditableTableCell';
import EditableActionsTableCell from './EditableActionsTableCell';
@@ -237,3 +238,27 @@ export default class EditableTable extends React.Component {
);
}
};
+
+EditableTable.propTypes = {
+ /** Rows are copied into local state; each needs an `id`. Re-synced when this prop changes. `:220` explicitly handles array rows too. */
+ data: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.object, PropTypes.array])).isRequired,
+ columns: PropTypes.arrayOf(PropTypes.shape({
+ /** Key used to read and write the cell on each row. */
+ columnKey: PropTypes.string.isRequired,
+ value: PropTypes.node,
+ width: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
+ })).isRequired,
+ /** Renders textareas instead of inputs. Gated on presence, so textArea={false} still enables it. */
+ textArea: PropTypes.bool,
+ options: PropTypes.shape({
+ className: PropTypes.string,
+ /** Skips the sweetalert confirmation on delete. Gated on presence. */
+ noAlert: PropTypes.bool,
+ actions: PropTypes.shape({
+ /** Called with the whole edited row once the user commits it. */
+ save: PropTypes.shape({ onClick: PropTypes.func.isRequired }),
+ /** Called with the row id, after confirmation unless noAlert is set. */
+ delete: PropTypes.shape({ onClick: PropTypes.func.isRequired })
+ })
+ }).isRequired
+};
diff --git a/src/components/table-selectable/SelectableTable.js b/src/components/table-selectable/SelectableTable.js
index 0f727fe7..63d40cf7 100644
--- a/src/components/table-selectable/SelectableTable.js
+++ b/src/components/table-selectable/SelectableTable.js
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import SelectableTableHeading from './SelectableTableHeading';
import SelectableTableCell from './SelectableTableCell';
import SelectableTableRow from './SelectableTableRow';
@@ -182,4 +183,50 @@ class SelectableTable extends React.Component {
}
}
+SelectableTable.propTypes = {
+ columns: PropTypes.arrayOf(PropTypes.shape({
+ columnKey: PropTypes.string.isRequired,
+ value: PropTypes.node,
+ sortable: PropTypes.bool,
+ width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ /** (row, cellValue) => node. Overrides default cell rendering. */
+ render: PropTypes.func
+ })).isRequired,
+ /** Each row's own `checked` flag drives its checkbox; selection state lives with the caller. `:152` explicitly handles array rows too. */
+ data: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.object, PropTypes.array])).isRequired,
+ options: PropTypes.shape({
+ className: PropTypes.string,
+ /** Hides the header select-all checkbox. */
+ disableSelectAll: PropTypes.bool,
+ /** Controlled checked state of the select-all checkbox. */
+ selectedAll: PropTypes.bool,
+ sortCol: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ sortDir: PropTypes.number,
+ sortFunc: PropTypes.func,
+ actionsHeader: PropTypes.node,
+ actions: PropTypes.shape({
+ edit: PropTypes.shape({
+ /** Row click. Checkbox clicks are excluded. */
+ onClick: PropTypes.func,
+ /** (id, checked) for a single row. */
+ onSelected: PropTypes.func,
+ /** Change handler for the header select-all checkbox. */
+ onSelectedAll: PropTypes.func,
+ display: PropTypes.func
+ }),
+ delete: PropTypes.shape({
+ onClick: PropTypes.func.isRequired,
+ display: PropTypes.func
+ }),
+ custom: PropTypes.arrayOf(PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ icon: PropTypes.node,
+ tooltip: PropTypes.string,
+ onClick: PropTypes.func.isRequired,
+ display: PropTypes.func
+ }))
+ })
+ }).isRequired
+};
+
export default SelectableTable;
diff --git a/src/components/table/Table.js b/src/components/table/Table.js
index 50ae9cf3..6677ad86 100644
--- a/src/components/table/Table.js
+++ b/src/components/table/Table.js
@@ -1,4 +1,5 @@
import React from 'react';
+import PropTypes from 'prop-types';
import TableHeading from './TableHeading';
import TableCell from './TableCell';
import TableRow from './TableRow';
@@ -114,4 +115,51 @@ const Table = (props) => {
);
};
+const actionShape = PropTypes.shape({
+ onClick: PropTypes.func.isRequired,
+ /** (id) => bool — return false to hide this action for a given row. */
+ display: PropTypes.func
+});
+
+Table.propTypes = {
+ columns: PropTypes.arrayOf(PropTypes.shape({
+ /** Key used to read the cell out of each row: row[columnKey]. */
+ columnKey: PropTypes.string.isRequired,
+ /** Heading content. */
+ value: PropTypes.node,
+ sortable: PropTypes.bool,
+ width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ /** Presence adds a title attribute to the cell, set to the raw value. */
+ title: PropTypes.any,
+ /** (row, cellValue) => node. Overrides default cell rendering. */
+ render: PropTypes.func,
+ styles: PropTypes.object
+ })).isRequired,
+ /** Row objects keyed by columnKey. Each needs an `id` when actions are used. */
+ data: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.object, PropTypes.array])).isRequired,
+ /** Called by sortable headings. */
+ onSort: PropTypes.func,
+ options: PropTypes.shape({
+ className: PropTypes.string,
+ /** Matched against either a column's columnKey or its numeric index. */
+ sortCol: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ sortDir: PropTypes.number,
+ sortFunc: PropTypes.func,
+ /** Heading for the actions column. */
+ actionsHeader: PropTypes.node,
+ actions: PropTypes.shape({
+ /** Makes whole rows clickable and adds the table-hover class. */
+ edit: actionShape,
+ delete: actionShape,
+ custom: PropTypes.arrayOf(PropTypes.shape({
+ name: PropTypes.string.isRequired,
+ icon: PropTypes.node,
+ tooltip: PropTypes.string,
+ onClick: PropTypes.func.isRequired,
+ display: PropTypes.func
+ }))
+ })
+ }).isRequired
+};
+
export default Table;
diff --git a/src/components/video-stream.js b/src/components/video-stream.js
index 4d4205ef..aa0c4ab2 100644
--- a/src/components/video-stream.js
+++ b/src/components/video-stream.js
@@ -12,6 +12,7 @@
**/
import React from 'react';
+import PropTypes from 'prop-types';
import videojs from 'video.js'
import 'video.js/dist/video-js.css'
@@ -82,4 +83,9 @@ const VideoStream = ({ url }) => {
return layout;
};
+VideoStream.propTypes = {
+ /** A .m3u8 URL plays through video.js as a live stream; anything else is embedded in an iframe. Omit to render the "No video URL Provided" placeholder. */
+ url: PropTypes.string
+};
+
export default VideoStream;