Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ package.json.lock
docs
package-lock.json
.claude

storybook-static
26 changes: 26 additions & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const styleRule = (test, extraLoader, { modules = false, exclude } = {}) => ({
test,
...(exclude ? { exclude } : {}),
use: [
"style-loader",
{ loader: "css-loader", options: { modules, sourceMap: false } },
...(extraLoader ? [{ loader: extraLoader, options: { sourceMap: false } }] : [])
]
});

module.exports = {
framework: "@storybook/react-webpack5",
// the webpack5 builder ships no JS compiler; without this addon nothing transpiles JSX
addons: ["@storybook/addon-docs", "@storybook/addon-webpack5-compiler-babel"],
stories: ["../stories/**/*.stories.@(js|jsx)"],
webpackFinal: (config) => {
// the builder ships plain css only; src imports less/scss in both module and global form
config.module.rules.push(
styleRule(/\.module\.less$/, "less-loader", { modules: true }),
styleRule(/\.module\.scss$/, "sass-loader", { modules: true }),
styleRule(/\.less$/, "less-loader", { exclude: /\.module\.less$/ }),
styleRule(/\.scss$/, "sass-loader", { exclude: /\.module\.scss$/ })
);
return config;
}
};
10 changes: 10 additions & 0 deletions .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!--
Injected into the preview iframe head (not the Storybook UI).

The legacy Core UI components emit bootstrap 3 classes (form-control,
btn btn-default, panel panel-default, col-md-*) but the library declares no
bootstrap dependency — consuming apps supply it. summit-admin, the current
reference consumer, links this exact file from src/index.ejs, so the preview
loads the same one rather than guessing a version.
-->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
44 changes: 44 additions & 0 deletions .storybook/preview.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Provider } from "react-redux";
import {
legacy_createStore as createStore,
combineReducers,
applyMiddleware
} from "redux";
Comment on lines +2 to +6
import thunk from "redux-thunk";
import { ThemeProvider, createTheme } from "@mui/material";
// Legacy (non-MUI) components render `fa fa-*` icons and bootstrap classes. The
// consuming apps supply both: summit-admin imports this same font-awesome css in
// src/index.js, and links bootstrap 3.3.7 from its index.ejs — mirrored for the
// preview iframe in .storybook/preview-head.html.
import "font-awesome/css/font-awesome.css";
import "../src/i18n/i18n"; // side-effect: T.setTexts, else components render raw keys
import { genericReducers } from "../src/utils/reducers";
import allFiltersReducer from "../src/components/mui/GridFilter/reducers/all-filters-reducer";
import { MuiBaseCustomTheme } from "../src/components/mui/MuiBaseCustomTheme";

const theme = createTheme(MuiBaseCustomTheme);

// GridFilter reads allGridFiltersState; SnackbarNotification reads baseState and
// dispatches clearSnackbarMessage, which is a thunk — hence the middleware.
export const store = createStore(
combineReducers({
baseState: genericReducers,
allGridFiltersState: allFiltersReducer
}),
applyMiddleware(thunk)
);

export const decorators = [
(Story) => (
<Provider store={store}>
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
</Provider>
)
];

export const parameters = { controls: { expanded: true } };

// gives every component a generated Docs page (props table + live controls)
export const tags = ["autodocs"];
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"clean": "rm -Rf node_modules & rm -Rf lib & yarn install",
"build-dev": "./node_modules/.bin/webpack --config webpack.dev.js",
"build": "./node_modules/.bin/webpack --config webpack.prod.js",
"test": "jest"
"test": "jest",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"license": "APACHE 2.0",
"dependencies": {
Expand All @@ -34,6 +36,9 @@
"@react-pdf/renderer": "^4.4.1",
"@sentry/react": "^8.54.0",
"@sentry/webpack-plugin": "^3.1.2",
"@storybook/addon-docs": "^10",
"@storybook/addon-webpack5-compiler-babel": "^4",
"@storybook/react-webpack5": "^10",
"@stripe/react-stripe-js": "^5.4.1",
"@stripe/stripe-js": "^8.5.3",
"@testing-library/jest-dom": "5.17.0",
Expand Down Expand Up @@ -99,6 +104,7 @@
"sass": "^1.77.0",
"sass-loader": "^14.2.1",
"spark-md5": "^3.0.2",
"storybook": "^10",
"style-loader": "^3.3.1",
"superagent": "8.0.9",
"sweetalert2": "^8.15.2",
Expand Down
14 changes: 14 additions & 0 deletions src/components/ajaxloader/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
**/

import React from 'react';
import PropTypes from 'prop-types';

const AjaxLoader = ({
show,
Expand Down Expand Up @@ -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;
15 changes: 15 additions & 0 deletions src/components/bulk-actions-selector/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
11 changes: 11 additions & 0 deletions src/components/clock.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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;
7 changes: 7 additions & 0 deletions src/components/exclusive-wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
**/

import React from 'react'
import PropTypes from 'prop-types'


export default class Exclusive extends React.Component {
Expand Down Expand Up @@ -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
};
18 changes: 18 additions & 0 deletions src/components/forms/rsvp-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -164,4 +165,21 @@ class RsvpForm extends React.Component {
}
}

RsvpForm.propTypes = {
/** Rendered by class_name, e.g. RSVPTextBoxQuestionTemplate or RSVPCheckBoxListQuestionTemplate. */
questions: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.oneOfType([PropTypes.string, 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
})).isRequired,
/** Receives the collected answers array on submit. */
onSubmit: PropTypes.func.isRequired,
/** Keyed by question id. Read once into state at mount. */
errors: PropTypes.object
};
export default RsvpForm;
17 changes: 17 additions & 0 deletions src/components/forms/simple-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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,
/** One of 'text', 'textarea', 'checkbox'. */
type: PropTypes.string.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;
21 changes: 21 additions & 0 deletions src/components/inputs/access-levels-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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
};
17 changes: 17 additions & 0 deletions src/components/inputs/action-dropdown/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
**/

import React from 'react';
import PropTypes from 'prop-types';
import './action-dropdown.less';
import Select from 'react-select';

Expand Down Expand Up @@ -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
};
21 changes: 21 additions & 0 deletions src/components/inputs/attendee-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
/** 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 queryAttendees lookup. */
queryFunction: PropTypes.func,
/** (attendee) => value. Defaults to attendee.id. */
getOptionValue: PropTypes.func,
/** (attendee) => label. */
getOptionLabel: PropTypes.func
};
export default AttendeeInput;

24 changes: 24 additions & 0 deletions src/components/inputs/company-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -115,3 +116,26 @@ export default class CompanyInput extends React.Component {

}
}

CompanyInput.propTypes = {
/** Selected company or companies, as { id, name }. */
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,
/** 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. */
onCreate: PropTypes.func,
/** Overrides the default queryCompanies lookup. */
queryFunction: PropTypes.func,
/** Appended to the fetched option list. */
extraOptions: PropTypes.array
};
Loading
Loading