From e5a2a10346dc1a35d055ab930a939f5f1f47107e Mon Sep 17 00:00:00 2001 From: Casey Locker Date: Wed, 12 Aug 2026 12:59:11 -0500 Subject: [PATCH 1/2] feat(storybook): add Storybook with full Core UI and MUI component coverage Adds Storybook 10 (react-webpack5) covering the component library: 304 index entries across 194 stories and 110 generated docs pages. * 35 story files for the non-MUI Core UI components: inputs, the four tables, forms, uploads, schedule builder, plus one grouped file for the async entity selects * Autodocs enabled globally, so each component gets a props table with live controls * webpack rules for .less/.scss in module and global form, which the webpack5 builder does not ship * font-awesome imported and bootstrap 3.3.7 linked via preview-head.html, matching what summit-admin and the other consumers load, so legacy components render with their icons and bootstrap classes * replaces SummitAddonSelect stories with AddonTypeSelect following the rename in #303 storybook-static is gitignored. --- .gitignore | 2 + .storybook/main.js | 26 + .storybook/preview-head.html | 10 + .storybook/preview.jsx | 44 + package.json | 8 +- stories/AddonTypeSelect.stories.jsx | 13 + stories/AlertButton.stories.jsx | 10 + stories/AlertModal.stories.jsx | 16 + stories/AuthButton.stories.jsx | 17 + stories/BulkEditTable.stories.jsx | 21 + stories/CartButton.stories.jsx | 12 + stories/CheckboxList.stories.jsx | 22 + stories/ChipList.stories.jsx | 9 + stories/ChipNotify.stories.jsx | 8 + stories/ChipSelectInput.stories.jsx | 17 + stories/ConfirmDeleteDialog.stories.jsx | 12 + stories/ConfirmDialog.stories.jsx | 25 + stories/CustomAlert.stories.jsx | 9 + stories/CustomTablePagination.stories.jsx | 11 + stories/DndList.stories.jsx | 24 + stories/DownloadBtn.stories.jsx | 6 + stories/DragNDropList.stories.jsx | 23 + stories/Dropdown.stories.jsx | 22 + stories/DropdownCheckbox.stories.jsx | 22 + stories/EditableTable.stories.jsx | 20 + stories/ExtraRows.stories.jsx | 30 + stories/FormItemTable.stories.jsx | 54 + stories/FormikAdditionalInput.stories.jsx | 25 + stories/FormikAdditionalInputList.stories.jsx | 19 + stories/FormikAddonTypeSelect.stories.jsx | 11 + stories/FormikAsyncSelect.stories.jsx | 24 + stories/FormikCheckbox.stories.jsx | 11 + stories/FormikCheckboxGroup.stories.jsx | 19 + stories/FormikCompanyInput.stories.jsx | 12 + stories/FormikDatepicker.stories.jsx | 11 + stories/FormikDiscountField.stories.jsx | 11 + stories/FormikDropdownCheckbox.stories.jsx | 19 + stories/FormikDropdownRadio.stories.jsx | 19 + stories/FormikFileSizeField.stories.jsx | 11 + stories/FormikItemPriceTiers.stories.jsx | 11 + stories/FormikPriceField.stories.jsx | 11 + stories/FormikQuantityField.stories.jsx | 11 + stories/FormikRadioGroup.stories.jsx | 18 + stories/FormikSelect.stories.jsx | 25 + stories/FormikSelectGroup.stories.jsx | 27 + stories/FormikSelectV2.stories.jsx | 20 + stories/FormikSponsorInput.stories.jsx | 12 + stories/FormikSponsorshipInput.stories.jsx | 12 + .../FormikSponsorshipSummitSelect.stories.jsx | 12 + stories/FormikSwitch.stories.jsx | 11 + stories/FormikTextEditor.stories.jsx | 11 + stories/FormikTextField.stories.jsx | 11 + stories/FormikTimepicker.stories.jsx | 11 + stories/FormikUpload.stories.jsx | 11 + stories/GridFilter.stories.jsx | 58 + stories/InfiniteTable.stories.jsx | 15 + stories/InfoNote.stories.jsx | 8 + stories/InlineCard.stories.jsx | 15 + stories/ItemSettingsModal.stories.jsx | 16 + stories/ListCard.stories.jsx | 15 + stories/LoadingOverlay.stories.jsx | 16 + stories/MenuButton.stories.jsx | 19 + stories/NavBar.stories.jsx | 20 + stories/NotesModal.stories.jsx | 20 + stories/OrderSummary.stories.jsx | 13 + stories/RoundButton.stories.jsx | 12 + stories/SearchInput.stories.jsx | 12 + stories/ShowConfirmDialog.stories.jsx | 37 + stories/SnackbarNotification.stories.jsx | 65 + stories/SortableTable.stories.jsx | 24 + stories/SponsorAddonSelect.stories.jsx | 18 + stories/SponsorOrderGrid.stories.jsx | 44 + stories/StatusChip.stories.jsx | 9 + stories/StripePayment.stories.jsx | 35 + stories/SummitsDropdown.stories.jsx | 28 + stories/Table.stories.jsx | 37 + stories/TableCard.stories.jsx | 19 + stories/ToggleButtons.stories.jsx | 16 + stories/UploadBtn.stories.jsx | 11 + stories/UploadDialog.stories.jsx | 23 + stories/_form-item-fixture.js | 102 + stories/_helpers.jsx | 41 + stories/core/ActionDropdown.stories.jsx | 16 + stories/core/AjaxLoader.stories.jsx | 17 + stories/core/AsyncEntityInputs.stories.jsx | 59 + stories/core/BulkActionsSelector.stories.jsx | 20 + stories/core/CheckboxList.stories.jsx | 16 + stories/core/CircleButton.stories.jsx | 20 + stories/core/Clock.stories.jsx | 13 + stories/core/DateTimePicker.stories.jsx | 26 + stories/core/Dropdown.stories.jsx | 18 + stories/core/EditableTable.stories.jsx | 29 + stories/core/Exclusive.stories.jsx | 20 + stories/core/FreeMultiTextInput.stories.jsx | 12 + stories/core/FreeTextSearch.stories.jsx | 10 + stories/core/GroupedDropdown.stories.jsx | 26 + stories/core/Panel.stories.jsx | 17 + stories/core/RadioList.stories.jsx | 17 + stories/core/RawHTML.stories.jsx | 14 + stories/core/RsvpForm.stories.jsx | 17 + stories/core/ScheduleBuilderView.stories.jsx | 70 + stories/core/SelectableTable.stories.jsx | 37 + stories/core/SimpleForm.stories.jsx | 22 + stories/core/SimpleLinkList.stories.jsx | 34 + stories/core/SortableTable.stories.jsx | 33 + stories/core/SteppedSelect.stories.jsx | 16 + stories/core/SummitDaysSelect.stories.jsx | 15 + stories/core/SummitDropdown.stories.jsx | 16 + stories/core/SummitVenuesSelect.stories.jsx | 17 + stories/core/Table.stories.jsx | 36 + stories/core/TextArea.stories.jsx | 13 + stories/core/TextEditor.stories.jsx | 13 + stories/core/TextInput.stories.jsx | 10 + stories/core/UploadInput.stories.jsx | 16 + stories/core/UploadInputV2.stories.jsx | 22 + stories/core/UploadInputV3.stories.jsx | 22 + stories/core/VideoStream.stories.jsx | 10 + yarn.lock | 1697 ++++++++++++++++- 118 files changed, 4098 insertions(+), 15 deletions(-) create mode 100644 .storybook/main.js create mode 100644 .storybook/preview-head.html create mode 100644 .storybook/preview.jsx create mode 100644 stories/AddonTypeSelect.stories.jsx create mode 100644 stories/AlertButton.stories.jsx create mode 100644 stories/AlertModal.stories.jsx create mode 100644 stories/AuthButton.stories.jsx create mode 100644 stories/BulkEditTable.stories.jsx create mode 100644 stories/CartButton.stories.jsx create mode 100644 stories/CheckboxList.stories.jsx create mode 100644 stories/ChipList.stories.jsx create mode 100644 stories/ChipNotify.stories.jsx create mode 100644 stories/ChipSelectInput.stories.jsx create mode 100644 stories/ConfirmDeleteDialog.stories.jsx create mode 100644 stories/ConfirmDialog.stories.jsx create mode 100644 stories/CustomAlert.stories.jsx create mode 100644 stories/CustomTablePagination.stories.jsx create mode 100644 stories/DndList.stories.jsx create mode 100644 stories/DownloadBtn.stories.jsx create mode 100644 stories/DragNDropList.stories.jsx create mode 100644 stories/Dropdown.stories.jsx create mode 100644 stories/DropdownCheckbox.stories.jsx create mode 100644 stories/EditableTable.stories.jsx create mode 100644 stories/ExtraRows.stories.jsx create mode 100644 stories/FormItemTable.stories.jsx create mode 100644 stories/FormikAdditionalInput.stories.jsx create mode 100644 stories/FormikAdditionalInputList.stories.jsx create mode 100644 stories/FormikAddonTypeSelect.stories.jsx create mode 100644 stories/FormikAsyncSelect.stories.jsx create mode 100644 stories/FormikCheckbox.stories.jsx create mode 100644 stories/FormikCheckboxGroup.stories.jsx create mode 100644 stories/FormikCompanyInput.stories.jsx create mode 100644 stories/FormikDatepicker.stories.jsx create mode 100644 stories/FormikDiscountField.stories.jsx create mode 100644 stories/FormikDropdownCheckbox.stories.jsx create mode 100644 stories/FormikDropdownRadio.stories.jsx create mode 100644 stories/FormikFileSizeField.stories.jsx create mode 100644 stories/FormikItemPriceTiers.stories.jsx create mode 100644 stories/FormikPriceField.stories.jsx create mode 100644 stories/FormikQuantityField.stories.jsx create mode 100644 stories/FormikRadioGroup.stories.jsx create mode 100644 stories/FormikSelect.stories.jsx create mode 100644 stories/FormikSelectGroup.stories.jsx create mode 100644 stories/FormikSelectV2.stories.jsx create mode 100644 stories/FormikSponsorInput.stories.jsx create mode 100644 stories/FormikSponsorshipInput.stories.jsx create mode 100644 stories/FormikSponsorshipSummitSelect.stories.jsx create mode 100644 stories/FormikSwitch.stories.jsx create mode 100644 stories/FormikTextEditor.stories.jsx create mode 100644 stories/FormikTextField.stories.jsx create mode 100644 stories/FormikTimepicker.stories.jsx create mode 100644 stories/FormikUpload.stories.jsx create mode 100644 stories/GridFilter.stories.jsx create mode 100644 stories/InfiniteTable.stories.jsx create mode 100644 stories/InfoNote.stories.jsx create mode 100644 stories/InlineCard.stories.jsx create mode 100644 stories/ItemSettingsModal.stories.jsx create mode 100644 stories/ListCard.stories.jsx create mode 100644 stories/LoadingOverlay.stories.jsx create mode 100644 stories/MenuButton.stories.jsx create mode 100644 stories/NavBar.stories.jsx create mode 100644 stories/NotesModal.stories.jsx create mode 100644 stories/OrderSummary.stories.jsx create mode 100644 stories/RoundButton.stories.jsx create mode 100644 stories/SearchInput.stories.jsx create mode 100644 stories/ShowConfirmDialog.stories.jsx create mode 100644 stories/SnackbarNotification.stories.jsx create mode 100644 stories/SortableTable.stories.jsx create mode 100644 stories/SponsorAddonSelect.stories.jsx create mode 100644 stories/SponsorOrderGrid.stories.jsx create mode 100644 stories/StatusChip.stories.jsx create mode 100644 stories/StripePayment.stories.jsx create mode 100644 stories/SummitsDropdown.stories.jsx create mode 100644 stories/Table.stories.jsx create mode 100644 stories/TableCard.stories.jsx create mode 100644 stories/ToggleButtons.stories.jsx create mode 100644 stories/UploadBtn.stories.jsx create mode 100644 stories/UploadDialog.stories.jsx create mode 100644 stories/_form-item-fixture.js create mode 100644 stories/_helpers.jsx create mode 100644 stories/core/ActionDropdown.stories.jsx create mode 100644 stories/core/AjaxLoader.stories.jsx create mode 100644 stories/core/AsyncEntityInputs.stories.jsx create mode 100644 stories/core/BulkActionsSelector.stories.jsx create mode 100644 stories/core/CheckboxList.stories.jsx create mode 100644 stories/core/CircleButton.stories.jsx create mode 100644 stories/core/Clock.stories.jsx create mode 100644 stories/core/DateTimePicker.stories.jsx create mode 100644 stories/core/Dropdown.stories.jsx create mode 100644 stories/core/EditableTable.stories.jsx create mode 100644 stories/core/Exclusive.stories.jsx create mode 100644 stories/core/FreeMultiTextInput.stories.jsx create mode 100644 stories/core/FreeTextSearch.stories.jsx create mode 100644 stories/core/GroupedDropdown.stories.jsx create mode 100644 stories/core/Panel.stories.jsx create mode 100644 stories/core/RadioList.stories.jsx create mode 100644 stories/core/RawHTML.stories.jsx create mode 100644 stories/core/RsvpForm.stories.jsx create mode 100644 stories/core/ScheduleBuilderView.stories.jsx create mode 100644 stories/core/SelectableTable.stories.jsx create mode 100644 stories/core/SimpleForm.stories.jsx create mode 100644 stories/core/SimpleLinkList.stories.jsx create mode 100644 stories/core/SortableTable.stories.jsx create mode 100644 stories/core/SteppedSelect.stories.jsx create mode 100644 stories/core/SummitDaysSelect.stories.jsx create mode 100644 stories/core/SummitDropdown.stories.jsx create mode 100644 stories/core/SummitVenuesSelect.stories.jsx create mode 100644 stories/core/Table.stories.jsx create mode 100644 stories/core/TextArea.stories.jsx create mode 100644 stories/core/TextEditor.stories.jsx create mode 100644 stories/core/TextInput.stories.jsx create mode 100644 stories/core/UploadInput.stories.jsx create mode 100644 stories/core/UploadInputV2.stories.jsx create mode 100644 stories/core/UploadInputV3.stories.jsx create mode 100644 stories/core/VideoStream.stories.jsx diff --git a/.gitignore b/.gitignore index c3abc985..5ed08681 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ package.json.lock docs package-lock.json .claude + +storybook-static diff --git a/.storybook/main.js b/.storybook/main.js new file mode 100644 index 00000000..348be07d --- /dev/null +++ b/.storybook/main.js @@ -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; + } +}; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html new file mode 100644 index 00000000..68ad7c91 --- /dev/null +++ b/.storybook/preview-head.html @@ -0,0 +1,10 @@ + + diff --git a/.storybook/preview.jsx b/.storybook/preview.jsx new file mode 100644 index 00000000..bf2ccbfc --- /dev/null +++ b/.storybook/preview.jsx @@ -0,0 +1,44 @@ +import { Provider } from "react-redux"; +import { + legacy_createStore as createStore, + combineReducers, + applyMiddleware +} from "redux"; +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) => ( + + + + + + ) +]; + +export const parameters = { controls: { expanded: true } }; + +// gives every component a generated Docs page (props table + live controls) +export const tags = ["autodocs"]; diff --git a/package.json b/package.json index 0932c260..50a6363e 100644 --- a/package.json +++ b/package.json @@ -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": { @@ -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", @@ -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", diff --git a/stories/AddonTypeSelect.stories.jsx b/stories/AddonTypeSelect.stories.jsx new file mode 100644 index 00000000..0e2a6eb1 --- /dev/null +++ b/stories/AddonTypeSelect.stories.jsx @@ -0,0 +1,13 @@ +import AddonTypeSelect from "../src/components/mui/addon-type-select"; +import { NEEDS_API } from "./_helpers"; + +export default { + title: "MUI/API-backed/AddonTypeSelect", + component: AddonTypeSelect, + argTypes: { onChange: { action: "changed" } }, + parameters: { docs: { description: { component: NEEDS_API } } } +}; + +// Options come from querySummitAddons and are keyed by add-on name, so `value` +// is a name rather than an id. No summit scoping — the query is global. +export const Default = { args: { value: "", placeholder: "Select an add-on" } }; diff --git a/stories/AlertButton.stories.jsx b/stories/AlertButton.stories.jsx new file mode 100644 index 00000000..e524100c --- /dev/null +++ b/stories/AlertButton.stories.jsx @@ -0,0 +1,10 @@ +import AlertButton from "../src/components/mui/AlertButton"; + +export default { + title: "MUI/Buttons/AlertButton", + component: AlertButton, + argTypes: { onClick: { action: "clicked" } } +}; + +export const Default = { args: { label: "Resolve conflict" } }; + diff --git a/stories/AlertModal.stories.jsx b/stories/AlertModal.stories.jsx new file mode 100644 index 00000000..9677f1c3 --- /dev/null +++ b/stories/AlertModal.stories.jsx @@ -0,0 +1,16 @@ +import AlertModal from "../src/components/mui/AlertModal"; + +export default { + title: "MUI/Dialogs/AlertModal", + component: AlertModal, + argTypes: { onClose: { action: "closed" } } +}; + +export const Default = { + args: { + open: true, + title: "Payment declined", + message: "The card ending 4242 was declined. Try another payment method." + } +}; + diff --git a/stories/AuthButton.stories.jsx b/stories/AuthButton.stories.jsx new file mode 100644 index 00000000..59fd9a49 --- /dev/null +++ b/stories/AuthButton.stories.jsx @@ -0,0 +1,17 @@ +import AuthButton from "../src/components/mui/AuthButton"; + +export default { + title: "MUI/Buttons/AuthButton", + component: AuthButton, + argTypes: { doLogin: { action: "login" }, initLogOut: { action: "logout" } } +}; + +export const LoggedOut = { args: { isLoggedUser: false } }; +export const LoggedIn = { + args: { + isLoggedUser: true, + profileName: "Casey Locker", + profileEmail: "casey@example.com" + } +}; + diff --git a/stories/BulkEditTable.stories.jsx b/stories/BulkEditTable.stories.jsx new file mode 100644 index 00000000..bef3f033 --- /dev/null +++ b/stories/BulkEditTable.stories.jsx @@ -0,0 +1,21 @@ +import BulkEditTable from "../src/components/mui/BulkEditTable"; +import { sampleRows } from "./_helpers"; + +export default { + title: "MUI/Tables/BulkEditTable", + component: BulkEditTable, + argTypes: { onSort: { action: "sort" }, onUpdate: { action: "update" } } +}; + +export const Default = { + args: { + options: { sortCol: "name", sortDir: 1 }, + columns: [ + { columnKey: "name", header: "Item", sortable: true }, + { columnKey: "quantity", header: "Qty", align: "right", editable: true }, + { columnKey: "price", header: "Price", align: "right", editable: true } + ], + data: sampleRows + } +}; + diff --git a/stories/CartButton.stories.jsx b/stories/CartButton.stories.jsx new file mode 100644 index 00000000..74800b68 --- /dev/null +++ b/stories/CartButton.stories.jsx @@ -0,0 +1,12 @@ +import CartButton from "../src/components/mui/CartButton"; + +export default { + title: "MUI/Buttons/CartButton", + component: CartButton, + argTypes: { onClick: { action: "clicked" } } +}; + +export const Empty = { args: { itemCount: 0 } }; +export const WithItems = { args: { itemCount: 3 } }; +export const Disabled = { args: { itemCount: 3, disabled: true } }; + diff --git a/stories/CheckboxList.stories.jsx b/stories/CheckboxList.stories.jsx new file mode 100644 index 00000000..9a23a13f --- /dev/null +++ b/stories/CheckboxList.stories.jsx @@ -0,0 +1,22 @@ +import CheckboxList from "../src/components/mui/checkbox-list"; + +export default { + title: "MUI/Inputs/CheckboxList", + component: CheckboxList, + argTypes: { onChange: { action: "changed" } } +}; + +export const Default = { + args: { + items: [ + { id: 1, name: "Keynote Hall", checked: true }, + { id: 2, name: "Breakout A", checked: false }, + { id: 3, name: "Breakout B", checked: false }, + { id: 4, name: "Expo Floor", checked: true } + ], + boxHeight: "220px" + } +}; + +export const Empty = { args: { items: [], boxHeight: "220px" } }; + diff --git a/stories/ChipList.stories.jsx b/stories/ChipList.stories.jsx new file mode 100644 index 00000000..d84a037d --- /dev/null +++ b/stories/ChipList.stories.jsx @@ -0,0 +1,9 @@ +import ChipList from "../src/components/mui/chip-list"; + +export default { title: "MUI/Data display/ChipList", component: ChipList }; + +const chips = ["Diamond", "Gold", "Silver", "Bronze", "In-kind", "Media"]; + +export const Default = { args: { chips } }; +export const Truncated = { args: { chips, maxLength: 3 } }; + diff --git a/stories/ChipNotify.stories.jsx b/stories/ChipNotify.stories.jsx new file mode 100644 index 00000000..069237d9 --- /dev/null +++ b/stories/ChipNotify.stories.jsx @@ -0,0 +1,8 @@ +import ChipNotify from "../src/components/mui/chip-notify"; + +export default { title: "MUI/Data display/ChipNotify", component: ChipNotify }; + +export const Warning = { args: { label: "3 items need review" } }; +export const Success = { args: { label: "All synced", color: "success" } }; +export const Error = { args: { label: "Payment failed", color: "error" } }; + diff --git a/stories/ChipSelectInput.stories.jsx b/stories/ChipSelectInput.stories.jsx new file mode 100644 index 00000000..c0638fcd --- /dev/null +++ b/stories/ChipSelectInput.stories.jsx @@ -0,0 +1,17 @@ +import ChipSelectInput from "../src/components/mui/chip-select-input"; + +export default { title: "MUI/Inputs/ChipSelectInput", component: ChipSelectInput }; + +export const Default = { + args: { + inputLabel: "Badge features", + availableOptions: [ + { id: 1, name: "Expo Access" }, + { id: 2, name: "Keynote Access" }, + { id: 3, name: "Workshop Access" } + ], + canAdd: true, + canEdit: true + } +}; + diff --git a/stories/ConfirmDeleteDialog.stories.jsx b/stories/ConfirmDeleteDialog.stories.jsx new file mode 100644 index 00000000..77342402 --- /dev/null +++ b/stories/ConfirmDeleteDialog.stories.jsx @@ -0,0 +1,12 @@ +import ConfirmDeleteDialog from "../src/components/mui/ConfirmDeleteDialog"; + +export default { + title: "MUI/Dialogs/ConfirmDeleteDialog", + component: ConfirmDeleteDialog, + argTypes: { onClose: { action: "closed" }, onConfirm: { action: "confirmed" } } +}; + +export const Default = { + args: { open: true, message: "Delete the Diamond sponsorship package?" } +}; + diff --git a/stories/ConfirmDialog.stories.jsx b/stories/ConfirmDialog.stories.jsx new file mode 100644 index 00000000..44e9db98 --- /dev/null +++ b/stories/ConfirmDialog.stories.jsx @@ -0,0 +1,25 @@ +import ConfirmDialog from "../src/components/mui/confirm-dialog"; + +export default { + title: "MUI/Dialogs/ConfirmDialog", + component: ConfirmDialog, + argTypes: { onConfirm: { action: "confirmed" }, onCancel: { action: "cancelled" } } +}; + +export const Default = { + args: { + open: true, + title: "Publish this order?", + text: "The sponsor will be emailed a copy of the invoice." + } +}; + +export const Warning = { + args: { + ...Default.args, + iconType: "warning", + confirmButtonText: "Publish anyway", + confirmButtonColor: "warning" + } +}; + diff --git a/stories/CustomAlert.stories.jsx b/stories/CustomAlert.stories.jsx new file mode 100644 index 00000000..559fb298 --- /dev/null +++ b/stories/CustomAlert.stories.jsx @@ -0,0 +1,9 @@ +import CustomAlert from "../src/components/mui/CustomAlert"; + +export default { title: "MUI/Feedback/CustomAlert", component: CustomAlert }; + +export const Info = { args: { severity: "info", message: "Rates refresh nightly." } }; +export const Warning = { args: { severity: "warning", message: "This show closes in 2 days." } }; +export const Error = { args: { severity: "error", message: "Could not reach the payment provider." } }; +export const NoIcon = { args: { severity: "info", message: "Compact variant.", hideIcon: true } }; + diff --git a/stories/CustomTablePagination.stories.jsx b/stories/CustomTablePagination.stories.jsx new file mode 100644 index 00000000..2999d364 --- /dev/null +++ b/stories/CustomTablePagination.stories.jsx @@ -0,0 +1,11 @@ +import CustomTablePagination from "../src/components/mui/table/CustomTablePagination"; + +export default { + title: "MUI/Tables/CustomTablePagination", + component: CustomTablePagination, + argTypes: { onPageChange: { action: "page-change" }, onPerPageChange: { action: "per-page-change" } } +}; + +export const Default = { args: { totalRows: 137, perPage: 10, currentPage: 3 } }; +export const SinglePage = { args: { totalRows: 4, perPage: 10, currentPage: 1 } }; + diff --git a/stories/DndList.stories.jsx b/stories/DndList.stories.jsx new file mode 100644 index 00000000..bbc5cfef --- /dev/null +++ b/stories/DndList.stories.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import DndList from "../src/components/mui/dnd-list"; + +export default { + title: "MUI/Drag and drop/DndList", + component: DndList, + argTypes: { onReorder: { action: "reorder" } }, + parameters: { + docs: { description: { component: "react-beautiful-dnd based (the older list). Prefer DragNDropList for new work." } } + } +}; + +export const Default = { + args: { + droppableId: "story-dnd-list", + items: [ + { id: 1, order: 1, name: "Keynote mention" }, + { id: 2, order: 2, name: "Booth 10x10" }, + { id: 3, order: 3, name: "Lanyard branding" } + ], + renderItem: (item) => {item.name} + } +}; + diff --git a/stories/DownloadBtn.stories.jsx b/stories/DownloadBtn.stories.jsx new file mode 100644 index 00000000..d312029a --- /dev/null +++ b/stories/DownloadBtn.stories.jsx @@ -0,0 +1,6 @@ +import DownloadBtn from "../src/components/mui/DownloadBtn"; + +export default { title: "MUI/Buttons/DownloadBtn", component: DownloadBtn }; + +export const Default = { args: { url: "https://example.com/invoice.pdf" } }; + diff --git a/stories/DragNDropList.stories.jsx b/stories/DragNDropList.stories.jsx new file mode 100644 index 00000000..3b733cea --- /dev/null +++ b/stories/DragNDropList.stories.jsx @@ -0,0 +1,23 @@ +import React from "react"; +import DragNDropList from "../src/components/mui/DragNDropList"; + +export default { + title: "MUI/Drag and drop/DragNDropList", + component: DragNDropList, + argTypes: { onReorder: { action: "reorder" } }, + parameters: { + docs: { description: { component: "dnd-kit based. Optional peer deps: @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities." } } + } +}; + +export const Default = { + args: { + items: [ + { id: 1, order: 1, name: "Keynote mention" }, + { id: 2, order: 2, name: "Booth 10x10" }, + { id: 3, order: 3, name: "Lanyard branding" } + ], + renderItem: (item) => {item.name} + } +}; + diff --git a/stories/Dropdown.stories.jsx b/stories/Dropdown.stories.jsx new file mode 100644 index 00000000..37a4017e --- /dev/null +++ b/stories/Dropdown.stories.jsx @@ -0,0 +1,22 @@ +import Dropdown from "../src/components/mui/Dropdown"; + +export default { + title: "MUI/Inputs/Dropdown", + component: Dropdown, + argTypes: { onChange: { action: "changed" } } +}; + +const options = [ + { value: "diamond", label: "Diamond" }, + { value: "gold", label: "Gold" }, + { value: "silver", label: "Silver" }, + { value: "legacy", label: "Legacy (retired)", disabled: true } +]; + +export const Default = { + args: { id: "tier", label: "Sponsorship tier", options, placeholder: "Select a tier" } +}; + +export const WithValue = { args: { ...Default.args, value: "gold" } }; +export const Multiple = { args: { ...Default.args, multiple: true, value: ["gold", "silver"] } }; + diff --git a/stories/DropdownCheckbox.stories.jsx b/stories/DropdownCheckbox.stories.jsx new file mode 100644 index 00000000..3b2349dc --- /dev/null +++ b/stories/DropdownCheckbox.stories.jsx @@ -0,0 +1,22 @@ +import DropdownCheckbox from "../src/components/mui/dropdown-checkbox"; + +export default { + title: "MUI/Inputs/DropdownCheckbox", + component: DropdownCheckbox, + argTypes: { onChange: { action: "changed" } } +}; + +export const Default = { + args: { + name: "tracks", + label: "Tracks", + allLabel: "All tracks", + value: ["ai"], + options: [ + { value: "ai", label: "AI / ML" }, + { value: "infra", label: "Infrastructure" }, + { value: "security", label: "Security" } + ] + } +}; + diff --git a/stories/EditableTable.stories.jsx b/stories/EditableTable.stories.jsx new file mode 100644 index 00000000..a9a7c663 --- /dev/null +++ b/stories/EditableTable.stories.jsx @@ -0,0 +1,20 @@ +import EditableTable from "../src/components/mui/editable-table/mui-table-editable"; +import { sampleRows } from "./_helpers"; + +export default { + title: "MUI/Tables/EditableTable", + component: EditableTable, + argTypes: { onSort: { action: "sort" }, onPageChange: { action: "page-change" } } +}; + +export const Default = { + args: { + columns: [ + { columnKey: "name", header: "Item", sortable: true }, + { columnKey: "quantity", header: "Qty", align: "right", editable: true }, + { columnKey: "price", header: "Price", align: "right", editable: true } + ], + data: sampleRows + } +}; + diff --git a/stories/ExtraRows.stories.jsx b/stories/ExtraRows.stories.jsx new file mode 100644 index 00000000..f0612650 --- /dev/null +++ b/stories/ExtraRows.stories.jsx @@ -0,0 +1,30 @@ +import React from "react"; +import { + TotalRow, + NotesRow, + FeeRow, + PaymentRow, + RefundRow, + DiscountRow +} from "../src/components/mui/table/extra-rows"; +import { inTable } from "./_helpers"; + +export default { + title: "MUI/Tables/Extra rows", + decorators: [inTable], + parameters: { + docs: { + description: { + component: "Summary rows appended to MuiTable. Each has to render inside a table body." + } + } + } +}; + +export const Total = { render: () => }; +export const Discount = { render: () => }; +export const Fee = { render: () => }; +export const Payment = { render: () => }; +export const Refund = { render: () => }; +export const Notes = { render: () => }; + diff --git a/stories/FormItemTable.stories.jsx b/stories/FormItemTable.stories.jsx new file mode 100644 index 00000000..bcd584aa --- /dev/null +++ b/stories/FormItemTable.stories.jsx @@ -0,0 +1,54 @@ +import React from "react"; +import { FormikProvider, useFormik } from "formik"; +import FormItemTable from "../src/components/mui/FormItemTable"; +import { MOCK_FORM } from "./_form-item-fixture"; + +// FormItemTable reads values/touched/errors off a live formik instance and +// needs discount_type/discount_amount seeded, so it takes the real useFormik +// host rather than the generic withFormik decorator. +const Host = (props) => { + const formik = useFormik({ + initialValues: { discount_type: "AMOUNT", discount_amount: 0 }, + onSubmit: () => {} + }); + return ( + + + + ); +}; + +export default { + title: "MUI/Forms/FormItemTable", + component: FormItemTable, + render: (args) => , + parameters: { + docs: { + description: { + component: + "Data fixture is lifted from the component's own test suite. Rows whose quantity is driven by a Form-level Quantity field default to expanded." + } + } + } +}; + +export const EarlyBird = { + args: { + data: MOCK_FORM.items, + currentApplicableRate: "early_bird", + timeZone: "America/New_York" + } +}; + +export const Standard = { + args: { ...EarlyBird.args, currentApplicableRate: "standard" } +}; + +export const Onsite = { + args: { ...EarlyBird.args, currentApplicableRate: "onsite" } +}; diff --git a/stories/FormikAdditionalInput.stories.jsx b/stories/FormikAdditionalInput.stories.jsx new file mode 100644 index 00000000..bb10e047 --- /dev/null +++ b/stories/FormikAdditionalInput.stories.jsx @@ -0,0 +1,25 @@ +import AdditionalInput from "../src/components/mui/formik-inputs/additional-input/additional-input"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/AdditionalInput", + component: AdditionalInput, + decorators: [ + withFormik({ + meta_fields: [ + { id: 1, name: "shirt_size", type: "ComboBox", values: [{ id: 1, value: "M" }] } + ] + }) + ], + argTypes: { onAdd: { action: "add" }, onDelete: { action: "delete" }, onDeleteValue: { action: "delete-value" } } +}; + +export const Default = { + args: { + baseName: "meta_fields", + itemIdx: 0, + entityId: 1, + item: { id: 1, name: "shirt_size", type: "ComboBox", values: [{ id: 1, value: "M" }] } + } +}; + diff --git a/stories/FormikAdditionalInputList.stories.jsx b/stories/FormikAdditionalInputList.stories.jsx new file mode 100644 index 00000000..9e8e5019 --- /dev/null +++ b/stories/FormikAdditionalInputList.stories.jsx @@ -0,0 +1,19 @@ +import AdditionalInputList from "../src/components/mui/formik-inputs/additional-input/additional-input-list"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/AdditionalInputList", + component: AdditionalInputList, + decorators: [ + withFormik({ + meta_fields: [ + { id: 1, name: "shirt_size", type: "ComboBox", values: [{ id: 1, value: "M" }] }, + { id: 2, name: "dietary", type: "Text", values: [] } + ] + }) + ], + argTypes: { onDelete: { action: "delete" }, onDeleteValue: { action: "delete-value" } } +}; + +export const Default = { args: { name: "meta_fields", entityId: 1 } }; + diff --git a/stories/FormikAddonTypeSelect.stories.jsx b/stories/FormikAddonTypeSelect.stories.jsx new file mode 100644 index 00000000..85653211 --- /dev/null +++ b/stories/FormikAddonTypeSelect.stories.jsx @@ -0,0 +1,11 @@ +import FormikAddonTypeSelect from "../src/components/mui/formik-inputs/mui-formik-addon-type-select"; +import { withFormik, NEEDS_API } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/AddonTypeSelect", + component: FormikAddonTypeSelect, + decorators: [withFormik({ addon: "" })], + parameters: { docs: { description: { component: NEEDS_API } } } +}; + +export const Default = { args: { name: "addon", placeholder: "Select an add-on" } }; diff --git a/stories/FormikAsyncSelect.stories.jsx b/stories/FormikAsyncSelect.stories.jsx new file mode 100644 index 00000000..9032a0ab --- /dev/null +++ b/stories/FormikAsyncSelect.stories.jsx @@ -0,0 +1,24 @@ +import FormikAsyncSelect from "../src/components/mui/formik-inputs/mui-formik-async-select"; +import { withFormik } from "./_helpers"; + +const queryFunction = (term) => + Promise.resolve( + [ + { id: 1, name: "Acme Corp" }, + { id: 2, name: "Globex" }, + { id: 3, name: "Initech" } + ].filter((c) => c.name.toLowerCase().includes((term || "").toLowerCase())) + ); + +export default { + title: "MUI/Formik inputs/AsyncSelect", + component: FormikAsyncSelect, + decorators: [withFormik({ company: null })], + parameters: { + docs: { description: { component: "queryFunction is stubbed here with a local promise; in an app it hits the API." } } + } +}; + +export const Default = { args: { name: "company", queryFunction, placeholder: "Search companies..." } }; +export const Multiple = { args: { ...Default.args, isMulti: true, multiple: true } }; + diff --git a/stories/FormikCheckbox.stories.jsx b/stories/FormikCheckbox.stories.jsx new file mode 100644 index 00000000..05655fc8 --- /dev/null +++ b/stories/FormikCheckbox.stories.jsx @@ -0,0 +1,11 @@ +import FormikCheckbox from "../src/components/mui/formik-inputs/mui-formik-checkbox"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/Checkbox", + component: FormikCheckbox, + decorators: [withFormik({ is_active: true })] +}; + +export const Default = { args: { name: "is_active", label: "Active" } }; + diff --git a/stories/FormikCheckboxGroup.stories.jsx b/stories/FormikCheckboxGroup.stories.jsx new file mode 100644 index 00000000..7b96db29 --- /dev/null +++ b/stories/FormikCheckboxGroup.stories.jsx @@ -0,0 +1,19 @@ +import FormikCheckboxGroup from "../src/components/mui/formik-inputs/mui-formik-checkbox-group"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/CheckboxGroup", + component: FormikCheckboxGroup, + decorators: [withFormik({ tracks: ["ai"] })] +}; + +export const Default = { args: { + name: "tracks", + label: "Tracks", + options: [ + { value: "ai", label: "AI / ML" }, + { value: "infra", label: "Infrastructure" }, + { value: "security", label: "Security" } + ] + } }; + diff --git a/stories/FormikCompanyInput.stories.jsx b/stories/FormikCompanyInput.stories.jsx new file mode 100644 index 00000000..f6277770 --- /dev/null +++ b/stories/FormikCompanyInput.stories.jsx @@ -0,0 +1,12 @@ +import FormikCompanyInput from "../src/components/mui/formik-inputs/company-input-mui"; +import { withFormik, NEEDS_API } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/CompanyInput", + component: FormikCompanyInput, + decorators: [withFormik({ company: null })], + parameters: { docs: { description: { component: NEEDS_API } } } +}; + +export const Default = { args: { id: "company", name: "company", placeholder: "Search companies...", allowCreate: true } }; + diff --git a/stories/FormikDatepicker.stories.jsx b/stories/FormikDatepicker.stories.jsx new file mode 100644 index 00000000..ca66809c --- /dev/null +++ b/stories/FormikDatepicker.stories.jsx @@ -0,0 +1,11 @@ +import FormikDatepicker from "../src/components/mui/formik-inputs/mui-formik-datepicker"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/Datepicker", + component: FormikDatepicker, + decorators: [withFormik({ starts_at: null })] +}; + +export const Default = { args: { name: "starts_at", label: "Starts at" } }; + diff --git a/stories/FormikDiscountField.stories.jsx b/stories/FormikDiscountField.stories.jsx new file mode 100644 index 00000000..2dd98359 --- /dev/null +++ b/stories/FormikDiscountField.stories.jsx @@ -0,0 +1,11 @@ +import FormikDiscountField from "../src/components/mui/formik-inputs/mui-formik-discountfield"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/DiscountField", + component: FormikDiscountField, + decorators: [withFormik({ discount: 10 })] +}; + +export const Default = { args: { name: "discount", label: "Discount", discountType: "percentage" } }; + diff --git a/stories/FormikDropdownCheckbox.stories.jsx b/stories/FormikDropdownCheckbox.stories.jsx new file mode 100644 index 00000000..067c9700 --- /dev/null +++ b/stories/FormikDropdownCheckbox.stories.jsx @@ -0,0 +1,19 @@ +import FormikDropdownCheckbox from "../src/components/mui/formik-inputs/mui-formik-dropdown-checkbox"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/DropdownCheckbox", + component: FormikDropdownCheckbox, + decorators: [withFormik({ tracks: ["ai"] })] +}; + +export const Default = { args: { + name: "tracks", + label: "Tracks", + placeholder: "Select tracks", + options: [ + { value: "ai", label: "AI / ML" }, + { value: "infra", label: "Infrastructure" } + ] + } }; + diff --git a/stories/FormikDropdownRadio.stories.jsx b/stories/FormikDropdownRadio.stories.jsx new file mode 100644 index 00000000..00b10312 --- /dev/null +++ b/stories/FormikDropdownRadio.stories.jsx @@ -0,0 +1,19 @@ +import FormikDropdownRadio from "../src/components/mui/formik-inputs/mui-formik-dropdown-radio"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/DropdownRadio", + component: FormikDropdownRadio, + decorators: [withFormik({ tier: "gold" })] +}; + +export const Default = { args: { + name: "tier", + label: "Tier", + placeholder: "Select a tier", + options: [ + { value: "gold", label: "Gold" }, + { value: "silver", label: "Silver" } + ] + } }; + diff --git a/stories/FormikFileSizeField.stories.jsx b/stories/FormikFileSizeField.stories.jsx new file mode 100644 index 00000000..d8f61e84 --- /dev/null +++ b/stories/FormikFileSizeField.stories.jsx @@ -0,0 +1,11 @@ +import FormikFileSizeField from "../src/components/mui/formik-inputs/mui-formik-file-size-field"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/FileSizeField", + component: FormikFileSizeField, + decorators: [withFormik({ max_size: 5242880 })] +}; + +export const Default = { args: { name: "max_size", label: "Max file size", displayUnit: "MB", valueUnit: "B" } }; + diff --git a/stories/FormikItemPriceTiers.stories.jsx b/stories/FormikItemPriceTiers.stories.jsx new file mode 100644 index 00000000..c3b62497 --- /dev/null +++ b/stories/FormikItemPriceTiers.stories.jsx @@ -0,0 +1,11 @@ +import FormikItemPriceTiers from "../src/components/mui/formik-inputs/item-price-tiers"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/ItemPriceTiers", + component: FormikItemPriceTiers, + decorators: [withFormik({ price_tiers: [{ id: 1, name: "Early bird", price: 7000 }] })] +}; + +export const Default = { args: { readOnly: false } }; + diff --git a/stories/FormikPriceField.stories.jsx b/stories/FormikPriceField.stories.jsx new file mode 100644 index 00000000..43149415 --- /dev/null +++ b/stories/FormikPriceField.stories.jsx @@ -0,0 +1,11 @@ +import FormikPriceField from "../src/components/mui/formik-inputs/mui-formik-pricefield"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/PriceField", + component: FormikPriceField, + decorators: [withFormik({ price: 8000 })] +}; + +export const Default = { args: { name: "price", label: "Price" } }; + diff --git a/stories/FormikQuantityField.stories.jsx b/stories/FormikQuantityField.stories.jsx new file mode 100644 index 00000000..d34e4e1e --- /dev/null +++ b/stories/FormikQuantityField.stories.jsx @@ -0,0 +1,11 @@ +import FormikQuantityField from "../src/components/mui/formik-inputs/mui-formik-quantity-field"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/QuantityField", + component: FormikQuantityField, + decorators: [withFormik({ quantity: 2 })] +}; + +export const Default = { args: { name: "quantity", label: "Quantity", min: 0, max: 99 } }; + diff --git a/stories/FormikRadioGroup.stories.jsx b/stories/FormikRadioGroup.stories.jsx new file mode 100644 index 00000000..c5ab6e46 --- /dev/null +++ b/stories/FormikRadioGroup.stories.jsx @@ -0,0 +1,18 @@ +import FormikRadioGroup from "../src/components/mui/formik-inputs/mui-formik-radio-group"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/RadioGroup", + component: FormikRadioGroup, + decorators: [withFormik({ visibility: "public" })] +}; + +export const Default = { args: { + name: "visibility", + label: "Visibility", + options: [ + { value: "public", label: "Public" }, + { value: "private", label: "Private" } + ] + } }; + diff --git a/stories/FormikSelect.stories.jsx b/stories/FormikSelect.stories.jsx new file mode 100644 index 00000000..5a863b89 --- /dev/null +++ b/stories/FormikSelect.stories.jsx @@ -0,0 +1,25 @@ +import React from "react"; +import { MenuItem } from "@mui/material"; +import FormikSelect from "../src/components/mui/formik-inputs/mui-formik-select"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/Select", + component: FormikSelect, + decorators: [withFormik({ tier: "gold" })] +}; + +export const Default = { + args: { + name: "tier", + label: "Tier", + placeholder: "Select a tier", + isClearable: true, + children: [ + Diamond, + Gold, + Silver + ] + } +}; + diff --git a/stories/FormikSelectGroup.stories.jsx b/stories/FormikSelectGroup.stories.jsx new file mode 100644 index 00000000..ffc994c5 --- /dev/null +++ b/stories/FormikSelectGroup.stories.jsx @@ -0,0 +1,27 @@ +import FormikSelectGroup from "../src/components/mui/formik-inputs/mui-formik-select-group"; +import { withFormik } from "./_helpers"; + +const queryFunction = () => + Promise.resolve([ + { id: 1, name: "Keynote Hall", group_id: 1, group_name: "Main venue" }, + { id: 2, name: "Breakout A", group_id: 1, group_name: "Main venue" }, + { id: 3, name: "Expo Floor", group_id: 2, group_name: "Expo" } + ]); + +export default { + title: "MUI/Formik inputs/SelectGroup", + component: FormikSelectGroup, + decorators: [withFormik({ rooms: [] })] +}; + +export const Default = { + args: { + name: "rooms", + queryFunction, + placeholder: "Select rooms", + showSelectAll: true, + getGroupId: (i) => i.group_id, + getGroupLabel: (i) => i.group_name + } +}; + diff --git a/stories/FormikSelectV2.stories.jsx b/stories/FormikSelectV2.stories.jsx new file mode 100644 index 00000000..7b5be334 --- /dev/null +++ b/stories/FormikSelectV2.stories.jsx @@ -0,0 +1,20 @@ +import FormikSelectV2 from "../src/components/mui/formik-inputs/mui-formik-select-v2"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/Select v2", + component: FormikSelectV2, + decorators: [withFormik({ tier: "gold" })] +}; + +export const Default = { args: { + name: "tier", + label: "Tier", + placeholder: "Select a tier", + options: [ + { value: "diamond", label: "Diamond" }, + { value: "gold", label: "Gold" }, + { value: "silver", label: "Silver" } + ] + } }; + diff --git a/stories/FormikSponsorInput.stories.jsx b/stories/FormikSponsorInput.stories.jsx new file mode 100644 index 00000000..13a9df3e --- /dev/null +++ b/stories/FormikSponsorInput.stories.jsx @@ -0,0 +1,12 @@ +import FormikSponsorInput from "../src/components/mui/formik-inputs/mui-sponsor-input"; +import { withFormik, NEEDS_API } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/SponsorInput", + component: FormikSponsorInput, + decorators: [withFormik({ sponsor: null })], + parameters: { docs: { description: { component: NEEDS_API } } } +}; + +export const Default = { args: { id: "sponsor", name: "sponsor", summitId: 1, placeholder: "Search sponsors..." } }; + diff --git a/stories/FormikSponsorshipInput.stories.jsx b/stories/FormikSponsorshipInput.stories.jsx new file mode 100644 index 00000000..5343ca30 --- /dev/null +++ b/stories/FormikSponsorshipInput.stories.jsx @@ -0,0 +1,12 @@ +import FormikSponsorshipInput from "../src/components/mui/formik-inputs/sponsorship-input-mui"; +import { withFormik, NEEDS_API } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/SponsorshipInput", + component: FormikSponsorshipInput, + decorators: [withFormik({ sponsorship: null })], + parameters: { docs: { description: { component: NEEDS_API } } } +}; + +export const Default = { args: { id: "sponsorship", name: "sponsorship", placeholder: "Search sponsorships..." } }; + diff --git a/stories/FormikSponsorshipSummitSelect.stories.jsx b/stories/FormikSponsorshipSummitSelect.stories.jsx new file mode 100644 index 00000000..e756e7fb --- /dev/null +++ b/stories/FormikSponsorshipSummitSelect.stories.jsx @@ -0,0 +1,12 @@ +import FormikSponsorshipSummitSelect from "../src/components/mui/formik-inputs/sponsorship-summit-select-mui"; +import { withFormik, NEEDS_API } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/SponsorshipSummitSelect", + component: FormikSponsorshipSummitSelect, + decorators: [withFormik({ sponsorship: null })], + parameters: { docs: { description: { component: NEEDS_API } } } +}; + +export const Default = { args: { name: "sponsorship", summitId: 1, placeholder: "Select a sponsorship" } }; + diff --git a/stories/FormikSwitch.stories.jsx b/stories/FormikSwitch.stories.jsx new file mode 100644 index 00000000..1742ef55 --- /dev/null +++ b/stories/FormikSwitch.stories.jsx @@ -0,0 +1,11 @@ +import FormikSwitch from "../src/components/mui/formik-inputs/mui-formik-switch"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/Switch", + component: FormikSwitch, + decorators: [withFormik({ notify: false })] +}; + +export const Default = { args: { name: "notify", label: "Email the sponsor on change" } }; + diff --git a/stories/FormikTextEditor.stories.jsx b/stories/FormikTextEditor.stories.jsx new file mode 100644 index 00000000..5a9c257d --- /dev/null +++ b/stories/FormikTextEditor.stories.jsx @@ -0,0 +1,11 @@ +import FormikTextEditor from "../src/components/mui/formik-inputs/mui-formik-text-editor"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/TextEditor", + component: FormikTextEditor, + decorators: [withFormik({ description: "

Sponsor description

" })] +}; + +export const Default = { args: { name: "description", label: "Description" } }; + diff --git a/stories/FormikTextField.stories.jsx b/stories/FormikTextField.stories.jsx new file mode 100644 index 00000000..62c69b08 --- /dev/null +++ b/stories/FormikTextField.stories.jsx @@ -0,0 +1,11 @@ +import FormikTextField from "../src/components/mui/formik-inputs/mui-formik-textfield"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/TextField", + component: FormikTextField, + decorators: [withFormik({ company_name: "Acme Corp" })] +}; + +export const Default = { args: { name: "company_name", label: "Company name", maxLength: 80 } }; + diff --git a/stories/FormikTimepicker.stories.jsx b/stories/FormikTimepicker.stories.jsx new file mode 100644 index 00000000..717cf394 --- /dev/null +++ b/stories/FormikTimepicker.stories.jsx @@ -0,0 +1,11 @@ +import FormikTimepicker from "../src/components/mui/formik-inputs/mui-formik-timepicker"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/Timepicker", + component: FormikTimepicker, + decorators: [withFormik({ starts_at: null })] +}; + +export const Default = { args: { name: "starts_at", label: "Starts at", timeZone: "America/Chicago" } }; + diff --git a/stories/FormikUpload.stories.jsx b/stories/FormikUpload.stories.jsx new file mode 100644 index 00000000..2f225c24 --- /dev/null +++ b/stories/FormikUpload.stories.jsx @@ -0,0 +1,11 @@ +import FormikUpload from "../src/components/mui/formik-inputs/mui-formik-upload"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Formik inputs/Upload", + component: FormikUpload, + decorators: [withFormik({ images: [] })] +}; + +export const Default = { args: { id: "images", name: "images", maxFiles: 3, allowedExtensions: ["png", "jpg"] } }; + diff --git a/stories/GridFilter.stories.jsx b/stories/GridFilter.stories.jsx new file mode 100644 index 00000000..cebea7a5 --- /dev/null +++ b/stories/GridFilter.stories.jsx @@ -0,0 +1,58 @@ +import React from "react"; +import GridFilter from "../src/components/mui/GridFilter/GridFilter"; +import { OPERATORS } from "../src/components/mui/GridFilter/utils"; + +// criterias lifted from src/components/mui/GridFilter/readme.md +const criterias = [ + { + key: "tracks", + label: "Tracks", + operators: [OPERATORS.IS, OPERATORS.LIKE], + values: { + type: "select", + props: { + options: [ + { value: 1, label: "OpenStack" }, + { value: 2, label: "FnTech" } + ], + multiple: true, + placeholder: "Select Tracks" + } + } + }, + { + key: "selection_status", + label: "Selection Status", + operators: [OPERATORS.IS], + values: { + type: "select", + props: { + options: [ + { value: "accepted", label: "Accepted" }, + { value: "rejected", label: "Rejected" }, + { value: "alternate", label: "Alternate" } + ], + placeholder: "Filter by Selection Status" + } + } + } +]; + +export default { + title: "MUI/GridFilter", + component: GridFilter, + args: { criterias, hideJoinOperators: false }, + argTypes: { onApply: { action: "applied" } }, + parameters: { docs: { description: { component: "See src/components/mui/GridFilter/readme.md for the full criteria contract." } } } +}; + +// each story gets its own id — filter state is keyed by id in a store shared across stories +export const Default = { args: { id: "story-default" } }; + +export const JoinOperatorsHidden = { + args: { id: "story-hidden-join", hideJoinOperators: true } +}; + +export const SingleCriteria = { + args: { id: "story-single", criterias: [criterias[0]] } +}; diff --git a/stories/InfiniteTable.stories.jsx b/stories/InfiniteTable.stories.jsx new file mode 100644 index 00000000..0b98b975 --- /dev/null +++ b/stories/InfiniteTable.stories.jsx @@ -0,0 +1,15 @@ +import InfiniteTable from "../src/components/mui/infinite-table"; +import { sampleRows, sampleColumns } from "./_helpers"; + +export default { + title: "MUI/Tables/InfiniteTable", + component: InfiniteTable, + argTypes: { onSort: { action: "sort" }, loadMoreData: { action: "load-more" }, onRowEdit: { action: "row-edit" } } +}; + +export const Default = { + args: { columns: sampleColumns, data: sampleRows, boxHeight: "300px" } +}; + +export const Empty = { args: { columns: sampleColumns, data: [], boxHeight: "300px" } }; + diff --git a/stories/InfoNote.stories.jsx b/stories/InfoNote.stories.jsx new file mode 100644 index 00000000..0783af41 --- /dev/null +++ b/stories/InfoNote.stories.jsx @@ -0,0 +1,8 @@ +import InfoNote from "../src/components/mui/InfoNote"; + +export default { title: "MUI/Feedback/InfoNote", component: InfoNote }; + +export const Default = { + args: { message: "Prices shown exclude tax. Tax is applied at checkout." } +}; + diff --git a/stories/InlineCard.stories.jsx b/stories/InlineCard.stories.jsx new file mode 100644 index 00000000..9bd15a17 --- /dev/null +++ b/stories/InlineCard.stories.jsx @@ -0,0 +1,15 @@ +import InlineCard from "../src/components/mui/cards/InlineCard"; + +export default { title: "MUI/Cards/InlineCard", component: InlineCard }; + +export const Default = { + args: { + title: "Sponsor", + rows: [ + { label: "Company", value: "Acme Corp" }, + { label: "Tier", value: "Diamond" }, + { label: "Contact", value: "casey@example.com" } + ] + } +}; + diff --git a/stories/ItemSettingsModal.stories.jsx b/stories/ItemSettingsModal.stories.jsx new file mode 100644 index 00000000..888da967 --- /dev/null +++ b/stories/ItemSettingsModal.stories.jsx @@ -0,0 +1,16 @@ +import ItemSettingsModal from "../src/components/mui/ItemSettingsModal"; + +export default { + title: "MUI/Dialogs/ItemSettingsModal", + component: ItemSettingsModal, + argTypes: { onClose: { action: "closed" } } +}; + +export const Default = { + args: { + open: true, + timeZone: "America/Chicago", + item: { id: 1, name: "Booth 10x10", quantity: 1, price: 8000, meta_fields: [] } + } +}; + diff --git a/stories/ListCard.stories.jsx b/stories/ListCard.stories.jsx new file mode 100644 index 00000000..90bfa7d9 --- /dev/null +++ b/stories/ListCard.stories.jsx @@ -0,0 +1,15 @@ +import ListCard from "../src/components/mui/cards/ListCard"; + +export default { title: "MUI/Cards/ListCard", component: ListCard }; + +export const Default = { + args: { + title: "Included add-ons", + rows: [ + { label: "Booth 10x10", value: "1" }, + { label: "Lanyard branding", value: "4" }, + { label: "Keynote mention", value: "1" } + ] + } +}; + diff --git a/stories/LoadingOverlay.stories.jsx b/stories/LoadingOverlay.stories.jsx new file mode 100644 index 00000000..0c80ceee --- /dev/null +++ b/stories/LoadingOverlay.stories.jsx @@ -0,0 +1,16 @@ +import React from "react"; +import LoadingOverlay from "../src/components/mui/LoadingOverlay"; + +export default { title: "MUI/Feedback/LoadingOverlay", component: LoadingOverlay }; + +export const Loading = { + render: (args) => ( +
+ +
+ ), + args: { loading: true } +}; + +export const Idle = { ...Loading, args: { loading: false } }; + diff --git a/stories/MenuButton.stories.jsx b/stories/MenuButton.stories.jsx new file mode 100644 index 00000000..3b3bde64 --- /dev/null +++ b/stories/MenuButton.stories.jsx @@ -0,0 +1,19 @@ +import MenuButton from "../src/components/mui/menu-button"; + +export default { title: "MUI/Buttons/MenuButton", component: MenuButton }; + +export const Default = { + args: { + buttonId: "story-menu-button", + menuId: "story-menu", + children: "Actions", + menuItems: [ + { label: "Edit", onClick: () => {} }, + { label: "Duplicate", onClick: () => {} }, + { label: "Delete", onClick: () => {} } + ] + } +}; + +export const WithBadge = { args: { ...Default.args, hasBadge: true } }; + diff --git a/stories/NavBar.stories.jsx b/stories/NavBar.stories.jsx new file mode 100644 index 00000000..d9b4bc6d --- /dev/null +++ b/stories/NavBar.stories.jsx @@ -0,0 +1,20 @@ +import NavBar from "../src/components/mui/NavBar"; + +export default { + title: "MUI/Layout/NavBar", + component: NavBar, + argTypes: { onClickLogin: { action: "login" }, initLogOut: { action: "logout" } }, + parameters: { layout: "fullscreen" } +}; + +export const LoggedOut = { args: { title: "Sponsor Portal", isLoggedUser: false } }; + +export const LoggedIn = { + args: { + title: "Sponsor Portal", + isLoggedUser: true, + profileName: "Casey Locker", + profileEmail: "casey@example.com" + } +}; + diff --git a/stories/NotesModal.stories.jsx b/stories/NotesModal.stories.jsx new file mode 100644 index 00000000..8c2b0ae0 --- /dev/null +++ b/stories/NotesModal.stories.jsx @@ -0,0 +1,20 @@ +import NotesModal from "../src/components/mui/NotesModal"; +import { withFormik } from "./_helpers"; + +export default { + title: "MUI/Dialogs/NotesModal", + component: NotesModal, + argTypes: { onClose: { action: "closed" } }, + decorators: [withFormik({ notes: "" })] +}; + +export const Default = { + args: { + open: true, + id: 42, + title: "Internal notes", + label: "Note", + placeholder: "Add a note for the ops team..." + } +}; + diff --git a/stories/OrderSummary.stories.jsx b/stories/OrderSummary.stories.jsx new file mode 100644 index 00000000..bf005f87 --- /dev/null +++ b/stories/OrderSummary.stories.jsx @@ -0,0 +1,13 @@ +import OrderSummary from "../src/components/mui/OrderSummary"; + +export default { title: "MUI/Commerce/OrderSummary", component: OrderSummary }; + +export const Default = { + args: { + amount: 3350000, + dueDate: "2026-09-30", + toName: "Acme Corp", + fromName: "Foxtrot November Events" + } +}; + diff --git a/stories/RoundButton.stories.jsx b/stories/RoundButton.stories.jsx new file mode 100644 index 00000000..225e7c9a --- /dev/null +++ b/stories/RoundButton.stories.jsx @@ -0,0 +1,12 @@ +import RoundButton from "../src/components/mui/RoundButton"; + +export default { + title: "MUI/Buttons/RoundButton", + component: RoundButton, + argTypes: { onClick: { action: "clicked" } } +}; + +export const Default = { args: { children: "Save changes" } }; +export const Outlined = { args: { children: "Cancel", variant: "outlined" } }; +export const Disabled = { args: { children: "Save changes", disabled: true } }; + diff --git a/stories/SearchInput.stories.jsx b/stories/SearchInput.stories.jsx new file mode 100644 index 00000000..fe3480d8 --- /dev/null +++ b/stories/SearchInput.stories.jsx @@ -0,0 +1,12 @@ +import SearchInput from "../src/components/mui/search-input"; + +export default { + title: "MUI/Inputs/SearchInput", + component: SearchInput, + argTypes: { onSearch: { action: "search" } } +}; + +export const Default = { args: { placeholder: "Search sponsors..." } }; +export const WithTerm = { args: { term: "diamond", placeholder: "Search sponsors..." } }; +export const Debounced = { args: { placeholder: "Type to search...", debounced: true } }; + diff --git a/stories/ShowConfirmDialog.stories.jsx b/stories/ShowConfirmDialog.stories.jsx new file mode 100644 index 00000000..b4a7d552 --- /dev/null +++ b/stories/ShowConfirmDialog.stories.jsx @@ -0,0 +1,37 @@ +import React from "react"; +import { Button } from "@mui/material"; +import showConfirmDialog, { GlobalConfirmDialog } from "../src/components/mui/showConfirmDialog"; + +export default { + title: "MUI/Dialogs/ShowConfirmDialog", + component: GlobalConfirmDialog, + parameters: { + docs: { + description: { + component: + "Imperative API. Mount GlobalConfirmDialog once at the app root, then call showConfirmDialog() from anywhere; it resolves with the user's choice." + } + } + } +}; + +export const Imperative = { + render: () => ( + <> + + + + ) +}; + diff --git a/stories/SnackbarNotification.stories.jsx b/stories/SnackbarNotification.stories.jsx new file mode 100644 index 00000000..fdf4b35f --- /dev/null +++ b/stories/SnackbarNotification.stories.jsx @@ -0,0 +1,65 @@ +import React from "react"; +import { useDispatch } from "react-redux"; +import { Button } from "@mui/material"; +import SnackbarNotification, { useSnackbarMessage } from "../src/components/mui/SnackbarNotification"; +import { setSnackbarMessage } from "../src/utils/actions"; + +export default { + title: "MUI/SnackbarNotification", + component: SnackbarNotification, + parameters: { + docs: { + description: { + component: + "Wrapper component — renders nothing until a message arrives. Two trigger paths: the useSnackbarMessage hook, or a snackbarMessage in the redux base reducer." + } + } + } +}; + +// path 1: the useSnackbarMessage hook, for on-demand messaging +const HookTrigger = () => { + const { successMessage, errorMessage } = useSnackbarMessage(); + return ( + <> + + + + ); +}; + +export const ViaHook = { + render: () => ( + + + + ) +}; + +// path 2: baseState.snackbarMessage, set by snackbarSuccessHandler/snackbarErrorHandler +const DispatchTrigger = () => { + const dispatch = useDispatch(); + return ( + + ); +}; + +export const ViaReduxState = { + render: () => ( + + + + ) +}; diff --git a/stories/SortableTable.stories.jsx b/stories/SortableTable.stories.jsx new file mode 100644 index 00000000..51fa01fa --- /dev/null +++ b/stories/SortableTable.stories.jsx @@ -0,0 +1,24 @@ +import SortableTable from "../src/components/mui/sortable-table/mui-table-sortable"; +import { sampleRows, sampleColumns } from "./_helpers"; + +export default { + title: "MUI/Tables/SortableTable", + component: SortableTable, + argTypes: { + onSort: { action: "sort" }, + onReorder: { action: "reorder" }, + onEdit: { action: "edit" }, + onDelete: { action: "delete" } + }, + parameters: { + docs: { description: { component: "Drag-to-reorder table. Pulls react-beautiful-dnd as an optional peer." } } + } +}; + +export const Default = { + args: { + columns: sampleColumns, + data: sampleRows.map((r, i) => ({ ...r, order: i + 1 })) + } +}; + diff --git a/stories/SponsorAddonSelect.stories.jsx b/stories/SponsorAddonSelect.stories.jsx new file mode 100644 index 00000000..9f94edc7 --- /dev/null +++ b/stories/SponsorAddonSelect.stories.jsx @@ -0,0 +1,18 @@ +import SponsorAddonSelect from "../src/components/mui/sponsor-addon-select"; +import { NEEDS_API } from "./_helpers"; + +export default { + title: "MUI/API-backed/SponsorAddonSelect", + component: SponsorAddonSelect, + argTypes: { onChange: { action: "changed" } }, + parameters: { docs: { description: { component: NEEDS_API } } } +}; + +export const Default = { + args: { summitId: 1, sponsor: { + id: 7, + company: { name: "Acme Corp" }, + sponsorships: [{ id: 1, name: "Diamond" }] + }, placeholder: "Select an add-on" } +}; + diff --git a/stories/SponsorOrderGrid.stories.jsx b/stories/SponsorOrderGrid.stories.jsx new file mode 100644 index 00000000..f74f6d17 --- /dev/null +++ b/stories/SponsorOrderGrid.stories.jsx @@ -0,0 +1,44 @@ +import SponsorOrderGrid from "../src/components/mui/SponsorOrderGrid"; + +const order = { + id: 9001, + status: "Paid", + currency: "USD", + currency_symbol: "$", + sub_total: 3300000, + taxes_amount: 50000, + discount_amount: 0, + total: 3350000, + purchased_amount: 3350000, + refunded_amount: 0, + lines: [ + { + id: 1, + name: "Diamond Sponsorship", + description: "Top tier package", + qty: 1, + subtotal: 2500000, + total: 2500000, + early_bird_discount: 0 + }, + { + id: 2, + name: "Booth 10x10", + description: "Expo floor booth", + qty: 1, + subtotal: 800000, + total: 800000, + early_bird_discount: 0 + } + ] +}; + +export default { + title: "MUI/Commerce/SponsorOrderGrid", + component: SponsorOrderGrid, + argTypes: { onCancelForm: { action: "cancel" }, onUndoCancelForm: { action: "undo-cancel" } } +}; + +export const Default = { args: { order } }; +export const WithReconciliation = { args: { order, withReconciliation: true } }; + diff --git a/stories/StatusChip.stories.jsx b/stories/StatusChip.stories.jsx new file mode 100644 index 00000000..318f5ce0 --- /dev/null +++ b/stories/StatusChip.stories.jsx @@ -0,0 +1,9 @@ +import StatusChip from "../src/components/mui/StatusChip"; + +export default { title: "MUI/Data display/StatusChip", component: StatusChip }; + +export const Paid = { args: { status: "Paid" } }; +export const Pending = { args: { status: "Pending" } }; +export const Cancelled = { args: { status: "Cancelled" } }; +export const Refunded = { args: { status: "Refunded" } }; + diff --git a/stories/StripePayment.stories.jsx b/stories/StripePayment.stories.jsx new file mode 100644 index 00000000..19bdd489 --- /dev/null +++ b/stories/StripePayment.stories.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Alert } from "@mui/material"; +import StripePayment from "../src/components/mui/StripePayment"; + +export default { + title: "MUI/Commerce/StripePayment", + component: StripePayment, + argTypes: { onPaymentSuccess: { action: "success" }, onPaymentError: { action: "error" } }, + parameters: { + docs: { + description: { + component: + "The only component here that cannot render standalone: Stripe Elements needs a real publishable key plus a client_secret from a server-created payment intent. Supply both via args to see the live card form." + } + } + } +}; + +export const NeedsStripeCredentials = { + render: (args) => ( + <> + + Renders blank without a real Stripe publishable key and client_secret. Set + both in the Controls panel to load the live card form. + + + + ), + args: { + stripeFormTitle: "Card details", + paymentProfile: { publishable_key: "" }, + paymentIntent: { client_secret: "" }, + paymentOptions: { currency: "USD", amount: 3350000 } + } +}; diff --git a/stories/SummitsDropdown.stories.jsx b/stories/SummitsDropdown.stories.jsx new file mode 100644 index 00000000..23dd04ac --- /dev/null +++ b/stories/SummitsDropdown.stories.jsx @@ -0,0 +1,28 @@ +import SummitsDropdown from "../src/components/mui/summits-dropdown"; + +export default { + title: "MUI/API-backed/SummitsDropdown", + component: SummitsDropdown, + argTypes: { onChange: { action: "changed" } }, + parameters: { + docs: { + description: { + component: + "`summits` has no default and is read during the first render, so it must always be passed even though propTypes does not mark it required — omitting it throws before the useEffect fetch ever runs. Pass [] to opt into the fetch path, or a populated array to skip it." + } + } + } +}; + +// [] opts into the fetch path; no API is reachable from Storybook, so it stays empty +export const FetchesOnMount = { args: { summits: [], label: "Search by show" } }; + +export const WithSummitsProvided = { + args: { + label: "Search by show", + summits: [ + { id: 1, name: "FN Summit 2026" }, + { id: 2, name: "FN Summit 2025" } + ] + } +}; diff --git a/stories/Table.stories.jsx b/stories/Table.stories.jsx new file mode 100644 index 00000000..6cd7b495 --- /dev/null +++ b/stories/Table.stories.jsx @@ -0,0 +1,37 @@ +import Table from "../src/components/mui/table/mui-table"; +import { sampleRows, sampleColumns } from "./_helpers"; + +export default { + title: "MUI/Tables/Table", + component: Table, + argTypes: { + onSort: { action: "sort" }, + onEdit: { action: "edit" }, + onDelete: { action: "delete" }, + onPageChange: { action: "page-change" }, + onPerPageChange: { action: "per-page-change" } + } +}; + +export const Default = { args: { columns: sampleColumns, data: sampleRows } }; + +export const Paginated = { + args: { + columns: sampleColumns, + data: sampleRows, + totalRows: 42, + perPage: 3, + currentPage: 1 + } +}; + +export const Sorted = { + args: { + columns: sampleColumns, + data: sampleRows, + options: { sortCol: "name", sortDir: 1 } + } +}; + +export const Empty = { args: { columns: sampleColumns, data: [] } }; + diff --git a/stories/TableCard.stories.jsx b/stories/TableCard.stories.jsx new file mode 100644 index 00000000..e39e057d --- /dev/null +++ b/stories/TableCard.stories.jsx @@ -0,0 +1,19 @@ +import TableCard from "../src/components/mui/cards/TableCard"; + +export default { title: "MUI/Cards/TableCard", component: TableCard }; + +export const Default = { + args: { + title: "Order lines", + columns: [ + { columnKey: "name", header: "Item" }, + { columnKey: "quantity", header: "Qty", align: "right" }, + { columnKey: "price", header: "Price", align: "right" } + ], + rows: [ + { id: 1, name: "Diamond Sponsorship", quantity: 2, price: "$25,000" }, + { id: 2, name: "Booth 10x10", quantity: 1, price: "$8,000" } + ] + } +}; + diff --git a/stories/ToggleButtons.stories.jsx b/stories/ToggleButtons.stories.jsx new file mode 100644 index 00000000..2913efc5 --- /dev/null +++ b/stories/ToggleButtons.stories.jsx @@ -0,0 +1,16 @@ +import ToggleButtons from "../src/components/mui/ToggleButtons"; + +export default { + title: "MUI/Inputs/ToggleButtons", + component: ToggleButtons, + argTypes: { onChange: { action: "changed" } } +}; + +export const Default = { + args: { options: ["ALL", "ANY"], value: "ALL" } +}; + +export const Secondary = { + args: { options: ["Day", "Week", "Month"], value: "Week", color: "secondary" } +}; + diff --git a/stories/UploadBtn.stories.jsx b/stories/UploadBtn.stories.jsx new file mode 100644 index 00000000..e88cc8e1 --- /dev/null +++ b/stories/UploadBtn.stories.jsx @@ -0,0 +1,11 @@ +import UploadBtn from "../src/components/mui/UploadBtn"; + +export default { + title: "MUI/Buttons/UploadBtn", + component: UploadBtn, + argTypes: { onClick: { action: "clicked" } } +}; + +export const Default = { args: { disabled: false } }; +export const Disabled = { args: { disabled: true } }; + diff --git a/stories/UploadDialog.stories.jsx b/stories/UploadDialog.stories.jsx new file mode 100644 index 00000000..57cdcd4c --- /dev/null +++ b/stories/UploadDialog.stories.jsx @@ -0,0 +1,23 @@ +import UploadDialog from "../src/components/mui/UploadDialog"; + +export default { + title: "MUI/Dialogs/UploadDialog", + component: UploadDialog, + argTypes: { onClose: { action: "closed" }, onUpload: { action: "uploaded" } } +}; + +export const Default = { + args: { + open: true, + name: "logo", + value: [], + maxFiles: 1, + fileMeta: { + name: "Sponsor logo", + description: "PNG or SVG, 5 MB max.", + max_file_size: 5242880, + allowed_extensions: "png,jpg,svg" + } + } +}; + diff --git a/stories/_form-item-fixture.js b/stories/_form-item-fixture.js new file mode 100644 index 00000000..a7e28b43 --- /dev/null +++ b/stories/_form-item-fixture.js @@ -0,0 +1,102 @@ +// lifted verbatim from src/components/mui/FormItemTable/__tests__/FormItemTable.test.js +export const MOCK_FORM = { + items: [ + { + form_item_id: 1, + code: "INST", + name: "Installation", + rates: { + early_bird: 15000, + standard: 18800, + onsite: 22400 + }, + meta_fields: [ + { + type_id: 1, + class_field: "Form", + name: "Qty of People", + type: "Quantity", + minimum_quantity: 1, + maximum_quantity: 4 + }, + { + type_id: 2, + class_field: "Form", + name: "Hour x Person", + type: "Quantity", + minimum_quantity: 1, + maximum_quantity: 8 + }, + { + type_id: 3, + class_field: "Form", + name: "Arrival Time", + type: "Time" + }, + { + type_id: 4, + class_field: "Item", + name: "Special Instructions", + type: "Text", + is_required: true + } + ] + }, + { + form_item_id: 2, + code: "DISMANTLE", + name: "Dismantle", + rates: { + early_bird: 15000, + standard: 18800, + onsite: 22400 + }, + meta_fields: [ + { + type_id: 1, + class_field: "Form", + name: "Qty of People", + type: "Quantity", + minimum_quantity: 1, + maximum_quantity: 4 + }, + { + type_id: 2, + class_field: "Form", + name: "Hour x Person", + type: "Quantity", + minimum_quantity: 1, + maximum_quantity: 8 + }, + { + type_id: 3, + class_field: "Form", + name: "Arrival Time", + type: "Time" + } + ] + }, + { + form_item_id: 3, + code: "INST-MAN", + name: "Installation Manpower", + rates: { + early_bird: 15000, + standard: 18800, + onsite: 22400 + }, + meta_fields: [] + }, + { + form_item_id: 4, + code: "DIS-MAN", + name: "Dismantle Manpower", + rates: { + early_bird: 15000, + standard: 18800, + onsite: 22400 + }, + meta_fields: [] + } + ] +}; diff --git a/stories/_helpers.jsx b/stories/_helpers.jsx new file mode 100644 index 00000000..50a5755f --- /dev/null +++ b/stories/_helpers.jsx @@ -0,0 +1,41 @@ +import React from "react"; +import { Formik, Form } from "formik"; + +/** + * Formik-bound inputs read from context, so every formik-inputs story needs a + * host form. Usage: decorators: [withFormik({ myField: "value" })] + */ +export const withFormik = + (initialValues = {}, formProps = {}) => + (Story) => ( + {}} {...formProps}> +
+ + +
+ ); + +/** Table rows have to live inside a table to render at all. */ +export const inTable = (Story) => ( + + + + +
+); + +/** Components that fetch on mount have no backend here — see docs note on each. */ +export const NEEDS_API = + "Fetches on mount via utils/query-actions. With no API reachable from Storybook it renders its empty state; wire msw to populate it."; + +export const sampleRows = [ + { id: 1, name: "Diamond Sponsorship", quantity: 2, price: 25000 }, + { id: 2, name: "Booth 10x10", quantity: 1, price: 8000 }, + { id: 3, name: "Lanyard Branding", quantity: 4, price: 1500 } +]; + +export const sampleColumns = [ + { columnKey: "name", header: "Item", sortable: true }, + { columnKey: "quantity", header: "Qty", align: "right" }, + { columnKey: "price", header: "Price", align: "right" } +]; diff --git a/stories/core/ActionDropdown.stories.jsx b/stories/core/ActionDropdown.stories.jsx new file mode 100644 index 00000000..2bc7fc9d --- /dev/null +++ b/stories/core/ActionDropdown.stories.jsx @@ -0,0 +1,16 @@ +import ActionDropdown from "../../src/components/inputs/action-dropdown"; + +export default { + title: "Core/Inputs/ActionDropdown", + component: ActionDropdown, + argTypes: { onClick: { action: "action" } } +}; + +const options = [ + { value: "export_csv", label: "Export CSV" }, + { value: "send_email", label: "Send Email" }, + { value: "archive", label: "Archive" } +]; + +export const Default = { args: { options, actionLabel: "Go", placeholder: "Bulk action..." } }; +export const Small = { args: { ...Default.args, small: true } }; diff --git a/stories/core/AjaxLoader.stories.jsx b/stories/core/AjaxLoader.stories.jsx new file mode 100644 index 00000000..5527fc71 --- /dev/null +++ b/stories/core/AjaxLoader.stories.jsx @@ -0,0 +1,17 @@ +import AjaxLoader from "../../src/components/ajaxloader"; + +export default { + title: "Core/Feedback/AjaxLoader", + component: AjaxLoader, + // fixed-position overlay; relative + a positioned box keeps it inside the story frame + decorators: [ + (Story) => ( +
+ +
+ ) + ] +}; + +export const Default = { args: { show: true, relative: true, size: 40 } }; +export const Hidden = { args: { show: false, relative: true, size: 40 } }; diff --git a/stories/core/AsyncEntityInputs.stories.jsx b/stories/core/AsyncEntityInputs.stories.jsx new file mode 100644 index 00000000..fd1fb311 --- /dev/null +++ b/stories/core/AsyncEntityInputs.stories.jsx @@ -0,0 +1,59 @@ +import CompanyInput from "../../src/components/inputs/company-input"; +import PromocodeInput from "../../src/components/inputs/promocode-input"; +import SponsorInput from "../../src/components/inputs/sponsor-input"; +import OrganizationInput from "../../src/components/inputs/organization-input"; +import EventInput from "../../src/components/inputs/event-input"; +import GroupInput from "../../src/components/inputs/group-input"; +import MemberInput from "../../src/components/inputs/member-input"; +import AttendeeInput from "../../src/components/inputs/attendee-input"; +import SummitInput from "../../src/components/inputs/summit-input"; +import SpeakerInput from "../../src/components/inputs/speaker-input"; +import OperatorInput from "../../src/components/inputs/operator-input"; +import TagInput from "../../src/components/inputs/tag-input"; +import AccessLevelsInput from "../../src/components/inputs/access-levels-input"; +import RegistrationCompanyInput from "../../src/components/inputs/registration-company-input"; +import TicketTypesInput from "../../src/components/inputs/ticket-types-input.js"; +import SponsoredProjectInput from "../../src/components/inputs/sponsored-project-input.js"; +import CountryInput from "../../src/components/inputs/country-input"; +import LanguageInput from "../../src/components/inputs/language-input"; +import CountryDropdown from "../../src/components/inputs/country-dropdown"; +import { NEEDS_API } from "../_helpers"; + +/** + * Every input here is an async select over utils/query-actions — one story each + * would be 19 near-identical files, so they share this one. NEEDS_API applies + * to all of them: they render and open, but option lists stay empty offline. + */ +export default { + title: "Core/Inputs/AsyncEntityInputs", + parameters: { docs: { description: { component: NEEDS_API } } }, + argTypes: { onChange: { action: "changed" } } +}; + +const base = { value: null, placeholder: "Type to search..." }; + +export const Company = { render: (args) => }; +export const Promocode = { render: (args) => }; +export const Sponsor = { render: (args) =>