diff --git a/.editorconfig b/.editorconfig index 5ce86bc41..87eabc131 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,8 +13,8 @@ max_line_length = 80 [*.md] trim_trailing_whitespace = false -[*.{css,html,js,jsx,json,rb,erb}] +[*.{css,html,js,json,rb,erb}] charset = utf-8 -[*.{html,jsx,erb,json}] +[*.{html,erb,json}] max_line_length = 160 diff --git a/.eslintrc.js b/.eslintrc.js index ad402e2a4..dab2bf4bd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -5,27 +5,13 @@ module.exports = { node: true, "cypress/globals": true, }, - extends: [ - "eslint:recommended", - "plugin:react/recommended", - "prettier", - "plugin:cypress/recommended", - "plugin:storybook/recommended", - ], + extends: ["eslint:recommended", "prettier", "plugin:cypress/recommended"], parserOptions: { - ecmaFeatures: { - jsx: true, - }, ecmaVersion: 12, sourceType: "module", }, - plugins: ["react", "cypress"], + plugins: ["cypress"], rules: {}, - settings: { - react: { - version: "detect", - }, - }, overrides: [ { files: ["*.ts", "*.tsx"], diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 87f9c7d4a..3374e6913 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -29,11 +29,11 @@ jobs: with: eslint: true eslint_dir: src - eslint_extensions: js,ts,jsx,tsx + eslint_extensions: js,ts,tsx eslint_auto_fix: false prettier: true prettier_dir: src - prettier_extensions: js,ts,jsx,tsx + prettier_extensions: js,ts,tsx prettier_auto_fix: false # Cypress disabled until tests rewritten to use new SB paths # - name: Cypress diff --git a/.prettierignore b/.prettierignore index fdbc0c9ad..28c048724 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,3 +3,4 @@ vendor ./reset preview node_modules +public \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json index e2b9a9442..de2d6fea1 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,7 +1,7 @@ { "overrides": [ { - "files": ["*.html", "*.jsx"], + "files": ["*.html"], "options": { "printWidth": 160, "htmlWhitespaceSensitivity": "strict" diff --git a/README.md b/README.md index 173763249..878090325 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,7 @@ All components live in `src` and follow a directory and filename convention: - component directory (TitleCase) - `component.js` - this is the entry file for a component and is the only required file - `components.css` - additional CSS - - for react, `components.jsx` + - for react, `components.tsx` For example: @@ -244,7 +244,7 @@ For example: - Accordion - component.js - component.css - - component.jsx + - component.tsx ``` #### CSS diff --git a/package.json b/package.json index 606579454..4b4a0a8e8 100644 --- a/package.json +++ b/package.json @@ -66,9 +66,9 @@ "build": "node scripts/build.js", "build:verbose": "node scripts/build.js -v", "watch": "node scripts/build.js -w", - "format:check": "yarn prettier -c *.{js,ts} src/**/*.{js,jsx,ts,tsx} cypress", - "format:write": "yarn prettier -w *.{js,ts} src/**/*.{js,jsx,ts,tsx} cypress", - "lint": "eslint *.{js,ts} src/**/*.{js,jsx,ts,tsx} cypress", + "format:check": "yarn prettier -c *.{js,ts} src/**/*.{js,ts,tsx} cypress", + "format:write": "yarn prettier -w *.{js,ts} src/**/*.{js,ts,tsx} cypress", + "lint": "eslint *.{js,ts} src/**/*.{js,ts,tsx} cypress", "cy:open": "cypress open", "cy:headless": "cypress run --quiet", "update:all": "./scripts/update-dependents.sh", @@ -87,7 +87,6 @@ "js-cookie": "^2.2.1", "lodash.throttle": "^4.1.1", "nanoid": "^4.0.0", - "prop-types": "^15.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", "redux": "^4.0.5", diff --git a/src/core/Code/Code.stories.tsx b/src/core/Code/Code.stories.tsx index 0a6454b64..484828a6c 100644 --- a/src/core/Code/Code.stories.tsx +++ b/src/core/Code/Code.stories.tsx @@ -1,4 +1,4 @@ -import Code from "./component.jsx"; +import Code from "./component.tsx"; export default { title: "Components/Code", diff --git a/src/core/Code/component.jsx b/src/core/Code/component.jsx deleted file mode 100644 index aee66ee71..000000000 --- a/src/core/Code/component.jsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; -import T from "prop-types"; - -import "../utils/syntax-highlighter.css"; -import { highlightSnippet, registerDefaultLanguages } from "../utils/syntax-highlighter"; -import languagesRegistry from "../utils/syntax-highlighter-registry"; - -registerDefaultLanguages(languagesRegistry); - -const Code = ({ language, snippet, textSize = "ui-text-code", padding = "p-32", additionalCSS = "" }) => { - const HTMLraw = highlightSnippet(language, `${snippet}`.trim()); - const className = `language-${language} ${textSize}`; - - return ( -
-
-        
-      
-
- ); -}; - -Code.propTypes = { - language: T.string, - snippet: T.string, - textSize: T.string, - padding: T.string, - additionalCSS: T.string, -}; - -export default Code; diff --git a/src/core/Code/component.tsx b/src/core/Code/component.tsx new file mode 100644 index 000000000..f479f778b --- /dev/null +++ b/src/core/Code/component.tsx @@ -0,0 +1,45 @@ +import React from "react"; + +import "../utils/syntax-highlighter.css"; +import { + highlightSnippet, + registerDefaultLanguages, +} from "../utils/syntax-highlighter"; +import languagesRegistry from "../utils/syntax-highlighter-registry"; + +registerDefaultLanguages(languagesRegistry); + +type CodeProps = { + language: string; + snippet: string; + textSize?: string; + padding?: string; + additionalCSS?: string; +}; + +const Code = ({ + language, + snippet, + textSize = "ui-text-code", + padding = "p-32", + additionalCSS = "", +}: CodeProps) => { + const HTMLraw = highlightSnippet(language, `${snippet}`.trim()) ?? ""; + const className = `language-${language} ${textSize}`; + + return ( +
+
+        
+      
+
+ ); +}; + +export default Code; diff --git a/src/core/ConnectStateWrapper/component.jsx b/src/core/ConnectStateWrapper/component.tsx similarity index 81% rename from src/core/ConnectStateWrapper/component.jsx rename to src/core/ConnectStateWrapper/component.tsx index 275a05f3c..3a4bc0572 100644 --- a/src/core/ConnectStateWrapper/component.jsx +++ b/src/core/ConnectStateWrapper/component.tsx @@ -12,14 +12,18 @@ import { connectState, getRemoteDataStore } from "../remote-data-store"; - initial state is set in useEffect so the wrapped component needs to handle it's props set to undefined initially */ -export const ConnectStateWrapper = (Component, selectors) => { +const ConnectStateWrapper = (Component, selectors) => { const [state, setState] = useState({}); - const setStateForKey = (key) => (storeState) => setState(() => ({ [key]: storeState })); + const setStateForKey = (key) => (storeState) => + setState(() => ({ [key]: storeState })); useEffect(() => { const store = getRemoteDataStore(); - const resolvedState = Object.keys(selectors).reduce((acc, key) => ({ ...acc, [key]: selectors[key](store) }), {}); + const resolvedState = Object.keys(selectors).reduce( + (acc, key) => ({ ...acc, [key]: selectors[key](store) }), + {} + ); // Set initial state setState(resolvedState); diff --git a/src/core/ContactFooter/ContactFooter.stories.tsx b/src/core/ContactFooter/ContactFooter.stories.tsx index 233f7f56f..ee83a6089 100644 --- a/src/core/ContactFooter/ContactFooter.stories.tsx +++ b/src/core/ContactFooter/ContactFooter.stories.tsx @@ -1,4 +1,4 @@ -import ContactFooter from "./component.jsx"; +import ContactFooter from "./component.tsx"; export default { title: "Components/Contact Footer", diff --git a/src/core/ContactFooter/component.jsx b/src/core/ContactFooter/component.jsx deleted file mode 100644 index b0399c66b..000000000 --- a/src/core/ContactFooter/component.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { useEffect } from "react"; -import T from "prop-types"; - -import Icon from "../Icon/component.jsx"; -import _absUrl from "../url-base"; - -import toggleChatWidget from "./component.js"; - -export default function ContactFooter({ urlBase }) { - useEffect(() => toggleChatWidget(), []); - const absUrl = (path) => _absUrl(path, urlBase); - - return ( -
-
-
- -
-
Live Chat
-

Reach out team of experts over chat powered by Ably.

-
- -
- -
- -
-
Call us
-

- - +1 877 434 5287 (USA, toll free) - - - +44 20 3318 4689 (UK) - -

-
-
- -
- -
-
Technical and account support
-

We're standing by to help with any questions or code.

-
- - Get support now - -
-
-
- ); -} - -ContactFooter.propTypes = { - urlBase: T.string, -}; diff --git a/src/core/ContactFooter/component.tsx b/src/core/ContactFooter/component.tsx new file mode 100644 index 000000000..438a7da28 --- /dev/null +++ b/src/core/ContactFooter/component.tsx @@ -0,0 +1,92 @@ +import React, { useEffect } from "react"; + +import Icon from "../Icon/component.tsx"; +import _absUrl from "../url-base.js"; +import toggleChatWidget from "../hubspot-chat-toggle"; +import "./component.css"; + +type ContactFooterProps = { + urlBase: string; +}; + +const ContactFooter = ({ urlBase }: ContactFooterProps) => { + useEffect(() => toggleChatWidget({ dataId: "contact-footer" }), []); + const absUrl = (path) => _absUrl(path, urlBase); + + return ( +
+
+
+ +
+
Live Chat
+

+ Reach out team of experts over chat powered by Ably. +

+
+ +
+ +
+ +
+
Call us
+

+ + +1 877 434 5287 (USA, + toll free) + + + +44 20 3318 4689 (UK) + +

+
+
+ +
+ +
+
+ Technical and account support +
+

+ We're standing by to help with any questions or code. +

+
+ + Get support now + +
+
+
+ ); +}; + +export default ContactFooter; diff --git a/src/core/CookieMessage/CookieMessage.stories.tsx b/src/core/CookieMessage/CookieMessage.stories.tsx index 72b141410..d43e57291 100644 --- a/src/core/CookieMessage/CookieMessage.stories.tsx +++ b/src/core/CookieMessage/CookieMessage.stories.tsx @@ -1,4 +1,4 @@ -import CookieMessage from "./component.jsx"; +import CookieMessage from "./component.tsx"; export default { title: "Components/Cookie Message", diff --git a/src/core/CookieMessage/component.css b/src/core/CookieMessage/component.css index 6d6844b0c..41bbae259 100644 --- a/src/core/CookieMessage/component.css +++ b/src/core/CookieMessage/component.css @@ -1,13 +1,11 @@ -@layer components { - .ui-cookie-message { - @apply rounded-lg bg-white font-sans; - @apply justify-between items-center; - @apply opacity-100 z-50 bottom-0 fixed sm:flex; - @apply p-16 mb-16 ml-16; - max-width: 70vw; - box-shadow: 0px 24px 32px 0px #0000000d; - border: 1px solid var(--color-mid-grey); - border-left: 0.5rem solid var(--color-electric-cyan); - transition: bottom 250ms ease-out, opacity 150ms ease-out; - } +.ui-cookie-message { + @apply rounded-lg bg-white font-sans; + @apply justify-between items-center; + @apply opacity-100 z-50 bottom-0 fixed sm:flex; + @apply p-16 mb-16 ml-16; + max-width: 70vw; + box-shadow: 0px 24px 32px 0px #0000000d; + border: 1px solid var(--color-mid-grey); + border-left: 0.5rem solid var(--color-electric-cyan); + transition: bottom 250ms ease-out, opacity 150ms ease-out; } diff --git a/src/core/CookieMessage/component.jsx b/src/core/CookieMessage/component.tsx similarity index 69% rename from src/core/CookieMessage/component.jsx rename to src/core/CookieMessage/component.tsx index 512c4720f..a61af340a 100644 --- a/src/core/CookieMessage/component.jsx +++ b/src/core/CookieMessage/component.tsx @@ -1,13 +1,18 @@ import React, { useRef, useEffect, useState } from "react"; -import T from "prop-types"; import Cookie from "js-cookie"; +import "./component.css"; import _absUrl from "../url-base"; const COOKIE_EXPIRY = 365; -export default function CookieMessage({ cookieId, urlBase }) { - const ref = useRef(null); +type CookieMessageProps = { + cookieId: string; + urlBase: string; +}; + +const CookieMessage = ({ cookieId, urlBase }: CookieMessageProps) => { + const ref = useRef(null); const [hideCookieMessage, setHideCookieMessage] = useState(true); useEffect(() => { @@ -18,7 +23,7 @@ export default function CookieMessage({ cookieId, urlBase }) { const handleClose = () => { Cookie.set(cookieId, "1", { expires: COOKIE_EXPIRY }); - ref.current.classList.add("bottom-1", "opacity-0"); + ref.current?.classList.add("bottom-1", "opacity-0"); setTimeout(() => setHideCookieMessage(true), 500); }; @@ -35,14 +40,14 @@ export default function CookieMessage({ cookieId, urlBase }) { {" "} to improve your experience.

- ); -} - -CookieMessage.propTypes = { - cookieId: T.string, - urlBase: T.string, }; + +export default CookieMessage; diff --git a/src/core/CustomerLogos/CustomerLogos.stories.tsx b/src/core/CustomerLogos/CustomerLogos.stories.tsx index 190d72d40..3f5b49931 100644 --- a/src/core/CustomerLogos/CustomerLogos.stories.tsx +++ b/src/core/CustomerLogos/CustomerLogos.stories.tsx @@ -1,4 +1,4 @@ -import CustomerLogos from "./component.jsx"; +import CustomerLogos from "./component.tsx"; import hubspot from "../images/cust-logo-hubspot-mono-pos.svg"; import webflow from "../images/cust-logo-webflow-col-pos.svg"; diff --git a/src/core/CustomerLogos/component.jsx b/src/core/CustomerLogos/component.jsx deleted file mode 100644 index 3a7059330..000000000 --- a/src/core/CustomerLogos/component.jsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from "react"; -import T from "prop-types"; - -const CustomerLogos = ({ companies, additionalCss = "" }) => { - return ( -
-
    - {companies.map((company) => ( -
  • - {company.label} -
  • - ))} -
-
- ); -}; - -CustomerLogos.propTypes = { - companies: T.arrayOf( - T.shape({ - label: T.string, - logo: T.string, - }) - ), - additionalCss: T.string, -}; - -export default CustomerLogos; diff --git a/src/core/CustomerLogos/component.tsx b/src/core/CustomerLogos/component.tsx new file mode 100644 index 000000000..93aa33e22 --- /dev/null +++ b/src/core/CustomerLogos/component.tsx @@ -0,0 +1,35 @@ +import React from "react"; + +type CompanyEntity = { + label: string; + logo: string; +}; + +type CustomerLogosProps = { + companies: CompanyEntity[]; + additionalCss?: string; +}; + +const CustomerLogos = ({ + companies, + additionalCss = "", +}: CustomerLogosProps) => { + return ( +
+
    + {companies.map((company) => ( +
  • + {company.label} +
  • + ))} +
+
+ ); +}; + +export default CustomerLogos; diff --git a/src/core/DropdownMenu/DropdownMenu.stories.tsx b/src/core/DropdownMenu/DropdownMenu.stories.tsx index 48d768c02..255301399 100644 --- a/src/core/DropdownMenu/DropdownMenu.stories.tsx +++ b/src/core/DropdownMenu/DropdownMenu.stories.tsx @@ -1,6 +1,6 @@ import React from "react"; -import Icon from "../Icon/component.jsx"; -import DropdownMenu from "./component.jsx"; +import Icon from "../Icon/component.tsx"; +import DropdownMenu from "./component.tsx"; export default { title: "Components/Dropdown Menu", diff --git a/src/core/DropdownMenu/component.jsx b/src/core/DropdownMenu/component.tsx similarity index 54% rename from src/core/DropdownMenu/component.jsx rename to src/core/DropdownMenu/component.tsx index 2ab254271..69d4429d7 100644 --- a/src/core/DropdownMenu/component.jsx +++ b/src/core/DropdownMenu/component.tsx @@ -1,16 +1,52 @@ -import React, { createContext, useContext, useState, useEffect, useRef } from "react"; -import T from "prop-types"; -import Icon from "../Icon/component.jsx"; +import React, { + createContext, + useContext, + useState, + useEffect, + useRef, + ReactNode, + Dispatch, +} from "react"; +import Icon from "../Icon/component.tsx"; + +const DropdownMenuContext = createContext<{ + isOpen: boolean; + setOpen: Dispatch>; +}>({ + isOpen: false, + setOpen: () => {}, +}); + +type DropdownMenuProps = { + children: ReactNode; +}; + +type TriggerProps = { + children: ReactNode; + additionalTriggerCSS?: string; +}; + +type ContentProps = { + children: ReactNode; + anchorPosition?: string; + additionalContentCSS?: string; +}; -const DropdownMenuContext = createContext(); +type LinkProps = { + url: string; + title: string; + subtitle: string; + iconName: string; + children: ReactNode; +}; -const DropdownMenu = ({ children }) => { +const DropdownMenu = ({ children }: DropdownMenuProps) => { const [isOpen, setOpen] = useState(false); - const ref = useRef(null); + const ref = useRef(null); useEffect(() => { const clickHandler = (e) => { - if (ref?.current?.contains(e.target)) return; + if (ref.current?.contains(e.target)) return; setOpen(false); }; @@ -30,11 +66,7 @@ const DropdownMenu = ({ children }) => { ); }; -DropdownMenu.propTypes = { - children: T.node, -}; - -const Trigger = ({ children, additionalTriggerCSS = "" }) => { +const Trigger = ({ children, additionalTriggerCSS = "" }: TriggerProps) => { const { isOpen, setOpen } = useContext(DropdownMenuContext); return ( @@ -54,19 +86,19 @@ const Trigger = ({ children, additionalTriggerCSS = "" }) => { ); }; -Trigger.propTypes = { - children: T.node, - additionalTriggerCSS: T.string, -}; - -const Content = ({ children, anchorPosition = "right", additionalContentCSS }) => { +const Content = ({ + children, + anchorPosition = "right", + additionalContentCSS, +}: ContentProps) => { const { isOpen } = useContext(DropdownMenuContext); if (!isOpen) { return null; } - const anchorPositionClasses = anchorPosition === "right" ? "right-0" : "left-0"; + const anchorPositionClasses = + anchorPosition === "right" ? "right-0" : "left-0"; return (
{ +const Link = ({ url, title, subtitle, iconName, children }: LinkProps) => { return ( - +

{title} - {iconName ? : null} + {iconName ? ( + + ) : null}

{subtitle ?

{subtitle}

: null} {children} @@ -98,14 +134,6 @@ const Link = ({ url, title, subtitle, iconName, children }) => { ); }; -Link.propTypes = { - url: T.string, - title: T.string, - subtitle: T.string, - iconName: T.string, - children: T.node, -}; - DropdownMenu.Trigger = Trigger; DropdownMenu.Content = Content; DropdownMenu.Link = Link; diff --git a/src/core/FeaturedLink/FeaturedLink.stories.tsx b/src/core/FeaturedLink/FeaturedLink.stories.tsx index db75f0b94..1b6ff6c72 100644 --- a/src/core/FeaturedLink/FeaturedLink.stories.tsx +++ b/src/core/FeaturedLink/FeaturedLink.stories.tsx @@ -1,4 +1,4 @@ -import FeaturedLink from "./component.jsx"; +import FeaturedLink from "./component.tsx"; export default { title: "Components/Featured Link", diff --git a/src/core/FeaturedLink/component.jsx b/src/core/FeaturedLink/component.tsx similarity index 73% rename from src/core/FeaturedLink/component.jsx rename to src/core/FeaturedLink/component.tsx index 694683688..39d02259d 100644 --- a/src/core/FeaturedLink/component.jsx +++ b/src/core/FeaturedLink/component.tsx @@ -1,13 +1,26 @@ -import React from "react"; -import T from "prop-types"; +import React, { CSSProperties, ReactNode } from "react"; -import Icon from "../Icon/component.jsx"; +import Icon from "../Icon/component.tsx"; + +type FeaturedLinkProps = { + url: string; + children: ReactNode; + textSize?: string; + iconColor?: string; + flush?: boolean; + reverse?: boolean; + additionalCSS?: string; + newWindow?: boolean; + onClick?: () => void; +}; + +type TargetProps = { target?: string; rel?: string }; // When generating links with target=_blank, we only add `noreferrer` to // links that don't start with `/`, so we can continue tracking referrers // across our own domains const buildTargetAndRel = (url, newWindow) => { - const props = {}; + const props: TargetProps = {}; if (newWindow) { props.target = "_blank"; @@ -32,7 +45,7 @@ const FeaturedLink = ({ newWindow = false, onClick = undefined, children, -}) => { +}: FeaturedLinkProps) => { const targetAndRel = buildTargetAndRel(url, newWindow); return ( @@ -41,7 +54,14 @@ const FeaturedLink = ({ className={`font-sans font-bold block text-gui-default hover:text-gui-hover focus:text-gui-focus focus:outline-gui-focus group ui-${textSize} ${ flush ? "" : "py-8" } ${additionalCSS}`} - style={{ "--featured-link-icon-size": `var(${textSize.replace("text", "--fs")})` }} + style={ + { + "--featured-link-icon-size": `var(${textSize.replace( + "text", + "--fs" + )})`, + } as CSSProperties + } {...targetAndRel} onClick={onClick} > @@ -70,16 +90,4 @@ const FeaturedLink = ({ ); }; -FeaturedLink.propTypes = { - url: T.string, - children: T.node, - textSize: T.string, - iconColor: T.string, - flush: T.bool, - reverse: T.bool, - additionalCSS: T.string, - newWindow: T.bool, - onClick: T.func, -}; - export default FeaturedLink; diff --git a/src/core/Flash/Flash.stories.tsx b/src/core/Flash/Flash.stories.tsx index 7e3311126..e571a0e25 100644 --- a/src/core/Flash/Flash.stories.tsx +++ b/src/core/Flash/Flash.stories.tsx @@ -1,5 +1,5 @@ import React from "react"; -import Flash, { reducerFlashes } from "./component.jsx"; +import Flash, { reducerFlashes } from "./component.tsx"; import { attachStoreToWindow, diff --git a/src/core/Flash/component.jsx b/src/core/Flash/component.tsx similarity index 60% rename from src/core/Flash/component.jsx rename to src/core/Flash/component.tsx index 91221702a..e1429a2f3 100644 --- a/src/core/Flash/component.jsx +++ b/src/core/Flash/component.tsx @@ -1,13 +1,28 @@ import React, { useEffect, useState, useRef } from "react"; import DOMPurify from "dompurify"; -import T from "prop-types"; import { nanoid } from "nanoid/non-secure"; import { getRemoteDataStore } from "../remote-data-store.js"; -import ConnectStateWrapper from "../ConnectStateWrapper/component.jsx"; -import Icon from "../Icon/component.jsx"; +import ConnectStateWrapper from "../ConnectStateWrapper/component.tsx"; +import Icon from "../Icon/component.tsx"; import "./component.css"; +type FlashProps = { + id: string; + removed: boolean; + type: "error" | "success" | "notice" | "info" | "alert"; + content: string; + removeFlash: (id: string) => void; +}; + +type FlashesProps = { + flashes: { items: FlashProps[] }; +}; + +type BackendFlashesProps = { + flashes: string[][]; +}; + const REDUCER_KEY = "flashes"; const FLASH_DATA_ID = "ui-flashes"; @@ -17,7 +32,9 @@ const reducerFlashes = { [REDUCER_KEY]: (state = initialState, action) => { switch (action.type) { case "flash/push": { - const flashes = Array.isArray(action.payload) ? action.payload : [action.payload]; + const flashes = Array.isArray(action.payload) + ? action.payload + : [action.payload]; return { items: [...state.items, ...flashes] }; } default: @@ -28,11 +45,6 @@ const reducerFlashes = { const selectFlashes = (store) => store.getState()[REDUCER_KEY]; -const FlashT = { - type: T.oneOf(["error", "success", "notice", "info", "alert"]), - content: T.string, -}; - const FLASH_BG_COLOR = { error: "bg-gui-error", success: "bg-zingy-green", @@ -53,7 +65,7 @@ const AUTO_HIDE = ["success", "info", "notice"]; const AUTO_HIDE_TIME = 8000; const useAutoHide = (type, closeFlash) => { - const timeoutId = useRef(null); + const timeoutId = useRef | null>(null); useEffect(() => { if (AUTO_HIDE.includes(type)) { @@ -70,8 +82,8 @@ const useAutoHide = (type, closeFlash) => { }, []); }; -const Flash = ({ id, type, content, removeFlash }) => { - const ref = useRef(null); +const Flash = ({ id, type, content, removeFlash }: FlashProps) => { + const ref = useRef(null); const [closed, setClosed] = useState(false); const [flashHeight, setFlashHeight] = useState(0); const [triggerEntryAnimation, setTriggerEntryAnimation] = useState(false); @@ -84,7 +96,7 @@ const Flash = ({ id, type, content, removeFlash }) => { setClosed(true); setTimeout(() => { - removeFlash(id); + id && removeFlash(id); }, 100); }; @@ -123,30 +135,62 @@ const Flash = ({ id, type, content, removeFlash }) => { }; return ( -
-
- {withIcons[type] && } -

-

); }; -Flash.propTypes = { - ...FlashT, -}; - -const Flashes = ({ flashes }) => { - const [flashesWithIds, setFlashesWithIds] = useState([]); +const Flashes = ({ flashes }: FlashesProps) => { + const [flashesWithIds, setFlashesWithIds] = useState([]); - const removeFlash = (flashId) => setFlashesWithIds((items) => items.filter((item) => item.id !== flashId)); + const removeFlash = (flashId) => + setFlashesWithIds((items) => items.filter((item) => item.id !== flashId)); useEffect(() => { setFlashesWithIds((state) => { - return [...state, ...(flashes?.items || []).map((flash) => ({ ...flash, id: nanoid(), removed: false }))]; + return [ + ...state, + ...(flashes?.items).map((flash) => ({ + ...flash, + id: nanoid(), + removed: false, + })), + ]; }); }, [flashes]); @@ -155,17 +199,13 @@ const Flashes = ({ flashes }) => { {flashesWithIds .filter((item) => !item.removed) .map((flash) => ( - + ))}
); }; -Flashes.propTypes = { - flashes: T.shape({ items: T.arrayOf(T.shape(FlashT)) }), -}; - -const BackendFlashes = ({ flashes }) => { +const BackendFlashes = ({ flashes }: BackendFlashesProps) => { useEffect(() => { const transformedFlashes = flashes.map((flash) => { @@ -183,14 +223,12 @@ const BackendFlashes = ({ flashes }) => { } }, []); - const WrappedFlashes = ConnectStateWrapper(Flashes, { flashes: selectFlashes }); + const WrappedFlashes = ConnectStateWrapper(Flashes, { + flashes: selectFlashes, + }); return ; }; -BackendFlashes.propTypes = { - flashes: T.arrayOf(T.arrayOf(T.string)), -}; - export { reducerFlashes, FLASH_DATA_ID, Flashes }; export default BackendFlashes; diff --git a/src/core/Footer/Footer.stories.tsx b/src/core/Footer/Footer.stories.tsx index 8de0e823f..c1e091a37 100644 --- a/src/core/Footer/Footer.stories.tsx +++ b/src/core/Footer/Footer.stories.tsx @@ -1,4 +1,4 @@ -import Footer from "./component.jsx"; +import Footer from "./component.tsx"; import ablyStack from "../images/ably-stack.svg"; import highestPerformer from "../images/high-performer-2023.svg"; diff --git a/src/core/Footer/component.jsx b/src/core/Footer/component.tsx similarity index 58% rename from src/core/Footer/component.jsx rename to src/core/Footer/component.tsx index 1f3a061e5..4ba85af2b 100644 --- a/src/core/Footer/component.jsx +++ b/src/core/Footer/component.tsx @@ -1,45 +1,77 @@ import React from "react"; -import T from "prop-types"; -import Icon from "../Icon/component.jsx"; -import _absUrl from "../url-base"; +import Icon from "../Icon/component.tsx"; +import _absUrl from "../url-base.js"; import "./component.css"; -export default function Footer({ paths, urlBase }) { +type FooterProps = { + paths: { + ablyStack: string; + highestPerformer: string; + highestUserAdoption: string; + bestSupport: string; + fastestImplementation: string; + }; + urlBase: string; +}; + +const Footer = ({ paths, urlBase }: FooterProps) => { const absUrl = (path) => _absUrl(path, urlBase); return ( -