diff --git a/package.json b/package.json index 66b9089fa3..649662e37c 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "build:sb": "pnpm --filter @nextui-org/storybook build", "start:sb": "pnpm --filter @nextui-org/storybook start", "test": "jest --verbose --config ./jest.config.js", + "test:strict": "cross-env STRICT_MODE=true pnpm test", "typecheck": "turbo typecheck", "lint": "pnpm lint:pkg && pnpm lint:docs", "lint:pkg": "eslint -c .eslintrc.json ./packages/**/*.{ts,tsx}", @@ -54,7 +55,6 @@ "devDependencies": { "@babel/cli": "^7.14.5", "@babel/core": "^7.16.7", - "tsx": "^3.8.2", "@babel/plugin-proposal-object-rest-spread": "^7.15.6", "@babel/plugin-transform-runtime": "^7.14.5", "@babel/preset-env": "^7.14.5", @@ -69,6 +69,7 @@ "@react-bootstrap/babel-preset": "^2.1.0", "@react-types/link": "^3.4.4", "@react-types/shared": "3.23.1", + "@storybook/react": "^7.4.6", "@swc-node/jest": "^1.5.2", "@swc/core": "^1.3.35", "@swc/jest": "^0.2.24", @@ -85,10 +86,10 @@ "@types/testing-library__jest-dom": "5.14.5", "@typescript-eslint/eslint-plugin": "^5.42.0", "@typescript-eslint/parser": "^5.42.0", - "@storybook/react": "^7.4.6", "chalk": "^4.1.2", - "concurrently": "^7.6.0", "commitlint-plugin-function-rules": "^1.7.1", + "concurrently": "^7.6.0", + "cross-env": "^7.0.3", "eslint": "^7.29.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-airbnb-typescript": "^12.3.1", @@ -106,8 +107,6 @@ "eslint-plugin-react": "^7.23.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-unused-imports": "^2.0.0", - "npm-check-updates": "^16.10.18", - "intl-messageformat": "^10.1.0", "execa": "^5.1.1", "find-up": "^6.3.0", "fs-extra": "^10.0.0", @@ -115,10 +114,12 @@ "graceful-fs": "^4.2.6", "gray-matter": "^4.0.3", "husky": "^8.0.1", + "intl-messageformat": "^10.1.0", "jest": "^28.1.1", "jest-environment-jsdom": "^28.1.1", "jest-watch-typeahead": "1.1.0", "lint-staged": "^13.0.3", + "npm-check-updates": "^16.10.18", "npm-run-all": "^4.1.5", "p-iteration": "^1.1.8", "parcel": "^2.3.1", @@ -131,6 +132,7 @@ "rimraf": "^3.0.2", "shelljs": "^0.8.4", "tsup": "6.4.0", + "tsx": "^3.8.2", "turbo": "1.6.3", "typescript": "^4.9.5", "webpack": "^5.53.0", diff --git a/packages/storybook/.storybook/addons/react-strict-mode/index.tsx b/packages/storybook/.storybook/addons/react-strict-mode/index.tsx new file mode 100644 index 0000000000..aea3be9ef6 --- /dev/null +++ b/packages/storybook/.storybook/addons/react-strict-mode/index.tsx @@ -0,0 +1,29 @@ +import type { PropsWithChildren } from "react" + +import { addons, makeDecorator } from "@storybook/preview-api" +import { getQueryParams } from "@storybook/preview-api" +import React, { StrictMode, useEffect, useState } from "react" + +function StrictModeDecorator({ children }: PropsWithChildren) { + const [isStrict, setStrict] = useState(() => getQueryParams()?.strict === "true") + + useEffect(() => { + const channel = addons.getChannel() + + channel.on("strict/updated", setStrict) + + return () => { + channel.removeListener("strict/updated", setStrict) + } + }, []) + + return isStrict ? {children} : children +} + +export const withStrictModeSwitcher = makeDecorator({ + name: "withStrictModeSwitcher", + parameterName: "strictModeSwitcher", + wrapper: (getStory, context) => { + return {getStory(context)} + }, +}) diff --git a/packages/storybook/.storybook/addons/react-strict-mode/register.tsx b/packages/storybook/.storybook/addons/react-strict-mode/register.tsx new file mode 100644 index 0000000000..f53c70e3b1 --- /dev/null +++ b/packages/storybook/.storybook/addons/react-strict-mode/register.tsx @@ -0,0 +1,55 @@ +import type { API } from "@storybook/manager-api"; + +import { addons, types } from "@storybook/manager-api"; +import React, { useEffect, useState } from "react"; + +const ADDON_ID = "StrictModeSwitcher"; + +function StrictModeSwitcher({ api }: { api: API }) { + const [isStrict, setStrict] = useState(() => api.getQueryParam("strict") === "true"); + + const onChange = () => setStrict((val) => !val); + + useEffect(() => { + const channel = api.getChannel(); + + channel?.emit("strict/updated", isStrict); + + api.setQueryParams({ + strict: String(isStrict), + }); + }, [isStrict]); + + return ( +
+ + +
+ ); +} + +if (process.env.NODE_ENV !== "production") { + addons.register(ADDON_ID, (api) => { + addons.add(ADDON_ID, { + match: ({ viewMode }) => !!viewMode?.match(/^(story|docs)$/), + render: () => , + title: "Strict Mode Switcher", + type: types.TOOL, + }); + }); +} diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index fe71ab6901..fe52861392 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -11,14 +11,14 @@ module.exports = { getAbsolutePath("@storybook/addon-essentials"), getAbsolutePath("@storybook/addon-links"), getAbsolutePath("storybook-dark-mode"), - getAbsolutePath("@storybook/addon-mdx-gfm") + getAbsolutePath("@storybook/addon-mdx-gfm"), + "./addons/react-strict-mode/register", ], framework: { name: getAbsolutePath("@storybook/react-vite"), - options: {} }, core: { - disableTelemetry: true + disableTelemetry: true, }, typescript: { reactDocgen: false, diff --git a/packages/storybook/.storybook/preview.tsx b/packages/storybook/.storybook/preview.tsx index ce502c4407..258a65787d 100644 --- a/packages/storybook/.storybook/preview.tsx +++ b/packages/storybook/.storybook/preview.tsx @@ -4,6 +4,7 @@ import {NextUIProvider} from "@nextui-org/system/src/provider"; import type {Preview} from "@storybook/react"; import "./style.css"; +import { withStrictModeSwitcher } from "./addons/react-strict-mode"; const decorators: Preview["decorators"] = [ (Story, {globals: {locale, disableAnimation}}) => { @@ -19,6 +20,7 @@ const decorators: Preview["decorators"] = [ ); }, + ...(process.env.NODE_ENV !== "production" ? [withStrictModeSwitcher] : []), ]; const commonTheme = { diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 098421a155..cd34db68dd 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -44,6 +44,8 @@ "@storybook/addon-links": "^7.4.6", "@storybook/addon-mdx-gfm": "^7.4.6", "@storybook/cli": "^7.4.6", + "@storybook/manager-api": "^7.6.17", + "@storybook/preview-api": "^7.6.17", "@storybook/react": "^7.4.6", "@storybook/react-vite": "^7.4.6", "@storybook/theming": "^7.4.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c77c332de9..0b2d9aed6f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -120,6 +120,9 @@ importers: concurrently: specifier: ^7.6.0 version: 7.6.0 + cross-env: + specifier: ^7.0.3 + version: 7.0.3 eslint: specifier: ^7.29.0 version: 7.32.0 @@ -3634,6 +3637,12 @@ importers: '@storybook/cli': specifier: ^7.4.6 version: 7.6.17(encoding@0.1.13) + '@storybook/manager-api': + specifier: ^7.6.17 + version: 7.6.17(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + '@storybook/preview-api': + specifier: ^7.6.17 + version: 7.6.17 '@storybook/react': specifier: ^7.4.6 version: 7.6.17(encoding@0.1.13)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)(typescript@4.9.5) @@ -5201,9 +5210,11 @@ packages: '@humanwhocodes/config-array@0.5.0': resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead '@iconify/icons-solar@1.2.3': resolution: {integrity: sha512-dots93IzoaOrJ8aUD2YGZ4+Jy+yf5D87CmzSeBkEi/m+WX1klvHqWuw5kyZvVroLOlaIaJXb5nZVaDnhc8XJyQ==} @@ -8146,6 +8157,7 @@ packages: are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -9026,6 +9038,11 @@ packages: crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + cross-spawn@5.1.0: resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==} @@ -10385,6 +10402,7 @@ packages: gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -10487,13 +10505,16 @@ packages: glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + deprecated: Glob versions prior to v9 are no longer supported glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported global-dirs@0.1.1: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} @@ -10891,6 +10912,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -12646,6 +12668,7 @@ packages: npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. nprogress@0.2.0: resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} @@ -13516,6 +13539,7 @@ packages: read-package-json@6.0.4: resolution: {integrity: sha512-AEtWXYfopBj2z5N5PbkAOeNHRPUg5q+Nen7QLxV8M2zJq1ym6/lCz3fYNTCXe19puu2d06jfHhrP7v/S2PtMMw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + deprecated: This package is no longer supported. Please use @npmcli/package-json instead. read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} @@ -13811,14 +13835,17 @@ packages: rimraf@2.6.3: resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@5.0.5: @@ -20769,7 +20796,7 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.2.5 + '@types/node': 15.14.9 '@types/buble@0.20.5': dependencies: @@ -20789,7 +20816,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 20.2.5 + '@types/node': 15.14.9 '@types/cross-spawn@6.0.6': dependencies: @@ -20833,7 +20860,7 @@ snapshots: '@types/express-serve-static-core@4.19.0': dependencies: - '@types/node': 20.2.5 + '@types/node': 15.14.9 '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -21021,12 +21048,12 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.2.5 + '@types/node': 15.14.9 '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.2.5 + '@types/node': 15.14.9 '@types/send': 0.17.4 '@types/shelljs@0.8.15': @@ -22568,6 +22595,10 @@ snapshots: crelt@1.0.6: {} + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.3 + cross-spawn@5.1.0: dependencies: lru-cache: 4.1.5 @@ -27902,7 +27933,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.2.5 + '@types/node': 15.14.9 long: 5.2.3 proxy-addr@2.0.7: diff --git a/scripts/setup-test.ts b/scripts/setup-test.ts index 76ab90f892..7f8af3fedf 100644 --- a/scripts/setup-test.ts +++ b/scripts/setup-test.ts @@ -1,4 +1,5 @@ import "@testing-library/jest-dom/extend-expect"; +import { configure } from "@testing-library/react"; const {getComputedStyle} = window; window.getComputedStyle = (elt) => getComputedStyle(elt); @@ -29,3 +30,7 @@ global.ResizeObserver = jest.fn().mockImplementation(() => ({ unobserve: jest.fn(), disconnect: jest.fn(), })); + +configure({ + reactStrictMode: process.env.STRICT_MODE === "true", +});