diff --git a/src/components/CodeEditorHelper/index.tsx b/src/components/CodeEditorHelper.tsx similarity index 86% rename from src/components/CodeEditorHelper/index.tsx rename to src/components/CodeEditorHelper.tsx index ec8aff39..553f2994 100644 --- a/src/components/CodeEditorHelper/index.tsx +++ b/src/components/CodeEditorHelper.tsx @@ -1,5 +1,6 @@ -import { Stack, Typography, Grid, Tooltip, Chip, Button } from "@mui/material"; +import { Stack, Typography, Grid, Tooltip, Button } from "@mui/material"; import InlineOpenInNewIcon from "components/InlineOpenInNewIcon"; + export interface ICodeEditorHelperProps { docLink: string; additionalVariables?: { @@ -42,24 +43,19 @@ export default function CodeEditorHelper({ return ( - + You can access: - + {availableVariables.concat(additionalVariables ?? []).map((v) => ( - + {v.key} ))} diff --git a/src/components/Table/TableHeader/Extensions/ExtensionList.tsx b/src/components/Table/TableHeader/Extensions/ExtensionList.tsx index e6e5b2c8..267211d0 100644 --- a/src/components/Table/TableHeader/Extensions/ExtensionList.tsx +++ b/src/components/Table/TableHeader/Extensions/ExtensionList.tsx @@ -18,16 +18,22 @@ import { } from "@mui/material"; import AddIcon from "@mui/icons-material/Add"; import ExtensionIcon from "assets/icons/Extension"; -import DuplicateIcon from "@mui/icons-material/ContentCopy"; -import EditIcon from "@mui/icons-material/Edit"; -import DeleteIcon from "@mui/icons-material/DeleteForever"; +import DuplicateIcon from "assets/icons/Copy"; +import EditIcon from "@mui/icons-material/EditOutlined"; +import DeleteIcon from "@mui/icons-material/DeleteOutlined"; import EmptyState from "components/EmptyState"; -import { extensionTypes, IExtension, IExtensionType } from "./utils"; +import { + extensionTypes, + extensionNames, + IExtension, + ExtensionType, +} from "./utils"; +import { DATE_TIME_FORMAT } from "constants/dates"; export interface IExtensionListProps { extensions: IExtension[]; - handleAddExtension: (type: IExtensionType) => void; + handleAddExtension: (type: ExtensionType) => void; handleUpdateActive: (index: number, active: boolean) => void; handleDuplicate: (index: number) => void; handleEdit: (index: number) => void; @@ -53,7 +59,7 @@ export default function ExtensionList({ setAnchorEl(addButtonRef.current); }; - const handleChooseAddType = (type: IExtensionType) => { + const handleChooseAddType = (type: ExtensionType) => { handleClose(); handleAddExtension(type); }; @@ -95,12 +101,8 @@ export default function ExtensionList({ transformOrigin={{ vertical: "top", horizontal: "right" }} > {extensionTypes.map((type) => ( - { - handleChooseAddType(type); - }} - > - {type} + handleChooseAddType(type)}> + {extensionNames[type]} ))} @@ -132,7 +134,14 @@ export default function ExtensionList({ children={ } secondaryAction={ @@ -182,13 +191,15 @@ export default function ExtensionList({ - Last updated by {extensionObject.lastEditor.displayName} + Last updated
- on{" "} - {format(extensionObject.lastEditor.lastUpdate, "PPPP")} + by {extensionObject.lastEditor.displayName}
at{" "} - {format(extensionObject.lastEditor.lastUpdate, "pppp")} + {format( + extensionObject.lastEditor.lastUpdate, + DATE_TIME_FORMAT + )} } > diff --git a/src/components/Table/TableHeader/Extensions/ExtensionModal.tsx b/src/components/Table/TableHeader/Extensions/ExtensionModal.tsx index b02f76bf..e992bcf1 100644 --- a/src/components/Table/TableHeader/Extensions/ExtensionModal.tsx +++ b/src/components/Table/TableHeader/Extensions/ExtensionModal.tsx @@ -1,73 +1,42 @@ import { useState } from "react"; import _isEqual from "lodash/isEqual"; +import _upperFirst from "lodash/upperFirst"; import useStateRef from "react-usestateref"; import { - styled, - Button, - Checkbox, - Divider, - FormControl, - FormControlLabel, - FormGroup, - FormLabel, Grid, - IconButton, - Switch, - Stack, - Tab, TextField, + FormControlLabel, + Switch, + Stepper, + Step, + StepButton, + StepContent, Typography, + Link, } from "@mui/material"; -import TabContext from "@mui/lab/TabContext"; -import TabList from "@mui/lab/TabList"; -import TabPanel from "@mui/lab/TabPanel"; -import AddIcon from "@mui/icons-material/AddBox"; -import DeleteIcon from "@mui/icons-material/RemoveCircle"; +import ExpandIcon from "@mui/icons-material/KeyboardArrowDown"; +import InlineOpenInNewIcon from "components/InlineOpenInNewIcon"; import Modal, { IModalProps } from "components/Modal"; -import CodeEditor from "../../editors/CodeEditor"; -import CodeEditorHelper from "components/CodeEditorHelper"; +import Step1Triggers from "./Step1Triggers"; +import Step2RequiredFields from "./Step2RequiredFields"; +import Step3Conditions from "./Step3Conditions"; +import Step4Body from "./Step4Body"; import { useConfirmation } from "components/ConfirmationDialog"; -import { useProjectContext } from "contexts/ProjectContext"; -import { IExtension, triggerTypes } from "./utils"; +import { extensionNames, IExtension } from "./utils"; import { WIKI_LINKS } from "constants/externalLinks"; -const additionalVariables = [ - { - key: "change", - description: - "you can pass in field name to change.before.get() or change.after.get() to get changes", - }, - { - key: "triggerType", - description: "triggerType indicates the type of the extension invocation", - }, - { - key: "fieldTypes", - description: - "fieldTypes is a map of all fields and its corresponding field type", - }, - { - key: "extensionConfig", - description: "the configuration object of this extension", - }, -]; - -const StyledTabPanel = styled(TabPanel)({ - flexGrow: 1, - - overflowY: "auto", - margin: "0 calc(var(--dialog-spacing) * -1) 0 !important", - padding: "var(--dialog-spacing) var(--dialog-spacing) 0", - - "&[hidden]": { display: "none" }, - - display: "flex", - flexDirection: "column", -}); +type StepValidation = Record<"condition" | "extensionBody", boolean>; +export interface IExtensionModalStepProps { + extensionObject: IExtension; + setExtensionObject: React.Dispatch>; + validation: StepValidation; + setValidation: React.Dispatch>; + validationRef: React.RefObject; +} export interface IExtensionModalProps { handleClose: IModalProps["onClose"]; @@ -85,46 +54,45 @@ export default function ExtensionModal({ extensionObject: initialObject, }: IExtensionModalProps) { const { requestConfirmation } = useConfirmation(); + const [extensionObject, setExtensionObject] = useState(initialObject); - const [tab, setTab] = useState("triggersRequirements"); - const [validation, setValidation, validationRef] = useStateRef({ - condition: true, - extensionBody: true, - }); - const [, setConditionEditorActive, conditionEditorActiveRef] = - useStateRef(false); - const [, setBodyEditorActive, bodyEditorActiveRef] = useStateRef(false); - const { tableState } = useProjectContext(); - const columns = Object.keys(tableState?.columns ?? {}); + + const [activeStep, setActiveStep] = useState(0); + + const [validation, setValidation, validationRef] = + useStateRef({ condition: true, extensionBody: true }); + const edited = !_isEqual(initialObject, extensionObject); const handleAddOrUpdate = () => { - switch (mode) { - case "add": - handleAdd(extensionObject); - return; - case "update": - handleUpdate(extensionObject); - return; - } + if (mode === "add") handleAdd(extensionObject); + if (mode === "update") handleUpdate(extensionObject); + }; + + const stepProps = { + extensionObject, + setExtensionObject, + validation, + setValidation, + validationRef, }; return ( - + - + - setExtensionObject({ + setExtensionObject((extensionObject) => ({ ...extensionObject, active: e.target.checked, - }) + })) } size="medium" /> @@ -174,337 +142,102 @@ export default function ExtensionModal({ }activated`} /> - - - - - - setTab(val)} - variant="fullWidth" - centered - style={{ - marginTop: 0, - marginLeft: "calc(var(--dialog-spacing) * -1)", - marginRight: "calc(var(--dialog-spacing) * -1)", - }} - > - - - - + - - - - - Triggers - - - Select a trigger that runs your extension code. Selected - actions on any cells will trigger the extension. - - - - {triggerTypes.map((trigger) => ( - { - if ( - extensionObject.triggers.includes(trigger) - ) { - setExtensionObject({ - ...extensionObject, - triggers: extensionObject.triggers.filter( - (t) => t !== trigger - ), - }); - } else { - setExtensionObject({ - ...extensionObject, - triggers: [ - ...extensionObject.triggers, - trigger, - ], - }); - } - }} - /> - } - /> - ))} - - - - - - - - Required fields (optional) - - - Optionally, select the fields that are required for the - extension to be triggered for a row. - - - *": { flexShrink: 0 }, - }} - > - {columns.sort().map((field) => ( - { - if ( - extensionObject.requiredFields.includes(field) - ) { - setExtensionObject({ - ...extensionObject, - requiredFields: - extensionObject.requiredFields.filter( - (t) => t !== field - ), - }); - } else { - setExtensionObject({ - ...extensionObject, - requiredFields: [ - ...extensionObject.requiredFields, - field, - ], - }); - } - }} - /> - } - /> - ))} - - {extensionObject.requiredFields.map((trigger, index) => { - const isTableColumn = columns.includes(trigger); - if (isTableColumn) { - return null; - } - - return ( - - { - setExtensionObject({ - ...extensionObject, - requiredFields: - extensionObject.requiredFields.filter( - (t) => t !== trigger - ), - }); - }} - > - - - { - setExtensionObject({ - ...extensionObject, - requiredFields: - extensionObject.requiredFields.map( - (value, i) => - i === index ? event.target.value : value - ), - }); - }} - /> - - ); - })} - - - - - - - - - -
- - Conditions + "& .MuiStepLabel-root": { width: "100%" }, + "& .MuiStepLabel-label": { + display: "flex", + width: "100%", + typography: "subtitle2", + "&.Mui-active": { typography: "subtitle2" }, + }, + "& .MuiStepLabel-label svg": { + display: "block", + marginLeft: "auto", + my: ((24 - 18) / 2 / 8) * -1, + transition: (theme) => theme.transitions.create("transform"), + }, + "& .Mui-active svg": { + transform: "rotate(180deg)", + }, + }} + > + + setActiveStep(0)}> + Trigger events + + + + + Select which events trigger this extension + + + - { - setExtensionObject({ - ...extensionObject, - conditions: newValue, - }); - }} - onValideStatusUpdate={({ isValid }) => { - if (!conditionEditorActiveRef.current) { - return; - } - setValidation({ - ...validationRef.current, - condition: isValid, - }); - console.log(validationRef.current); - }} - diagnosticsOptions={{ - noSemanticValidation: false, - noSyntaxValidation: false, - noSuggestionDiagnostics: true, - }} - onMount={() => { - setConditionEditorActive(true); - }} - onUnmount={() => { - setConditionEditorActive(false); - }} - /> -
- - - - -
- - Extension body + + setActiveStep(1)}> + Required fields (optional) + + + + + Optionally, select fields that must have a value set for the + extension to be triggered for that row + + + - { - setExtensionObject({ - ...extensionObject, - extensionBody: newValue, - }); - }} - onValidStatusUpdate={({ isValid }) => { - if (!bodyEditorActiveRef.current) { - return; + + setActiveStep(2)}> + Trigger conditions (optional) + + + + + Optionally, write a function that determines if the extension + should be triggered for a given row. Leave the function to + always return true if you do not want to write + additional logic. + + + + + + + setActiveStep(3)}> + Extension body + + + + + Write the extension body function. Make sure you have set all + the required parameters.{" "} + { - setBodyEditorActive(true); - }} - onUnmount={() => { - setBodyEditorActive(false); - }} - /> -
- -
-
+ target="_blank" + rel="noopener noreferrer" + > + Docs + + +
+ + + + } actions={{ diff --git a/src/components/Table/TableHeader/Extensions/Step1Triggers.tsx b/src/components/Table/TableHeader/Extensions/Step1Triggers.tsx new file mode 100644 index 00000000..7e630712 --- /dev/null +++ b/src/components/Table/TableHeader/Extensions/Step1Triggers.tsx @@ -0,0 +1,56 @@ +import { IExtensionModalStepProps } from "./ExtensionModal"; + +import { + FormControl, + FormLabel, + FormGroup, + FormControlLabel, + Checkbox, +} from "@mui/material"; + +import { triggerTypes } from "./utils"; + +export default function Step1Triggers({ + extensionObject, + setExtensionObject, +}: IExtensionModalStepProps) { + return ( + + + Triggers + + + + {triggerTypes.map((trigger) => ( + { + setExtensionObject((extensionObject) => { + if (extensionObject.triggers.includes(trigger)) { + return { + ...extensionObject, + triggers: extensionObject.triggers.filter( + (t) => t !== trigger + ), + }; + } else { + return { + ...extensionObject, + triggers: [...extensionObject.triggers, trigger], + }; + } + }); + }} + /> + } + /> + ))} + + + ); +} diff --git a/src/components/Table/TableHeader/Extensions/Step2RequiredFields.tsx b/src/components/Table/TableHeader/Extensions/Step2RequiredFields.tsx new file mode 100644 index 00000000..09ea596f --- /dev/null +++ b/src/components/Table/TableHeader/Extensions/Step2RequiredFields.tsx @@ -0,0 +1,59 @@ +import { IExtensionModalStepProps } from "./ExtensionModal"; +import _sortBy from "lodash/sortBy"; + +import MultiSelect from "@rowy/multiselect"; +import { ListItemIcon } from "@mui/material"; + +import { useProjectContext } from "contexts/ProjectContext"; +import { FieldType } from "constants/fields"; +import { getFieldProp } from "components/fields"; + +export default function Step2RequiredFields({ + extensionObject, + setExtensionObject, +}: IExtensionModalStepProps) { + const { tableState } = useProjectContext(); + + return ( + c.type !== FieldType.id) + .map((c) => ({ + value: c.key, + label: c.name, + type: c.type, + })) + : [] + } + onChange={(requiredFields) => + setExtensionObject((e) => ({ ...e, requiredFields })) + } + TextFieldProps={{ autoFocus: true }} + freeText + AddButtonProps={{ children: "Add other field" }} + AddDialogProps={{ + title: "Add other field", + textFieldLabel: "Field key", + }} + itemRenderer={(option: { + value: string; + label: string; + type?: FieldType; + }) => ( + <> + + {option.type && getFieldProp("icon", option.type)} + + {option.label} + {option.value} + + )} + /> + ); +} diff --git a/src/components/Table/TableHeader/Extensions/Step3Conditions.tsx b/src/components/Table/TableHeader/Extensions/Step3Conditions.tsx new file mode 100644 index 00000000..ad23b46b --- /dev/null +++ b/src/components/Table/TableHeader/Extensions/Step3Conditions.tsx @@ -0,0 +1,71 @@ +import { IExtensionModalStepProps } from "./ExtensionModal"; +import useStateRef from "react-usestateref"; + +import CodeEditor from "components/Table/editors/CodeEditor"; +import CodeEditorHelper from "components/CodeEditorHelper"; + +import { WIKI_LINKS } from "constants/externalLinks"; + +const additionalVariables = [ + { + key: "change", + description: + "you can pass in field name to change.before.get() or change.after.get() to get changes", + }, + { + key: "triggerType", + description: "triggerType indicates the type of the extension invocation", + }, + { + key: "fieldTypes", + description: + "fieldTypes is a map of all fields and its corresponding field type", + }, + { + key: "extensionConfig", + description: "the configuration object of this extension", + }, +]; + +export default function Step3Conditions({ + extensionObject, + setExtensionObject, + setValidation, + validationRef, +}: IExtensionModalStepProps) { + const [, setConditionEditorActive, conditionEditorActiveRef] = + useStateRef(false); + + return ( + <> +
+ { + setExtensionObject({ + ...extensionObject, + conditions: newValue, + }); + }} + onValidStatusUpdate={({ isValid }) => { + if (!conditionEditorActiveRef.current) return; + setValidation({ ...validationRef.current!, condition: isValid }); + }} + diagnosticsOptions={{ + noSemanticValidation: false, + noSyntaxValidation: false, + noSuggestionDiagnostics: true, + }} + onMount={() => setConditionEditorActive(true)} + onUnmount={() => setConditionEditorActive(false)} + /> +
+ + + + ); +} diff --git a/src/components/Table/TableHeader/Extensions/Step4Body.tsx b/src/components/Table/TableHeader/Extensions/Step4Body.tsx new file mode 100644 index 00000000..60060d6c --- /dev/null +++ b/src/components/Table/TableHeader/Extensions/Step4Body.tsx @@ -0,0 +1,77 @@ +import { IExtensionModalStepProps } from "./ExtensionModal"; +import _upperFirst from "lodash/upperFirst"; +import useStateRef from "react-usestateref"; + +import CodeEditor from "components/Table/editors/CodeEditor"; +import CodeEditorHelper from "components/CodeEditorHelper"; + +import { WIKI_LINKS } from "constants/externalLinks"; + +const additionalVariables = [ + { + key: "change", + description: + "you can pass in field name to change.before.get() or change.after.get() to get changes", + }, + { + key: "triggerType", + description: "triggerType indicates the type of the extension invocation", + }, + { + key: "fieldTypes", + description: + "fieldTypes is a map of all fields and its corresponding field type", + }, + { + key: "extensionConfig", + description: "the configuration object of this extension", + }, +]; + +export default function Step4Body({ + extensionObject, + setExtensionObject, + setValidation, + validationRef, +}: IExtensionModalStepProps) { + const [, setBodyEditorActive, bodyEditorActiveRef] = useStateRef(false); + + return ( + <> +
+ { + setExtensionObject({ + ...extensionObject, + extensionBody: newValue, + }); + }} + onValidStatusUpdate={({ isValid }) => { + if (!bodyEditorActiveRef.current) return; + setValidation({ + ...validationRef.current!, + extensionBody: isValid, + }); + }} + diagnosticsOptions={{ + noSemanticValidation: false, + noSyntaxValidation: false, + noSuggestionDiagnostics: true, + }} + onMount={() => setBodyEditorActive(true)} + onUnmount={() => setBodyEditorActive(false)} + /> +
+ + + + ); +} diff --git a/src/components/Table/TableHeader/Extensions/index.tsx b/src/components/Table/TableHeader/Extensions/index.tsx index 247edc89..293cbff3 100644 --- a/src/components/Table/TableHeader/Extensions/index.tsx +++ b/src/components/Table/TableHeader/Extensions/index.tsx @@ -1,9 +1,7 @@ import { useState } from "react"; import _isEqual from "lodash/isEqual"; -import { db } from "../../../../firebase"; -import { useSnackbar } from "notistack"; -import { Breadcrumbs, Typography, Button } from "@mui/material"; +import { Breadcrumbs } from "@mui/material"; import TableHeaderButton from "../TableHeaderButton"; import ExtensionIcon from "assets/icons/Extension"; @@ -17,20 +15,19 @@ import { useAppContext } from "contexts/AppContext"; import { useConfirmation } from "components/ConfirmationDialog"; import { useSnackLogContext } from "contexts/SnackLogContext"; -import { emptyExtensionObject, IExtension, IExtensionType } from "./utils"; -import { name } from "@root/package.json"; +import { emptyExtensionObject, IExtension, ExtensionType } from "./utils"; import { runRoutes } from "constants/runRoutes"; import { analytics } from "@src/analytics"; -export default function ExtensionsEditor() { - const { enqueueSnackbar } = useSnackbar(); +export default function Extensions() { const { tableState, tableActions, rowyRun } = useProjectContext(); const appContext = useAppContext(); const { requestConfirmation } = useConfirmation(); - const currentextensionObjects = (tableState?.config.extensionObjects ?? + + const currentExtensionObjects = (tableState?.config.extensionObjects ?? []) as IExtension[]; const [localExtensionsObjects, setLocalExtensionsObjects] = useState( - currentextensionObjects + currentExtensionObjects ); const [openExtensionList, setOpenExtensionList] = useState(false); const [openMigrationGuide, setOpenMigrationGuide] = useState(false); @@ -39,8 +36,9 @@ export default function ExtensionsEditor() { extensionObject: IExtension; index?: number; } | null>(null); + const snackLogContext = useSnackLogContext(); - const edited = !_isEqual(currentextensionObjects, localExtensionsObjects); + const edited = !_isEqual(currentExtensionObjects, localExtensionsObjects); const tablePathTokens = tableState?.tablePath?.split("/").filter(function (_, i) { @@ -65,7 +63,7 @@ export default function ExtensionsEditor() { body: "You will lose changes you have made to extensions", confirm: "Discard", handleConfirm: () => { - setLocalExtensionsObjects(currentextensionObjects); + setLocalExtensionsObjects(currentExtensionObjects); setOpenExtensionList(false); }, }); @@ -199,13 +197,13 @@ export default function ExtensionsEditor() { children={ <> - {tablePathTokens.map((pathToken) => { - return {pathToken}; - })} + {tablePathTokens.map((pathToken) => ( + {pathToken} + ))} { + handleAddExtension={(type: ExtensionType) => { setExtensionModal({ mode: "add", extensionObject: emptyExtensionObject( diff --git a/src/components/Table/TableHeader/Extensions/utils.ts b/src/components/Table/TableHeader/Extensions/utils.ts index a57870a0..61366df4 100644 --- a/src/components/Table/TableHeader/Extensions/utils.ts +++ b/src/components/Table/TableHeader/Extensions/utils.ts @@ -1,40 +1,4 @@ -type IExtensionType = - | "task" - | "docSync" - | "historySnapshot" - | "algoliaIndex" - | "meiliIndex" - | "bigqueryIndex" - | "slackMessage" - | "sendgridEmail" - | "apiCall" - | "twilioMessage"; - -type IExtensionTrigger = "create" | "update" | "delete"; - -interface IExtensionEditor { - displayName: string; - photoURL: string; - lastUpdate: number; -} - -interface IExtension { - // rowy meta fields - name: string; - active: boolean; - lastEditor: IExtensionEditor; - - // ft build fields - triggers: IExtensionTrigger[]; - type: IExtensionType; - requiredFields: string[]; - extensionBody: string; - conditions: string; -} - -const triggerTypes: IExtensionTrigger[] = ["create", "update", "delete"]; - -const extensionTypes: IExtensionType[] = [ +export const extensionTypes = [ "task", "docSync", "historySnapshot", @@ -45,7 +9,46 @@ const extensionTypes: IExtensionType[] = [ "sendgridEmail", "apiCall", "twilioMessage", -]; +] as const; + +export type ExtensionType = typeof extensionTypes[number]; + +export const extensionNames: Record = { + task: "Task", + docSync: "Doc Sync", + historySnapshot: "History Snapshot", + algoliaIndex: "Algolia Index", + meiliIndex: "MeiliSearch Index", + bigqueryIndex: "Big Query Index", + slackMessage: "Slack Message", + sendgridEmail: "SendGrid Email", + apiCall: "API Call", + twilioMessage: "Twilio Message", +}; + +export type ExtensionTrigger = "create" | "update" | "delete"; + +export interface IExtensionEditor { + displayName: string; + photoURL: string; + lastUpdate: number; +} + +export interface IExtension { + // rowy meta fields + name: string; + active: boolean; + lastEditor: IExtensionEditor; + + // ft build fields + triggers: ExtensionTrigger[]; + type: ExtensionType; + requiredFields: string[]; + extensionBody: string; + conditions: string; +} + +export const triggerTypes: ExtensionTrigger[] = ["create", "update", "delete"]; const extensionBodyTemplate = { task: `const extensionBody: TaskBody = async({row, db, change, ref}) => { @@ -158,8 +161,8 @@ const extensionBodyTemplate = { }`, }; -function emptyExtensionObject( - type: IExtensionType, +export function emptyExtensionObject( + type: ExtensionType, user: IExtensionEditor ): IExtension { return { @@ -176,7 +179,7 @@ function emptyExtensionObject( lastEditor: user, }; } -function sparkToExtensionObjects( +export function sparkToExtensionObjects( sparkConfig: string, user: IExtensionEditor ): IExtension[] { @@ -225,8 +228,8 @@ function sparkToExtensionObjects( lastEditor: user, // ft build fields - triggers: (spark.triggers ?? []) as IExtensionTrigger[], - type: spark.type as IExtensionType, + triggers: (spark.triggers ?? []) as ExtensionTrigger[], + type: spark.type as ExtensionType, requiredFields: spark.requiredFields ?? [], extensionBody: spark.sparkBody, conditions: spark.shouldRun ?? "", @@ -234,11 +237,3 @@ function sparkToExtensionObjects( }); return extensionObjects ?? []; } - -export { - extensionTypes, - triggerTypes, - emptyExtensionObject, - sparkToExtensionObjects, -}; -export type { IExtension, IExtensionType, IExtensionEditor }; diff --git a/src/theme/components.tsx b/src/theme/components.tsx index 72e01620..644907bc 100644 --- a/src/theme/components.tsx +++ b/src/theme/components.tsx @@ -91,6 +91,16 @@ export const components = (theme: Theme): ThemeOptions => { "& input, & label": theme.typography.body2, }, + + ".visually-hidden": { + position: "absolute", + clip: "rect(1px, 1px, 1px, 1px)", + overflow: "hidden", + height: 1, + width: 1, + padding: 0, + border: 0, + }, }, },