@mitodl/smoot-design 0.0.0-0a23f44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +28 -0
- package/README.md +35 -0
- package/dist/bundles/aiDrawerManager.es.js +38832 -0
- package/dist/bundles/aiDrawerManager.es.js.map +1 -0
- package/dist/bundles/aiDrawerManager.umd.js +245 -0
- package/dist/bundles/aiDrawerManager.umd.js.map +1 -0
- package/dist/bundles/remoteTutorDrawer.es.js +38832 -0
- package/dist/bundles/remoteTutorDrawer.es.js.map +1 -0
- package/dist/bundles/remoteTutorDrawer.umd.js +245 -0
- package/dist/bundles/remoteTutorDrawer.umd.js.map +1 -0
- package/dist/cjs/VERSION.d.ts +12 -0
- package/dist/cjs/VERSION.js +15 -0
- package/dist/cjs/ai.d.ts +3 -0
- package/dist/cjs/ai.js +9 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawer.d.ts +55 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawer.js +262 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawer.stories.d.ts +17 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawer.stories.js +264 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawerManager.d.ts +12 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawerManager.js +51 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawerManager.stories.d.ts +6 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawerManager.stories.js +267 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawerManager.test.d.ts +1 -0
- package/dist/cjs/bundles/AiDrawer/AiDrawerManager.test.js +245 -0
- package/dist/cjs/bundles/AiDrawer/FlashcardsScreen.d.ts +9 -0
- package/dist/cjs/bundles/AiDrawer/FlashcardsScreen.js +87 -0
- package/dist/cjs/bundles/aiDrawerManager.d.ts +6 -0
- package/dist/cjs/bundles/aiDrawerManager.js +44 -0
- package/dist/cjs/components/AiChat/AiChat.d.ts +5 -0
- package/dist/cjs/components/AiChat/AiChat.js +267 -0
- package/dist/cjs/components/AiChat/AiChat.stories.d.ts +17 -0
- package/dist/cjs/components/AiChat/AiChat.stories.js +194 -0
- package/dist/cjs/components/AiChat/AiChat.test.d.ts +1 -0
- package/dist/cjs/components/AiChat/AiChat.test.js +211 -0
- package/dist/cjs/components/AiChat/AiChatContext.d.ts +26 -0
- package/dist/cjs/components/AiChat/AiChatContext.js +106 -0
- package/dist/cjs/components/AiChat/AiChatContext.stories.d.ts +14 -0
- package/dist/cjs/components/AiChat/AiChatContext.stories.js +75 -0
- package/dist/cjs/components/AiChat/AiChatMarkdown.stories.d.ts +15 -0
- package/dist/cjs/components/AiChat/AiChatMarkdown.stories.js +282 -0
- package/dist/cjs/components/AiChat/ChatTitle.d.ts +8 -0
- package/dist/cjs/components/AiChat/ChatTitle.js +43 -0
- package/dist/cjs/components/AiChat/EllipsisIcon.d.ts +6 -0
- package/dist/cjs/components/AiChat/EllipsisIcon.js +17 -0
- package/dist/cjs/components/AiChat/EntryScreen.d.ts +11 -0
- package/dist/cjs/components/AiChat/EntryScreen.js +123 -0
- package/dist/cjs/components/AiChat/Markdown.d.ts +7 -0
- package/dist/cjs/components/AiChat/Markdown.js +14 -0
- package/dist/cjs/components/AiChat/TimLogo.d.ts +5 -0
- package/dist/cjs/components/AiChat/TimLogo.js +15 -0
- package/dist/cjs/components/AiChat/test-utils/api.d.ts +2 -0
- package/dist/cjs/components/AiChat/test-utils/api.js +164 -0
- package/dist/cjs/components/AiChat/types.d.ts +96 -0
- package/dist/cjs/components/AiChat/types.js +3 -0
- package/dist/cjs/components/AiChat/utils.d.ts +9 -0
- package/dist/cjs/components/AiChat/utils.js +41 -0
- package/dist/cjs/components/Alert/Alert.d.ts +15 -0
- package/dist/cjs/components/Alert/Alert.js +62 -0
- package/dist/cjs/components/Alert/Alert.stories.d.ts +8 -0
- package/dist/cjs/components/Alert/Alert.stories.js +53 -0
- package/dist/cjs/components/Button/ActionButton.d.ts +30 -0
- package/dist/cjs/components/Button/ActionButton.js +73 -0
- package/dist/cjs/components/Button/ActionButton.stories.d.ts +15 -0
- package/dist/cjs/components/Button/ActionButton.stories.js +113 -0
- package/dist/cjs/components/Button/Button.d.ts +58 -0
- package/dist/cjs/components/Button/Button.js +261 -0
- package/dist/cjs/components/Button/Button.stories.d.ts +18 -0
- package/dist/cjs/components/Button/Button.stories.js +148 -0
- package/dist/cjs/components/Button/Button.test.d.ts +1 -0
- package/dist/cjs/components/Button/Button.test.js +46 -0
- package/dist/cjs/components/Checkbox/Checkbox.d.ts +20 -0
- package/dist/cjs/components/Checkbox/Checkbox.js +85 -0
- package/dist/cjs/components/Checkbox/Checkbox.stories.d.ts +8 -0
- package/dist/cjs/components/Checkbox/Checkbox.stories.js +33 -0
- package/dist/cjs/components/CheckboxChoiceField/CheckboxChoiceField.d.ts +21 -0
- package/dist/cjs/components/CheckboxChoiceField/CheckboxChoiceField.js +43 -0
- package/dist/cjs/components/CheckboxChoiceField/CheckboxChoiceField.stories.d.ts +8 -0
- package/dist/cjs/components/CheckboxChoiceField/CheckboxChoiceField.stories.js +50 -0
- package/dist/cjs/components/CheckboxChoiceField/CheckboxChoiceField.test.d.ts +1 -0
- package/dist/cjs/components/CheckboxChoiceField/CheckboxChoiceField.test.js +52 -0
- package/dist/cjs/components/ImageAdapter/ImageAdapter.d.ts +23 -0
- package/dist/cjs/components/ImageAdapter/ImageAdapter.js +30 -0
- package/dist/cjs/components/Input/Input.d.ts +116 -0
- package/dist/cjs/components/Input/Input.js +237 -0
- package/dist/cjs/components/Input/Input.stories.d.ts +19 -0
- package/dist/cjs/components/Input/Input.stories.js +135 -0
- package/dist/cjs/components/Input/Input.test.d.ts +1 -0
- package/dist/cjs/components/Input/Input.test.js +32 -0
- package/dist/cjs/components/LinkAdapter/LinkAdapter.d.ts +23 -0
- package/dist/cjs/components/LinkAdapter/LinkAdapter.js +34 -0
- package/dist/cjs/components/RadioChoiceField/BooleanRadioChoiceField.stories.d.ts +6 -0
- package/dist/cjs/components/RadioChoiceField/BooleanRadioChoiceField.stories.js +47 -0
- package/dist/cjs/components/RadioChoiceField/RadioChoiceField.d.ts +45 -0
- package/dist/cjs/components/RadioChoiceField/RadioChoiceField.js +69 -0
- package/dist/cjs/components/RadioChoiceField/RadioChoiceField.stories.d.ts +6 -0
- package/dist/cjs/components/RadioChoiceField/RadioChoiceField.stories.js +55 -0
- package/dist/cjs/components/RadioChoiceField/RadioChoiceField.test.d.ts +1 -0
- package/dist/cjs/components/RadioChoiceField/RadioChoiceField.test.js +53 -0
- package/dist/cjs/components/ScrollSnap/ScrollSnap.d.ts +19 -0
- package/dist/cjs/components/ScrollSnap/ScrollSnap.js +59 -0
- package/dist/cjs/components/ScrollSnap/ScrollSnap.stories.d.ts +6 -0
- package/dist/cjs/components/ScrollSnap/ScrollSnap.stories.js +43 -0
- package/dist/cjs/components/ScrollSnap/useScrollSnap.d.ts +6 -0
- package/dist/cjs/components/ScrollSnap/useScrollSnap.js +36 -0
- package/dist/cjs/components/SrAnnouncer/SrAnnouncer.d.ts +25 -0
- package/dist/cjs/components/SrAnnouncer/SrAnnouncer.js +43 -0
- package/dist/cjs/components/SrAnnouncer/SrAnnouncer.stories.d.ts +6 -0
- package/dist/cjs/components/SrAnnouncer/SrAnnouncer.stories.js +44 -0
- package/dist/cjs/components/SrAnnouncer/SrAnnouncer.test.d.ts +1 -0
- package/dist/cjs/components/SrAnnouncer/SrAnnouncer.test.js +62 -0
- package/dist/cjs/components/TabButtons/TabButtonList.d.ts +25 -0
- package/dist/cjs/components/TabButtons/TabButtonList.js +97 -0
- package/dist/cjs/components/TabButtons/TabButtonList.stories.d.ts +24 -0
- package/dist/cjs/components/TabButtons/TabButtonList.stories.js +139 -0
- package/dist/cjs/components/TextField/TextField.d.ts +29 -0
- package/dist/cjs/components/TextField/TextField.js +33 -0
- package/dist/cjs/components/TextField/TextField.stories.d.ts +10 -0
- package/dist/cjs/components/TextField/TextField.stories.js +136 -0
- package/dist/cjs/components/TextField/TextField.test.d.ts +1 -0
- package/dist/cjs/components/TextField/TextField.test.js +77 -0
- package/dist/cjs/components/ThemeProvider/ThemeProvider.d.ts +21 -0
- package/dist/cjs/components/ThemeProvider/ThemeProvider.js +86 -0
- package/dist/cjs/components/ThemeProvider/ThemeProvider.stories.d.ts +63 -0
- package/dist/cjs/components/ThemeProvider/ThemeProvider.stories.js +102 -0
- package/dist/cjs/components/ThemeProvider/Typography.stories.d.ts +39 -0
- package/dist/cjs/components/ThemeProvider/Typography.stories.js +65 -0
- package/dist/cjs/components/ThemeProvider/breakpoints.d.ts +4 -0
- package/dist/cjs/components/ThemeProvider/breakpoints.js +19 -0
- package/dist/cjs/components/ThemeProvider/buttons.d.ts +7 -0
- package/dist/cjs/components/ThemeProvider/buttons.js +20 -0
- package/dist/cjs/components/ThemeProvider/chips.d.ts +3 -0
- package/dist/cjs/components/ThemeProvider/chips.js +154 -0
- package/dist/cjs/components/ThemeProvider/colors.d.ts +32 -0
- package/dist/cjs/components/ThemeProvider/colors.js +35 -0
- package/dist/cjs/components/ThemeProvider/typography.d.ts +18 -0
- package/dist/cjs/components/ThemeProvider/typography.js +173 -0
- package/dist/cjs/components/VisuallyHidden/VisuallyHidden.d.ts +24 -0
- package/dist/cjs/components/VisuallyHidden/VisuallyHidden.js +33 -0
- package/dist/cjs/components/VisuallyHidden/VisuallyHidden.stories.d.ts +6 -0
- package/dist/cjs/components/VisuallyHidden/VisuallyHidden.stories.js +13 -0
- package/dist/cjs/components/internal/FormHelpers/FormHelpers.d.ts +39 -0
- package/dist/cjs/components/internal/FormHelpers/FormHelpers.js +78 -0
- package/dist/cjs/components/internal/FormHelpers/FormHelpers.test.d.ts +1 -0
- package/dist/cjs/components/internal/FormHelpers/FormHelpers.test.js +93 -0
- package/dist/cjs/index.d.ts +25 -0
- package/dist/cjs/index.js +44 -0
- package/dist/cjs/jest-setup.d.ts +1 -0
- package/dist/cjs/jest-setup.js +18 -0
- package/dist/cjs/jsdom-extended.d.ts +6 -0
- package/dist/cjs/jsdom-extended.js +14 -0
- package/dist/cjs/story-utils/index.d.ts +6 -0
- package/dist/cjs/story-utils/index.js +17 -0
- package/dist/cjs/utils/composeRefs.d.ts +7 -0
- package/dist/cjs/utils/composeRefs.js +20 -0
- package/dist/cjs/utils/composeRefs.test.d.ts +1 -0
- package/dist/cjs/utils/composeRefs.test.js +19 -0
- package/dist/cjs/utils/retryingFetch.d.ts +19 -0
- package/dist/cjs/utils/retryingFetch.js +98 -0
- package/dist/cjs/utils/retryingFetch.test.d.ts +1 -0
- package/dist/cjs/utils/retryingFetch.test.js +48 -0
- package/dist/cjs/utils/useDevCheckStable.d.ts +8 -0
- package/dist/cjs/utils/useDevCheckStable.js +29 -0
- package/dist/cjs/utils/useInterval.d.ts +7 -0
- package/dist/cjs/utils/useInterval.js +25 -0
- package/dist/esm/VERSION.d.ts +12 -0
- package/dist/esm/VERSION.js +12 -0
- package/dist/esm/ai.d.ts +3 -0
- package/dist/esm/ai.js +2 -0
- package/dist/esm/bundles/AiDrawer/AiDrawer.d.ts +55 -0
- package/dist/esm/bundles/AiDrawer/AiDrawer.js +259 -0
- package/dist/esm/bundles/AiDrawer/AiDrawer.stories.d.ts +17 -0
- package/dist/esm/bundles/AiDrawer/AiDrawer.stories.js +261 -0
- package/dist/esm/bundles/AiDrawer/AiDrawerManager.d.ts +12 -0
- package/dist/esm/bundles/AiDrawer/AiDrawerManager.js +48 -0
- package/dist/esm/bundles/AiDrawer/AiDrawerManager.stories.d.ts +6 -0
- package/dist/esm/bundles/AiDrawer/AiDrawerManager.stories.js +264 -0
- package/dist/esm/bundles/AiDrawer/AiDrawerManager.test.d.ts +1 -0
- package/dist/esm/bundles/AiDrawer/AiDrawerManager.test.js +243 -0
- package/dist/esm/bundles/AiDrawer/FlashcardsScreen.d.ts +9 -0
- package/dist/esm/bundles/AiDrawer/FlashcardsScreen.js +83 -0
- package/dist/esm/bundles/aiDrawerManager.d.ts +6 -0
- package/dist/esm/bundles/aiDrawerManager.js +41 -0
- package/dist/esm/components/AiChat/AiChat.d.ts +5 -0
- package/dist/esm/components/AiChat/AiChat.js +263 -0
- package/dist/esm/components/AiChat/AiChat.stories.d.ts +17 -0
- package/dist/esm/components/AiChat/AiChat.stories.js +191 -0
- package/dist/esm/components/AiChat/AiChat.test.d.ts +1 -0
- package/dist/esm/components/AiChat/AiChat.test.js +209 -0
- package/dist/esm/components/AiChat/AiChatContext.d.ts +26 -0
- package/dist/esm/components/AiChat/AiChatContext.js +102 -0
- package/dist/esm/components/AiChat/AiChatContext.stories.d.ts +14 -0
- package/dist/esm/components/AiChat/AiChatContext.stories.js +72 -0
- package/dist/esm/components/AiChat/AiChatMarkdown.stories.d.ts +15 -0
- package/dist/esm/components/AiChat/AiChatMarkdown.stories.js +279 -0
- package/dist/esm/components/AiChat/ChatTitle.d.ts +8 -0
- package/dist/esm/components/AiChat/ChatTitle.js +40 -0
- package/dist/esm/components/AiChat/EllipsisIcon.d.ts +6 -0
- package/dist/esm/components/AiChat/EllipsisIcon.js +15 -0
- package/dist/esm/components/AiChat/EntryScreen.d.ts +11 -0
- package/dist/esm/components/AiChat/EntryScreen.js +120 -0
- package/dist/esm/components/AiChat/Markdown.d.ts +7 -0
- package/dist/esm/components/AiChat/Markdown.js +12 -0
- package/dist/esm/components/AiChat/TimLogo.d.ts +5 -0
- package/dist/esm/components/AiChat/TimLogo.js +13 -0
- package/dist/esm/components/AiChat/test-utils/api.d.ts +2 -0
- package/dist/esm/components/AiChat/test-utils/api.js +161 -0
- package/dist/esm/components/AiChat/types.d.ts +96 -0
- package/dist/esm/components/AiChat/types.js +2 -0
- package/dist/esm/components/AiChat/utils.d.ts +9 -0
- package/dist/esm/components/AiChat/utils.js +38 -0
- package/dist/esm/components/Alert/Alert.d.ts +15 -0
- package/dist/esm/components/Alert/Alert.js +59 -0
- package/dist/esm/components/Alert/Alert.stories.d.ts +8 -0
- package/dist/esm/components/Alert/Alert.stories.js +50 -0
- package/dist/esm/components/Button/ActionButton.d.ts +30 -0
- package/dist/esm/components/Button/ActionButton.js +68 -0
- package/dist/esm/components/Button/ActionButton.stories.d.ts +15 -0
- package/dist/esm/components/Button/ActionButton.stories.js +110 -0
- package/dist/esm/components/Button/Button.d.ts +58 -0
- package/dist/esm/components/Button/Button.js +252 -0
- package/dist/esm/components/Button/Button.stories.d.ts +18 -0
- package/dist/esm/components/Button/Button.stories.js +145 -0
- package/dist/esm/components/Button/Button.test.d.ts +1 -0
- package/dist/esm/components/Button/Button.test.js +44 -0
- package/dist/esm/components/Checkbox/Checkbox.d.ts +20 -0
- package/dist/esm/components/Checkbox/Checkbox.js +81 -0
- package/dist/esm/components/Checkbox/Checkbox.stories.d.ts +8 -0
- package/dist/esm/components/Checkbox/Checkbox.stories.js +30 -0
- package/dist/esm/components/CheckboxChoiceField/CheckboxChoiceField.d.ts +21 -0
- package/dist/esm/components/CheckboxChoiceField/CheckboxChoiceField.js +40 -0
- package/dist/esm/components/CheckboxChoiceField/CheckboxChoiceField.stories.d.ts +8 -0
- package/dist/esm/components/CheckboxChoiceField/CheckboxChoiceField.stories.js +47 -0
- package/dist/esm/components/CheckboxChoiceField/CheckboxChoiceField.test.d.ts +1 -0
- package/dist/esm/components/CheckboxChoiceField/CheckboxChoiceField.test.js +50 -0
- package/dist/esm/components/ImageAdapter/ImageAdapter.d.ts +23 -0
- package/dist/esm/components/ImageAdapter/ImageAdapter.js +27 -0
- package/dist/esm/components/Input/Input.d.ts +116 -0
- package/dist/esm/components/Input/Input.js +232 -0
- package/dist/esm/components/Input/Input.stories.d.ts +19 -0
- package/dist/esm/components/Input/Input.stories.js +132 -0
- package/dist/esm/components/Input/Input.test.d.ts +1 -0
- package/dist/esm/components/Input/Input.test.js +30 -0
- package/dist/esm/components/LinkAdapter/LinkAdapter.d.ts +23 -0
- package/dist/esm/components/LinkAdapter/LinkAdapter.js +31 -0
- package/dist/esm/components/RadioChoiceField/BooleanRadioChoiceField.stories.d.ts +6 -0
- package/dist/esm/components/RadioChoiceField/BooleanRadioChoiceField.stories.js +44 -0
- package/dist/esm/components/RadioChoiceField/RadioChoiceField.d.ts +45 -0
- package/dist/esm/components/RadioChoiceField/RadioChoiceField.js +65 -0
- package/dist/esm/components/RadioChoiceField/RadioChoiceField.stories.d.ts +6 -0
- package/dist/esm/components/RadioChoiceField/RadioChoiceField.stories.js +52 -0
- package/dist/esm/components/RadioChoiceField/RadioChoiceField.test.d.ts +1 -0
- package/dist/esm/components/RadioChoiceField/RadioChoiceField.test.js +51 -0
- package/dist/esm/components/ScrollSnap/ScrollSnap.d.ts +19 -0
- package/dist/esm/components/ScrollSnap/ScrollSnap.js +56 -0
- package/dist/esm/components/ScrollSnap/ScrollSnap.stories.d.ts +6 -0
- package/dist/esm/components/ScrollSnap/ScrollSnap.stories.js +40 -0
- package/dist/esm/components/ScrollSnap/useScrollSnap.d.ts +6 -0
- package/dist/esm/components/ScrollSnap/useScrollSnap.js +33 -0
- package/dist/esm/components/SrAnnouncer/SrAnnouncer.d.ts +25 -0
- package/dist/esm/components/SrAnnouncer/SrAnnouncer.js +40 -0
- package/dist/esm/components/SrAnnouncer/SrAnnouncer.stories.d.ts +6 -0
- package/dist/esm/components/SrAnnouncer/SrAnnouncer.stories.js +41 -0
- package/dist/esm/components/SrAnnouncer/SrAnnouncer.test.d.ts +1 -0
- package/dist/esm/components/SrAnnouncer/SrAnnouncer.test.js +60 -0
- package/dist/esm/components/TabButtons/TabButtonList.d.ts +25 -0
- package/dist/esm/components/TabButtons/TabButtonList.js +92 -0
- package/dist/esm/components/TabButtons/TabButtonList.stories.d.ts +24 -0
- package/dist/esm/components/TabButtons/TabButtonList.stories.js +136 -0
- package/dist/esm/components/TextField/TextField.d.ts +29 -0
- package/dist/esm/components/TextField/TextField.js +30 -0
- package/dist/esm/components/TextField/TextField.stories.d.ts +10 -0
- package/dist/esm/components/TextField/TextField.stories.js +133 -0
- package/dist/esm/components/TextField/TextField.test.d.ts +1 -0
- package/dist/esm/components/TextField/TextField.test.js +75 -0
- package/dist/esm/components/ThemeProvider/ThemeProvider.d.ts +21 -0
- package/dist/esm/components/ThemeProvider/ThemeProvider.js +82 -0
- package/dist/esm/components/ThemeProvider/ThemeProvider.stories.d.ts +63 -0
- package/dist/esm/components/ThemeProvider/ThemeProvider.stories.js +99 -0
- package/dist/esm/components/ThemeProvider/Typography.stories.d.ts +39 -0
- package/dist/esm/components/ThemeProvider/Typography.stories.js +62 -0
- package/dist/esm/components/ThemeProvider/breakpoints.d.ts +4 -0
- package/dist/esm/components/ThemeProvider/breakpoints.js +15 -0
- package/dist/esm/components/ThemeProvider/buttons.d.ts +7 -0
- package/dist/esm/components/ThemeProvider/buttons.js +17 -0
- package/dist/esm/components/ThemeProvider/chips.d.ts +3 -0
- package/dist/esm/components/ThemeProvider/chips.js +151 -0
- package/dist/esm/components/ThemeProvider/colors.d.ts +32 -0
- package/dist/esm/components/ThemeProvider/colors.js +32 -0
- package/dist/esm/components/ThemeProvider/typography.d.ts +18 -0
- package/dist/esm/components/ThemeProvider/typography.js +167 -0
- package/dist/esm/components/VisuallyHidden/VisuallyHidden.d.ts +24 -0
- package/dist/esm/components/VisuallyHidden/VisuallyHidden.js +30 -0
- package/dist/esm/components/VisuallyHidden/VisuallyHidden.stories.d.ts +6 -0
- package/dist/esm/components/VisuallyHidden/VisuallyHidden.stories.js +10 -0
- package/dist/esm/components/internal/FormHelpers/FormHelpers.d.ts +39 -0
- package/dist/esm/components/internal/FormHelpers/FormHelpers.js +73 -0
- package/dist/esm/components/internal/FormHelpers/FormHelpers.test.d.ts +1 -0
- package/dist/esm/components/internal/FormHelpers/FormHelpers.test.js +91 -0
- package/dist/esm/index.d.ts +25 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/jest-setup.d.ts +1 -0
- package/dist/esm/jest-setup.js +16 -0
- package/dist/esm/jsdom-extended.d.ts +6 -0
- package/dist/esm/jsdom-extended.js +12 -0
- package/dist/esm/story-utils/index.d.ts +6 -0
- package/dist/esm/story-utils/index.js +13 -0
- package/dist/esm/utils/composeRefs.d.ts +7 -0
- package/dist/esm/utils/composeRefs.js +17 -0
- package/dist/esm/utils/composeRefs.test.d.ts +1 -0
- package/dist/esm/utils/composeRefs.test.js +17 -0
- package/dist/esm/utils/retryingFetch.d.ts +19 -0
- package/dist/esm/utils/retryingFetch.js +96 -0
- package/dist/esm/utils/retryingFetch.test.d.ts +1 -0
- package/dist/esm/utils/retryingFetch.test.js +46 -0
- package/dist/esm/utils/useDevCheckStable.d.ts +8 -0
- package/dist/esm/utils/useDevCheckStable.js +26 -0
- package/dist/esm/utils/useInterval.d.ts +7 -0
- package/dist/esm/utils/useInterval.js +22 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/type-augmentation/TypescriptDocs.mdx +17 -0
- package/dist/type-augmentation/imports.d.ts +3 -0
- package/dist/type-augmentation/index.d.ts +3 -0
- package/dist/type-augmentation/theme.d.ts +105 -0
- package/dist/type-augmentation/typography.d.ts +54 -0
- package/package.json +159 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import styled from "@emotion/styled";
|
|
2
|
+
/**
|
|
3
|
+
* VisuallyHidden is a utility component that hides its children from sighted
|
|
4
|
+
* users, but keeps them accessible to screen readers.
|
|
5
|
+
*
|
|
6
|
+
* Often, screenreader-only content can be handled with an `aria-label`. However,
|
|
7
|
+
* occasionally we need actual elements.
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
* - a visually hidden aria-live section that reads announcements that
|
|
11
|
+
* isual users can ascertain in some other way.
|
|
12
|
+
* - a visually hidden Heading for a section whose purpose is clear for sighted users
|
|
13
|
+
* - a visually hidden description used for aria-describeddby
|
|
14
|
+
* - There is an aria-description attribute that can be used to provide a
|
|
15
|
+
* without an actual element on the page. However, it is introduced in
|
|
16
|
+
* ARIA 1.3 (working draft), not compatible with some screen readers, and
|
|
17
|
+
* flagged problematic by our linting.
|
|
18
|
+
*
|
|
19
|
+
* The CSS here is based on https://inclusive-components.design/tooltips-toggletips/
|
|
20
|
+
*/
|
|
21
|
+
const VisuallyHidden = styled.span({
|
|
22
|
+
clipPath: "inset(100%)",
|
|
23
|
+
clip: "rect(1px, 1px, 1px, 1px)",
|
|
24
|
+
height: "1px",
|
|
25
|
+
overflow: "hidden",
|
|
26
|
+
position: "absolute",
|
|
27
|
+
whiteSpace: "nowrap",
|
|
28
|
+
width: "1px",
|
|
29
|
+
});
|
|
30
|
+
export { VisuallyHidden };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import { VisuallyHidden } from "./VisuallyHidden";
|
|
3
|
+
declare const meta: Meta<typeof VisuallyHidden>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof VisuallyHidden>;
|
|
6
|
+
export declare const ScreenreaderOnly: Story;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { VisuallyHidden } from "./VisuallyHidden";
|
|
2
|
+
const meta = {
|
|
3
|
+
title: "smoot-design/VisuallyHidden",
|
|
4
|
+
component: VisuallyHidden,
|
|
5
|
+
args: {
|
|
6
|
+
children: "Not visible, but screen readers can still read this text.",
|
|
7
|
+
},
|
|
8
|
+
};
|
|
9
|
+
export default meta;
|
|
10
|
+
export const ScreenreaderOnly = {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
declare const Description: import("@emotion/styled").StyledComponent<{
|
|
3
|
+
theme?: import("@emotion/react").Theme;
|
|
4
|
+
as?: React.ElementType;
|
|
5
|
+
} & {
|
|
6
|
+
error?: boolean;
|
|
7
|
+
}, React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {}>;
|
|
8
|
+
type ControlLabelProps = {
|
|
9
|
+
htmlFor: string;
|
|
10
|
+
label: React.ReactNode;
|
|
11
|
+
required?: boolean;
|
|
12
|
+
id?: string;
|
|
13
|
+
fullWidth?: boolean;
|
|
14
|
+
};
|
|
15
|
+
declare const ControlLabel: React.FC<ControlLabelProps>;
|
|
16
|
+
type FormFieldWrapperProps = {
|
|
17
|
+
label: React.ReactNode;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
helpText?: string;
|
|
20
|
+
error?: boolean;
|
|
21
|
+
errorText?: string;
|
|
22
|
+
className?: string;
|
|
23
|
+
fullWidth?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The id of the input element. If not provided, a unique id will be generated.
|
|
26
|
+
*/
|
|
27
|
+
id?: string;
|
|
28
|
+
children: (childProps: {
|
|
29
|
+
id: string;
|
|
30
|
+
required?: boolean;
|
|
31
|
+
error?: boolean;
|
|
32
|
+
fullWidth?: boolean;
|
|
33
|
+
labelId: string;
|
|
34
|
+
"aria-describedby"?: string;
|
|
35
|
+
}) => React.ReactNode;
|
|
36
|
+
};
|
|
37
|
+
declare const FormFieldWrapper: React.FC<FormFieldWrapperProps>;
|
|
38
|
+
export { FormFieldWrapper, ControlLabel, Description };
|
|
39
|
+
export type { FormFieldWrapperProps, ControlLabelProps };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import styled from "@emotion/styled";
|
|
3
|
+
import { RiErrorWarningLine } from "@remixicon/react";
|
|
4
|
+
import Typography from "@mui/material/Typography";
|
|
5
|
+
const Required = styled.span(({ theme }) => ({
|
|
6
|
+
color: theme.custom.colors.lightRed,
|
|
7
|
+
marginLeft: "4px",
|
|
8
|
+
}));
|
|
9
|
+
const Description = styled.div(({ theme, error }) => [
|
|
10
|
+
Object.assign(Object.assign({}, theme.typography.body2), { color: error
|
|
11
|
+
? theme.custom.colors.lightRed
|
|
12
|
+
: theme.custom.colors.silverGrayDark }),
|
|
13
|
+
error && {
|
|
14
|
+
textIndent: "-24px",
|
|
15
|
+
paddingLeft: "24px",
|
|
16
|
+
"> svg:first-of-type": {
|
|
17
|
+
marginRight: "4px",
|
|
18
|
+
transform: "translateY(2px)",
|
|
19
|
+
width: "18px",
|
|
20
|
+
height: "18px",
|
|
21
|
+
position: "relative",
|
|
22
|
+
top: "2px",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
]);
|
|
26
|
+
const Container = styled.div(({ fullWidth }) => [
|
|
27
|
+
{
|
|
28
|
+
display: "inline-flex",
|
|
29
|
+
flexDirection: "column",
|
|
30
|
+
alignItems: "start",
|
|
31
|
+
"> *:not(:last-child)": {
|
|
32
|
+
marginBottom: "4px",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
fullWidth && {
|
|
36
|
+
width: "100%",
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
39
|
+
const ControlLabel = ({ htmlFor, label, required, id, fullWidth, }) => {
|
|
40
|
+
return (React.createElement(Typography, { id: id, component: "label", htmlFor: htmlFor, variant: "subtitle2", sx: { marginBottom: "4px", width: fullWidth ? "100%" : "auto" } },
|
|
41
|
+
label,
|
|
42
|
+
required ? React.createElement(Required, { "aria-hidden": "true" }, "*") : null));
|
|
43
|
+
};
|
|
44
|
+
const FormFieldWrapper = ({ label, required, helpText, error, errorText, className, fullWidth, id, children, }) => {
|
|
45
|
+
const fallbackInputId = React.useId();
|
|
46
|
+
const inputId = id || fallbackInputId;
|
|
47
|
+
const helpId = React.useId();
|
|
48
|
+
const errorId = React.useId();
|
|
49
|
+
const labelId = React.useId();
|
|
50
|
+
/**
|
|
51
|
+
* aria-errormessage would be more semantic for the error message but has
|
|
52
|
+
* somewhat limited support. See https://github.com/w3c/aria/issues/2048 for
|
|
53
|
+
* some related information.
|
|
54
|
+
*/
|
|
55
|
+
const describedBy = [helpText && helpId, error && errorText && errorId]
|
|
56
|
+
.filter(Boolean)
|
|
57
|
+
.join(" ") || undefined;
|
|
58
|
+
return (React.createElement(Container, { className: className, fullWidth: fullWidth },
|
|
59
|
+
React.createElement(ControlLabel, { htmlFor: inputId, id: labelId, label: label, required: required, fullWidth: fullWidth }),
|
|
60
|
+
children({
|
|
61
|
+
id: inputId,
|
|
62
|
+
error,
|
|
63
|
+
required,
|
|
64
|
+
labelId,
|
|
65
|
+
fullWidth,
|
|
66
|
+
"aria-describedby": describedBy,
|
|
67
|
+
}),
|
|
68
|
+
helpText && React.createElement(Description, { id: helpId }, helpText),
|
|
69
|
+
error && errorText && (React.createElement(Description, { id: errorId, error: true },
|
|
70
|
+
React.createElement(RiErrorWarningLine, { fontSize: "inherit" }),
|
|
71
|
+
errorText))));
|
|
72
|
+
};
|
|
73
|
+
export { FormFieldWrapper, ControlLabel, Description };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import * as React from "react";
|
|
13
|
+
import { render, screen } from "@testing-library/react";
|
|
14
|
+
import { FormFieldWrapper } from "./FormHelpers";
|
|
15
|
+
import { ThemeProvider } from "../../../components/ThemeProvider/ThemeProvider";
|
|
16
|
+
import { faker } from "@faker-js/faker/locale/en";
|
|
17
|
+
const assertDescription = ({ text, total, exists, }) => {
|
|
18
|
+
var _a;
|
|
19
|
+
const input = screen.getByRole("textbox");
|
|
20
|
+
const describedBy = input.getAttribute("aria-describedby");
|
|
21
|
+
const descriptionIds = (_a = describedBy === null || describedBy === void 0 ? void 0 : describedBy.split(" ")) !== null && _a !== void 0 ? _a : [];
|
|
22
|
+
if (total === 0) {
|
|
23
|
+
expect(describedBy).toBe(null);
|
|
24
|
+
}
|
|
25
|
+
expect(descriptionIds.length).toBe(total);
|
|
26
|
+
const description = screen.queryByText(text);
|
|
27
|
+
expect(!!description).toBe(exists);
|
|
28
|
+
if (exists) {
|
|
29
|
+
expect(description).toBeInTheDocument();
|
|
30
|
+
expect(descriptionIds).toContain(description === null || description === void 0 ? void 0 : description.id);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
describe("FormFieldWrapper", () => {
|
|
34
|
+
const setup = (props) => {
|
|
35
|
+
const defaults = {
|
|
36
|
+
name: "test-name",
|
|
37
|
+
value: "test-value",
|
|
38
|
+
label: "test-label",
|
|
39
|
+
};
|
|
40
|
+
const { rerender: _rerender } = render(React.createElement(FormFieldWrapper, Object.assign({}, defaults, props), (_a) => {
|
|
41
|
+
var { error, labelId, fullWidth } = _a, childProps = __rest(_a, ["error", "labelId", "fullWidth"]);
|
|
42
|
+
return (React.createElement("input", Object.assign({}, childProps)));
|
|
43
|
+
}), {
|
|
44
|
+
wrapper: ThemeProvider,
|
|
45
|
+
});
|
|
46
|
+
const rerender = (newProps) => {
|
|
47
|
+
_rerender(React.createElement(FormFieldWrapper, Object.assign({}, defaults, newProps), (_a) => {
|
|
48
|
+
var { error, labelId, fullWidth } = _a, childProps = __rest(_a, ["error", "labelId", "fullWidth"]);
|
|
49
|
+
return (React.createElement("input", Object.assign({}, childProps)));
|
|
50
|
+
}));
|
|
51
|
+
};
|
|
52
|
+
return { rerender };
|
|
53
|
+
};
|
|
54
|
+
it("Labels the input", () => {
|
|
55
|
+
const label = faker.lorem.words();
|
|
56
|
+
setup({ label });
|
|
57
|
+
const input = screen.getByRole("textbox", { name: label });
|
|
58
|
+
expect(input).toBeInstanceOf(HTMLInputElement);
|
|
59
|
+
});
|
|
60
|
+
it("Has a description only if description provided", () => {
|
|
61
|
+
const label = faker.lorem.words();
|
|
62
|
+
const helpText = faker.lorem.words();
|
|
63
|
+
const { rerender } = setup({ label, helpText });
|
|
64
|
+
assertDescription({ text: helpText, total: 1, exists: true });
|
|
65
|
+
rerender({ label });
|
|
66
|
+
assertDescription({ text: helpText, total: 0, exists: false });
|
|
67
|
+
});
|
|
68
|
+
it("Has an error message only if errorText provided AND error=true", () => {
|
|
69
|
+
const label = faker.lorem.words();
|
|
70
|
+
const errorText = faker.lorem.words();
|
|
71
|
+
const { rerender } = setup({ label, errorText, error: true });
|
|
72
|
+
assertDescription({ text: errorText, total: 1, exists: true });
|
|
73
|
+
rerender({ label, errorText });
|
|
74
|
+
assertDescription({ text: errorText, total: 0, exists: false });
|
|
75
|
+
rerender({ label, error: true });
|
|
76
|
+
assertDescription({ text: errorText, total: 0, exists: false });
|
|
77
|
+
rerender({ label, errorText, error: true });
|
|
78
|
+
assertDescription({ text: errorText, total: 1, exists: true });
|
|
79
|
+
});
|
|
80
|
+
it("Shows both description and errormessage if both provided and error", () => {
|
|
81
|
+
const label = faker.lorem.words();
|
|
82
|
+
const errorText = faker.lorem.words();
|
|
83
|
+
const helpText = faker.lorem.words();
|
|
84
|
+
const { rerender } = setup({ label, errorText, helpText });
|
|
85
|
+
assertDescription({ text: helpText, total: 1, exists: true });
|
|
86
|
+
assertDescription({ text: errorText, total: 1, exists: false });
|
|
87
|
+
rerender({ label, errorText, helpText, error: true });
|
|
88
|
+
assertDescription({ text: helpText, total: 2, exists: true });
|
|
89
|
+
assertDescription({ text: errorText, total: 2, exists: true });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { default as styled } from "@emotion/styled";
|
|
2
|
+
export { css, Global } from "@emotion/react";
|
|
3
|
+
export { ThemeProvider, createTheme, } from "./components/ThemeProvider/ThemeProvider";
|
|
4
|
+
export { Alert } from "./components/Alert/Alert";
|
|
5
|
+
export type { AlertProps } from "./components/Alert/Alert";
|
|
6
|
+
export { Button, ButtonLoadingIcon, ButtonLink, } from "./components/Button/Button";
|
|
7
|
+
export type { ButtonProps, ButtonLinkProps } from "./components/Button/Button";
|
|
8
|
+
export { ActionButton, ActionButtonLink, } from "./components/Button/ActionButton";
|
|
9
|
+
export type { ActionButtonProps, ActionButtonLinkProps, } from "./components/Button/ActionButton";
|
|
10
|
+
export type { LinkAdapterPropsOverrides } from "./components/LinkAdapter/LinkAdapter";
|
|
11
|
+
export { Input, AdornmentButton } from "./components/Input/Input";
|
|
12
|
+
export type { InputProps, AdornmentButtonProps } from "./components/Input/Input";
|
|
13
|
+
export { TextField } from "./components/TextField/TextField";
|
|
14
|
+
export type { TextFieldProps } from "./components/TextField/TextField";
|
|
15
|
+
export { Checkbox, childCheckboxStyles } from "./components/Checkbox/Checkbox";
|
|
16
|
+
export type { CheckboxProps } from "./components/Checkbox/Checkbox";
|
|
17
|
+
export { CheckboxChoiceField } from "./components/CheckboxChoiceField/CheckboxChoiceField";
|
|
18
|
+
export type { CheckboxChoiceFieldProps } from "./components/CheckboxChoiceField/CheckboxChoiceField";
|
|
19
|
+
export { RadioChoiceField, BooleanRadioChoiceField, } from "./components/RadioChoiceField/RadioChoiceField";
|
|
20
|
+
export type { RadioChoiceFieldProps, BooleanRadioChoiceFieldProps, } from "./components/RadioChoiceField/RadioChoiceField";
|
|
21
|
+
export { SrAnnouncer } from "./components/SrAnnouncer/SrAnnouncer";
|
|
22
|
+
export type { SrAnnouncerProps } from "./components/SrAnnouncer/SrAnnouncer";
|
|
23
|
+
export { TabButton, TabButtonLink, TabButtonList, } from "./components/TabButtons/TabButtonList";
|
|
24
|
+
export { VERSION } from "./VERSION";
|
|
25
|
+
export { VisuallyHidden } from "./components/VisuallyHidden/VisuallyHidden";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
export { default as styled } from "@emotion/styled";
|
|
3
|
+
export { css, Global } from "@emotion/react";
|
|
4
|
+
export { ThemeProvider, createTheme, } from "./components/ThemeProvider/ThemeProvider";
|
|
5
|
+
export { Alert } from "./components/Alert/Alert";
|
|
6
|
+
export { Button, ButtonLoadingIcon, ButtonLink, } from "./components/Button/Button";
|
|
7
|
+
export { ActionButton, ActionButtonLink, } from "./components/Button/ActionButton";
|
|
8
|
+
export { Input, AdornmentButton } from "./components/Input/Input";
|
|
9
|
+
export { TextField } from "./components/TextField/TextField";
|
|
10
|
+
export { Checkbox, childCheckboxStyles } from "./components/Checkbox/Checkbox";
|
|
11
|
+
export { CheckboxChoiceField } from "./components/CheckboxChoiceField/CheckboxChoiceField";
|
|
12
|
+
export { RadioChoiceField, BooleanRadioChoiceField, } from "./components/RadioChoiceField/RadioChoiceField";
|
|
13
|
+
export { SrAnnouncer } from "./components/SrAnnouncer/SrAnnouncer";
|
|
14
|
+
export { TabButton, TabButtonLink, TabButtonList, } from "./components/TabButtons/TabButtonList";
|
|
15
|
+
export { VERSION } from "./VERSION";
|
|
16
|
+
export { VisuallyHidden } from "./components/VisuallyHidden/VisuallyHidden";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import "@testing-library/jest-dom";
|
|
2
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
3
|
+
const failOnConsole = require("jest-fail-on-console");
|
|
4
|
+
failOnConsole();
|
|
5
|
+
beforeAll(() => {
|
|
6
|
+
const scrollBy = jest.fn();
|
|
7
|
+
HTMLElement.prototype.scrollBy = scrollBy;
|
|
8
|
+
});
|
|
9
|
+
afterEach(() => {
|
|
10
|
+
/**
|
|
11
|
+
* Clear all mock call counts between tests.
|
|
12
|
+
* This does NOT clear mock implementations.
|
|
13
|
+
* Mock implementations are always cleared between test files.
|
|
14
|
+
*/
|
|
15
|
+
jest.clearAllMocks();
|
|
16
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { TestEnvironment } from "jest-environment-jsdom";
|
|
2
|
+
import { EnvironmentContext, JestEnvironmentConfig } from "@jest/environment";
|
|
3
|
+
declare class JSDOMEnvironmentExtended extends TestEnvironment {
|
|
4
|
+
constructor(config: JestEnvironmentConfig, context: EnvironmentContext);
|
|
5
|
+
}
|
|
6
|
+
export default JSDOMEnvironmentExtended;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Jest environment extended with Web APIs para tranbajar con MSW
|
|
2
|
+
import { TestEnvironment } from "jest-environment-jsdom";
|
|
3
|
+
class JSDOMEnvironmentExtended extends TestEnvironment {
|
|
4
|
+
constructor(config, context) {
|
|
5
|
+
super(config, context);
|
|
6
|
+
this.global.TransformStream = TransformStream;
|
|
7
|
+
this.global.ReadableStream = ReadableStream;
|
|
8
|
+
this.global.Response = Response;
|
|
9
|
+
this.global.TextDecoderStream = TextDecoderStream;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export default JSDOMEnvironmentExtended;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type helper just to make sure an array contains union values.
|
|
3
|
+
*/
|
|
4
|
+
declare const enumValues: <T extends string | undefined>(obj: Record<NonNullable<T>, unknown>) => NonNullable<T>[];
|
|
5
|
+
declare const gitLink: (filepath: string) => string;
|
|
6
|
+
export { enumValues, gitLink };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A type helper just to make sure an array contains union values.
|
|
3
|
+
*/
|
|
4
|
+
const enumValues = (obj) => {
|
|
5
|
+
return Object.keys(obj);
|
|
6
|
+
};
|
|
7
|
+
const gitLink = (filepath) => {
|
|
8
|
+
if (!filepath.startsWith("src/")) {
|
|
9
|
+
throw new Error(`Invalid filepath: ${filepath}\nShould start with "src/"`);
|
|
10
|
+
}
|
|
11
|
+
return `https://github.com/mitodl/smoot-design/blob/${process.env.STORYBOOK_GIT_SHA}/${filepath}`;
|
|
12
|
+
};
|
|
13
|
+
export { enumValues, gitLink };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MutableRefObject, ForwardedRef, RefCallback } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Compose 2+ refs. Useful when a reusable component needs a ref itself, but
|
|
4
|
+
* consumers may also need the ref.
|
|
5
|
+
*/
|
|
6
|
+
declare const composeRefs: <T>(...refs: (ForwardedRef<T> | MutableRefObject<T | undefined> | RefCallback<T>)[]) => RefCallback<T>;
|
|
7
|
+
export { composeRefs };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compose 2+ refs. Useful when a reusable component needs a ref itself, but
|
|
3
|
+
* consumers may also need the ref.
|
|
4
|
+
*/
|
|
5
|
+
const composeRefs = (...refs) => {
|
|
6
|
+
return (value) => {
|
|
7
|
+
refs.forEach((ref) => {
|
|
8
|
+
if (typeof ref === "function") {
|
|
9
|
+
ref(value);
|
|
10
|
+
}
|
|
11
|
+
else if (ref) {
|
|
12
|
+
ref.current = value;
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export { composeRefs };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { composeRefs } from "./composeRefs";
|
|
3
|
+
import { render, screen } from "@testing-library/react";
|
|
4
|
+
describe("composeRefs", () => {
|
|
5
|
+
test("Composing object + fn ref", () => {
|
|
6
|
+
const objRef1 = React.createRef();
|
|
7
|
+
const objRef2 = React.createRef();
|
|
8
|
+
const fnRef1 = jest.fn();
|
|
9
|
+
const fnRef2 = jest.fn();
|
|
10
|
+
render(React.createElement("div", { "data-testid": "my-div", ref: composeRefs(objRef1, objRef2, fnRef1, fnRef2) }));
|
|
11
|
+
const el = screen.getByTestId("my-div");
|
|
12
|
+
expect(objRef1.current).toBe(el);
|
|
13
|
+
expect(objRef2.current).toBe(el);
|
|
14
|
+
expect(fnRef1).toHaveBeenCalledWith(el);
|
|
15
|
+
expect(fnRef2).toHaveBeenCalledWith(el);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A wrapper around fetch that retries the request on certain error codes.
|
|
3
|
+
*
|
|
4
|
+
* By default:
|
|
5
|
+
*
|
|
6
|
+
* Requests will be retried if max retries not exceeded and :
|
|
7
|
+
* - The status code is >= 400 AND NOT in the NO_RETRY_ERROR_CODES list,
|
|
8
|
+
* - OR the request promise rejected (network error)
|
|
9
|
+
*
|
|
10
|
+
* The retry delay is exponential, 1s, 2s, 4s, 8s, ... maxing at 30s.
|
|
11
|
+
*
|
|
12
|
+
* Maximum retries is 3.
|
|
13
|
+
*
|
|
14
|
+
* NOTE:
|
|
15
|
+
* - When NODE_ENV="test", the maximum retries is set 0 by default but can be
|
|
16
|
+
* set via the TEST_ENV_MAX_RETRIES environment variable.
|
|
17
|
+
*/
|
|
18
|
+
declare const retryingFetch: ((input: RequestInfo | URL, init?: RequestInit) => Promise<Response>) & typeof globalThis.fetch;
|
|
19
|
+
export default retryingFetch;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const MAX_RETRIES = 3;
|
|
2
|
+
const NO_RETRY_ERROR_CODES = [400, 401, 403, 404, 405, 409, 422];
|
|
3
|
+
const BASE_RETRY_DELAY = process.env.NODE_ENV === "test" ? 10 : 1000;
|
|
4
|
+
// This is a workaround to ensure retryingFetch uses MSW's fetch version in tests.
|
|
5
|
+
// See https://github.com/jonbern/fetch-retry/issues/95#issuecomment-2613990480
|
|
6
|
+
const fetch = (...args) => window.fetch(...args);
|
|
7
|
+
// From https://github.com/jonbern/fetch-retry/blob/master/index.js
|
|
8
|
+
const fetchRetry = (fetch, { retryDelay, retryOn }) => {
|
|
9
|
+
return (input, init) => {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
const wrappedFetch = (attempt) => {
|
|
12
|
+
const _input = typeof Request !== "undefined" && input instanceof Request
|
|
13
|
+
? input.clone()
|
|
14
|
+
: input;
|
|
15
|
+
fetch(_input, init)
|
|
16
|
+
.then((response) => {
|
|
17
|
+
try {
|
|
18
|
+
Promise.resolve(retryOn(attempt, null, response))
|
|
19
|
+
.then((retryOnResponse) => {
|
|
20
|
+
if (retryOnResponse) {
|
|
21
|
+
retry(attempt, null, response);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
resolve(response);
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
.catch(reject);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
reject(error);
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
.catch((error) => {
|
|
34
|
+
try {
|
|
35
|
+
Promise.resolve(retryOn(attempt, error, null))
|
|
36
|
+
.then((retryOnResponse) => {
|
|
37
|
+
if (retryOnResponse) {
|
|
38
|
+
retry(attempt, error, null);
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
reject(error);
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
.catch((error) => {
|
|
45
|
+
reject(error);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
reject(error);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
const retry = (attempt, error, response) => {
|
|
54
|
+
const delay = retryDelay(attempt, error, response);
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
wrappedFetch(++attempt);
|
|
57
|
+
}, delay);
|
|
58
|
+
};
|
|
59
|
+
wrappedFetch(0);
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* A wrapper around fetch that retries the request on certain error codes.
|
|
65
|
+
*
|
|
66
|
+
* By default:
|
|
67
|
+
*
|
|
68
|
+
* Requests will be retried if max retries not exceeded and :
|
|
69
|
+
* - The status code is >= 400 AND NOT in the NO_RETRY_ERROR_CODES list,
|
|
70
|
+
* - OR the request promise rejected (network error)
|
|
71
|
+
*
|
|
72
|
+
* The retry delay is exponential, 1s, 2s, 4s, 8s, ... maxing at 30s.
|
|
73
|
+
*
|
|
74
|
+
* Maximum retries is 3.
|
|
75
|
+
*
|
|
76
|
+
* NOTE:
|
|
77
|
+
* - When NODE_ENV="test", the maximum retries is set 0 by default but can be
|
|
78
|
+
* set via the TEST_ENV_MAX_RETRIES environment variable.
|
|
79
|
+
*/
|
|
80
|
+
const retryingFetch = fetchRetry(fetch, {
|
|
81
|
+
retryDelay: (attempt, _error, _response) => {
|
|
82
|
+
return Math.min(BASE_RETRY_DELAY * Math.pow(2, attempt), 30000);
|
|
83
|
+
},
|
|
84
|
+
retryOn: (attempt, _error, response) => {
|
|
85
|
+
if (attempt >= MAX_RETRIES) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
if (response) {
|
|
89
|
+
if (response.status < 400)
|
|
90
|
+
return false;
|
|
91
|
+
return !NO_RETRY_ERROR_CODES.includes(response.status);
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
export default retryingFetch;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import retryingFetch from "./retryingFetch";
|
|
11
|
+
import { http, HttpResponse } from "msw";
|
|
12
|
+
import { setupServer } from "msw/node";
|
|
13
|
+
const counter = jest.fn(); // use jest.fn as counter because it resets on each test
|
|
14
|
+
const NETWORK_SUCCESS_URL = "http://localhost:3456/success";
|
|
15
|
+
const NETWORK_ERROR_URL = "http://localhost:3456/error";
|
|
16
|
+
const server = setupServer(http.get(NETWORK_SUCCESS_URL, (_a) => __awaiter(void 0, [_a], void 0, function* ({ request }) {
|
|
17
|
+
var _b;
|
|
18
|
+
counter();
|
|
19
|
+
const url = new URL(request.url);
|
|
20
|
+
const status = +((_b = url.searchParams.get("status")) !== null && _b !== void 0 ? _b : 200);
|
|
21
|
+
return HttpResponse.text(`Status ${status}`, { status });
|
|
22
|
+
})), http.get(NETWORK_ERROR_URL, () => __awaiter(void 0, void 0, void 0, function* () {
|
|
23
|
+
counter();
|
|
24
|
+
return HttpResponse.error();
|
|
25
|
+
})));
|
|
26
|
+
beforeAll(() => server.listen());
|
|
27
|
+
afterEach(() => server.resetHandlers());
|
|
28
|
+
afterAll(() => server.close());
|
|
29
|
+
describe("retryingFetch", () => {
|
|
30
|
+
beforeAll(() => { });
|
|
31
|
+
test.each([200, 201, 202, 367, 400, 401, 403])("should not retry on %s", (status) => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
|
+
const result = yield retryingFetch(`${NETWORK_SUCCESS_URL}?status=${status}`);
|
|
33
|
+
expect(yield result.text()).toBe(`Status ${status}`);
|
|
34
|
+
expect(counter).toHaveBeenCalledTimes(1);
|
|
35
|
+
}));
|
|
36
|
+
test.each([429, 500, 501, 502, 503])("should retry on %s", (status) => __awaiter(void 0, void 0, void 0, function* () {
|
|
37
|
+
const result = yield retryingFetch(`${NETWORK_SUCCESS_URL}?status=${status}`);
|
|
38
|
+
expect(yield result.text()).toBe(`Status ${status}`);
|
|
39
|
+
expect(counter).toHaveBeenCalledTimes(4);
|
|
40
|
+
}));
|
|
41
|
+
test("should retry on error", () => __awaiter(void 0, void 0, void 0, function* () {
|
|
42
|
+
const result = yield retryingFetch(NETWORK_ERROR_URL).catch((err) => err);
|
|
43
|
+
expect(result).toBeInstanceOf(Error);
|
|
44
|
+
expect(counter).toHaveBeenCalledTimes(4);
|
|
45
|
+
}));
|
|
46
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Emits `console.error` if two subsequent values of `jsonSerializable` serialize
|
|
3
|
+
* to the same thing but are different references.
|
|
4
|
+
*
|
|
5
|
+
* This hook does NOT run in production.
|
|
6
|
+
*/
|
|
7
|
+
declare const useDevCheckStable: (jsonSerializable: unknown, msg?: string) => void;
|
|
8
|
+
export { useDevCheckStable };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Emits `console.error` if two subsequent values of `jsonSerializable` serialize
|
|
4
|
+
* to the same thing but are different references.
|
|
5
|
+
*
|
|
6
|
+
* This hook does NOT run in production.
|
|
7
|
+
*/
|
|
8
|
+
const useDevCheckStable = (jsonSerializable, msg = "The value has changed. This may cause unnecessary re-renders") => {
|
|
9
|
+
if (process.env.NODE_ENV !== "production") {
|
|
10
|
+
/**
|
|
11
|
+
* Calling hooks conditionally based on env vars is not really a problem.
|
|
12
|
+
* In a given environment, the hook will always run or always not run.
|
|
13
|
+
*/
|
|
14
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
15
|
+
const valRef = useRef(jsonSerializable);
|
|
16
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const sameJson = JSON.stringify(valRef.current) === JSON.stringify(jsonSerializable);
|
|
19
|
+
const differentRefs = valRef.current !== jsonSerializable;
|
|
20
|
+
if (!sameJson || differentRefs) {
|
|
21
|
+
console.error(`useDevCheckStable: ${msg}`, valRef.current, jsonSerializable);
|
|
22
|
+
}
|
|
23
|
+
}, [jsonSerializable, msg]);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
export { useDevCheckStable };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { useRef, useEffect } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Calls a function at a specified interval.
|
|
4
|
+
*
|
|
5
|
+
* Based on https://overreacted.io/making-setinterval-declarative-with-react-hooks/
|
|
6
|
+
*/
|
|
7
|
+
const useInterval = (callback, delay) => {
|
|
8
|
+
const savedCallback = useRef(null);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
savedCallback.current = callback;
|
|
11
|
+
}, [callback]);
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (delay !== null) {
|
|
14
|
+
const id = setInterval(() => { var _a; return (_a = savedCallback.current) === null || _a === void 0 ? void 0 : _a.call(savedCallback); }, delay);
|
|
15
|
+
return () => clearInterval(id);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
return () => { };
|
|
19
|
+
}
|
|
20
|
+
}, [delay]);
|
|
21
|
+
};
|
|
22
|
+
export { useInterval };
|