From f5356ccce87c2e76f95e6507617f41eb3605dc57 Mon Sep 17 00:00:00 2001 From: aphilibeaux Date: Thu, 13 Nov 2025 00:24:47 +0100 Subject: [PATCH 1/3] Revert "chore(devdeps): update oxlint monorepo to v1.16.0 (#5566)" This reverts commit 88a45771499dba1d5d19f2a9b0ae9351eb63c2c9. --- .oxlintrc.json | 179 ++++++++---------- biome.json | 1 - eslint.config.mjs | 1 - examples/next-app-router/app/layout.tsx | 1 - examples/next/package.json | 2 +- examples/next/src/components/Link.tsx | 2 + examples/next/src/pages/_document.tsx | 1 - packages/form/package.json | 2 +- .../form/src/providers/ErrorContext/index.tsx | 1 - .../EstimateCost/EstimateCostProvider.tsx | 1 - .../EstimateCost/OverlayContext.tsx | 1 - .../Navigation/NavigationProvider.tsx | 2 - .../components/Navigation/components/Item.tsx | 3 +- .../Navigation/components/ItemProvider.tsx | 1 - .../Navigation/components/PinnedItems.tsx | 5 +- .../OfferList/OfferListProvider.tsx | 1 - .../OfferList/__stories__/Example.stories.tsx | 2 +- .../OrderSummary/{helpers.ts => helpers.tsx} | 0 packages/themes/src/ThemeProvider.tsx | 1 - .../src/__stories__/Properties/Properties.tsx | 6 +- .../Tools/ThemeGenerator/contants.ts | 2 +- .../ui/src/components/Avatar/constants.ts | 2 +- .../ui/src/components/BarChart/Tooltip.tsx | 8 +- .../BarChart/__tests__/Tooltip.test.tsx | 2 +- packages/ui/src/components/BarChart/index.tsx | 2 +- .../Button/__stories__/Size.stories.tsx | 5 +- .../Button/__tests__/index.test.tsx | 4 +- .../ui/src/components/Button/constants.ts | 3 - packages/ui/src/components/Button/index.tsx | 5 + .../__stories__/Size.stories.tsx | 3 +- .../components/ExpandableCard/constants.ts | 1 - .../src/components/ExpandableCard/index.tsx | 3 +- .../Key/__stories__/SpeciaKeys.stories.tsx | 3 +- .../components/Key/__tests__/index.test.tsx | 3 +- packages/ui/src/components/Key/constants.ts | 8 - packages/ui/src/components/Key/index.tsx | 10 +- .../ui/src/components/List/ColumnProvider.tsx | 1 - .../ui/src/components/List/ListContext.tsx | 1 - .../List/__stories__/Example.stories.tsx | 2 +- .../List/__stories__/Ordering.stories.tsx | 2 +- packages/ui/src/components/List/index.tsx | 2 +- .../Loader/__stories__/Sizes.stories.tsx | 3 +- .../Loader/__tests__/index.test.tsx | 3 +- .../ui/src/components/Loader/constants.ts | 8 - .../ui/src/components/Menu/MenuProvider.tsx | 4 +- .../src/components/Menu/components/Item.tsx | 4 +- packages/ui/src/components/Menu/index.tsx | 2 +- .../ui/src/components/Modal/ModalProvider.tsx | 2 - .../components/Notification/Notification.tsx | 57 ------ .../Notification/NotificationContainer.tsx | 59 ------ .../ui/src/components/Notification/index.ts | 2 - .../ui/src/components/Notification/index.tsx | 115 +++++++++++ .../ui/src/components/PieChart/Legends.tsx | 11 +- .../ui/src/components/PieChart/Tooltip.tsx | 4 +- .../PieChart/__tests__/index.test.tsx | 1 + packages/ui/src/components/PieChart/index.tsx | 2 +- packages/ui/src/components/Row/styles.css.ts | 12 +- .../SelectInput/SelectInputProvider.tsx | 1 - .../components/SearchBarDropdown.tsx | 14 +- packages/ui/src/components/Skeleton/index.tsx | 1 - .../ui/src/components/Stack/styles.css.ts | 4 +- .../components/Stepper/StepperProvider.tsx | 1 - packages/ui/src/components/Stepper/index.tsx | 4 +- .../ui/src/components/Table/TableContext.tsx | 1 - .../Table/__stories__/Ordering.stories.tsx | 2 +- packages/ui/src/components/Table/index.tsx | 2 +- packages/ui/src/components/TagList/index.tsx | 2 +- .../Text/__stories__/Variants.stories.tsx | 3 +- .../components/Text/__tests__/index.test.tsx | 3 +- packages/ui/src/components/Text/constant.ts | 6 + packages/ui/src/components/Text/constants.ts | 12 -- packages/ui/src/components/Text/index.tsx | 5 +- packages/ui/src/components/Text/style.css.ts | 2 +- .../ui/src/components/Toaster/Toaster.tsx | 42 ---- .../components/Toaster/ToasterContainer.tsx | 72 ------- .../Toaster/components/CloseButton.tsx | 28 --- .../components/Toaster/components/Content.tsx | 21 -- .../ui/src/components/Toaster/constants.ts | 2 - packages/ui/src/components/Toaster/index.ts | 10 - packages/ui/src/components/Toaster/index.tsx | 160 ++++++++++++++++ .../__stories__/Size.stories.tsx | 3 +- .../components/VerificationCode/constants.ts | 4 - .../src/components/VerificationCode/index.tsx | 7 +- packages/ui/src/helpers/legend.ts | 2 +- packages/ui/src/hooks/useIsOverflowing.ts | 3 +- packages/ui/src/theme/ThemeProvider.tsx | 1 - utils/scripts/analyse-deps.ts | 4 +- utils/scripts/figma-synchronise-tokens.tsx | 11 +- vite.config.ts | 2 +- 89 files changed, 464 insertions(+), 545 deletions(-) rename packages/plus/src/components/OrderSummary/{helpers.ts => helpers.tsx} (100%) delete mode 100644 packages/ui/src/components/ExpandableCard/constants.ts delete mode 100644 packages/ui/src/components/Key/constants.ts delete mode 100644 packages/ui/src/components/Loader/constants.ts delete mode 100644 packages/ui/src/components/Notification/Notification.tsx delete mode 100644 packages/ui/src/components/Notification/NotificationContainer.tsx delete mode 100644 packages/ui/src/components/Notification/index.ts create mode 100644 packages/ui/src/components/Notification/index.tsx create mode 100644 packages/ui/src/components/Text/constant.ts delete mode 100644 packages/ui/src/components/Text/constants.ts delete mode 100644 packages/ui/src/components/Toaster/Toaster.tsx delete mode 100644 packages/ui/src/components/Toaster/ToasterContainer.tsx delete mode 100644 packages/ui/src/components/Toaster/components/CloseButton.tsx delete mode 100644 packages/ui/src/components/Toaster/components/Content.tsx delete mode 100644 packages/ui/src/components/Toaster/constants.ts delete mode 100644 packages/ui/src/components/Toaster/index.ts create mode 100644 packages/ui/src/components/Toaster/index.tsx diff --git a/.oxlintrc.json b/.oxlintrc.json index 5184e2c6be..d0913ecd34 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,14 +1,5 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", - "categories": { - "correctness": "deny", - "nursery": "off", - "pedantic": "deny", - "perf": "deny", - "restriction": "deny", - "style": "deny", - "suspicious": "deny" - }, "ignorePatterns": [ "**/app.config.d.ts", "**/package.config.d.ts", @@ -17,23 +8,32 @@ "*.d.ts", "svgo.config.mjs" ], + "categories": { + "correctness": "deny", + "style": "deny", + "suspicious": "deny", + "perf": "deny", + "pedantic": "deny", + "restriction": "deny", + "nursery": "off" + }, + "plugins": [ + "import", + "node", + "oxc", + "react", + "typescript", + "unicorn", + "jsx-a11y" + ], "overrides": [ { - "files": [ - "**/__stories__/**/*.{ts,tsx}", - "**/__tests__/**/*.{ts,tsx}", - ".storybook/**/*.{ts,tsx}", - "exemples/**", - ".storybook/**/*.{ts,tsx}" - ], + "files": ["**/__stories__/**/*.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}", ".storybook/**/*.{ts,tsx}"], "rules": { - "eslint/no-alert": "off", "eslint/no-console": "off", + "eslint/no-alert": "off", "import/no-anonymous-default-export": "off", - "import/no-named-export": "off", - "import/no-unassigned-import": "off", - "react/jsx-pascal-case": "off", - "react/only-export-components": "off" + "import/no-unassigned-import": "off" } }, { @@ -45,40 +45,46 @@ "setup.ts", "vitest.setup.ts", "**/vitest.setup.ts", - "*.config.ts", - "**/vitest/setup.ts" + "*.config.ts" ], "plugins": ["import", "oxc", "vitest"], "rules": { "@typescript-eslint/consistent-type-imports": "off", + "import/no-namespace": "off", "import/export": "off", "import/no-anonymous-default-export": "off", - "import/no-named-export": "off", - "import/no-namespace": "off", "import/no-unassigned-import": "off", "jsx_a11y/label-has-associated-control": "off", - "no-console": "off", - "node/no-process-env": "off", - "vitest/prefer-lowercase-title": "error" - } - }, - { - "files": ["e2e/**/*.{ts,tsx}"], - "rules": { - "node/no-process-env": "off" + "vitest/prefer-lowercase-title": "error", + "no-console": "off" } } ], - "plugins": [ - "import", - "node", - "oxc", - "react", - "typescript", - "unicorn", - "jsx-a11y" - ], "rules": { + "jsx_a11y/label-has-associated-control": "off", + "jsx_a11y/no-autofocus": "off", + "jsx_a11y/prefer-tag-over-role": "off", + "import/default": "error", + "import/export": "off", + "import/exports-last": "off", + "import/max-dependencies": "off", + "import/named": "error", + "import/no-amd": "error", + "import/consistent-type-specifier-style": "error", + "import/group-exports": "off", + "import/import-no-namespace": "off", + "import/namespace": "off", + "import/no-cycle": "error", + "import/no-default-export": "off", + "import/no-duplicates": "error", + "import/no-named-as-default-member": "error", + "import/no-named-as-default": "error", + "import/no-self-import": "error", + "import/no-unassigned-import": "off", + "import/no-unused-modules": "off", + "import/prefer-default-export": "off", + "import/no-namespace": "off", + "import/unambiguous": "error", "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": "error", "@typescript-eslint/ban-ts-comment": "error", @@ -91,8 +97,8 @@ "@typescript-eslint/no-duplicate-enum-values": "error", "@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/no-extra-non-null-assertion": "error", "@typescript-eslint/no-magic-numbers": "off", + "@typescript-eslint/no-extra-non-null-assertion": "error", "@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-non-null-asserted-optional-chain": "error", @@ -100,38 +106,33 @@ "@typescript-eslint/no-this-alias": "error", "@typescript-eslint/no-unnecessary-type-constraint": "error", "@typescript-eslint/no-unsafe-declaration-merging": "error", - "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-var-requires": "error", "@typescript-eslint/prefer-as-const": "error", + "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/prefer-enum-initializers": "off", "@typescript-eslint/prefer-for-of": "error", "@typescript-eslint/prefer-function-type": "off", "@typescript-eslint/prefer-literal-enum-member": "off", "@typescript-eslint/prefer-ts-expect-error": "off", "@typescript-eslint/triple-slash-reference": "error", - "arrow-body-style": "off", + "eslint/yoda": "error", "eslint/array-callback-return": "error", "eslint/constructor-super": "error", "eslint/default-case-last": "error", "eslint/default-param-last": "off", "eslint/eqeqeq": "error", "eslint/for-direction": "error", - "eslint/func-style": "off", "eslint/getter-return": "error", "eslint/guard-for-in": "error", - "eslint/id-length": "off", - "eslint/init-declarations": "off", "eslint/max-classes-per-file": "error", - "eslint/max-depth": "off", "eslint/max-lines": "off", - "eslint/max-lines-per-function": "off", - "eslint/max-nested-callbacks": "off", "eslint/max-params": "off", "eslint/no-array-constructor": "error", "eslint/no-async-promise-executor": "error", "eslint/no-await-in-loop": "off", "eslint/no-bitwise": "error", "eslint/no-caller": "error", + "eslint/id-length": "off", "eslint/no-case-declarations": "error", "eslint/no-class-assign": "error", "eslint/no-compare-neg-zero": "error", @@ -149,11 +150,11 @@ "eslint/no-dupe-keys": "error", "eslint/no-duplicate-case": "error", "eslint/no-duplicate-imports": "off", - "eslint/no-empty": "error", "eslint/no-empty-character-class": "error", "eslint/no-empty-function": "off", "eslint/no-empty-pattern": "error", "eslint/no-empty-static-block": "error", + "eslint/no-empty": "error", "eslint/no-eq-null": "error", "eslint/no-eval": "error", "eslint/no-ex-assign": "error", @@ -194,6 +195,8 @@ "eslint/no-unsafe-optional-chaining": "error", "eslint/no-unused-labels": "error", "eslint/no-unused-private-class-members": "error", + "eslint/sort-keys": "off", + "eslint/react-in-jsx-scope": "off", "eslint/no-unused-vars": [ "error", { @@ -207,7 +210,6 @@ "eslint/no-void": "error", "eslint/no-with": "error", "eslint/radix": "error", - "eslint/react-in-jsx-scope": "off", "eslint/require-await": "off", "eslint/require-yield": "error", "eslint/sort-imports": [ @@ -217,37 +219,14 @@ "memberSyntaxSortOrder": ["single", "multiple", "all", "none"] } ], - "eslint/sort-keys": "off", "eslint/unicode-bom": "error", "eslint/use-isnan": "error", "eslint/valid-typeof": "error", - "eslint/yoda": "error", - "import/consistent-type-specifier-style": "error", - "import/default": "error", - "import/export": "off", - "import/exports-last": "off", - "import/group-exports": "off", - "import/import-no-namespace": "off", - "import/max-dependencies": "off", - "import/named": "error", - "import/namespace": "off", - "import/no-amd": "error", - "import/no-anonymous-default-export": "off", - "import/no-cycle": "error", - "import/no-default-export": "off", - "import/no-duplicates": "error", - "import/no-named-as-default": "error", - "import/no-named-as-default-member": "error", - "import/no-named-export": "off", - "import/no-namespace": "off", - "import/no-self-import": "error", - "import/no-unassigned-import": "off", - "import/no-unused-modules": "off", - "import/prefer-default-export": "off", - "import/unambiguous": "error", - "jsx_a11y/label-has-associated-control": "off", - "jsx_a11y/no-autofocus": "off", - "jsx_a11y/prefer-tag-over-role": "off", + "eslint/init-declarations": "off", + "eslint/func-style": "off", + "eslint/max-lines-per-function": "off", + "eslint/max-nested-callbacks": "off", + "eslint/max-depth": "off", "oxc/bad-bitwise-operator": "error", "oxc/no-accumulating-spread": "off", "oxc/no-async-await": "off", @@ -255,25 +234,23 @@ "oxc/no-const-enum": "error", "oxc/no-optional-chaining": "off", "oxc/no-rest-spread-properties": "off", - "prefer-destructuring": "off", - "react_perf/jsx-no-jsx-as-prop": "error", - "react_perf/jsx-no-new-array-as-prop": "error", - "react_perf/jsx-no-new-function-as-props": "error", - "react_perf/jsx-no-new-object-as-prop": "error", "react-perf/jsx-no-jsx-as-prop": "off", "react-perf/jsx-no-new-array-as-prop": "off", "react-perf/jsx-no-new-function-as-prop": "off", "react-perf/jsx-no-new-object-as-prop": "off", + "react_perf/jsx-no-jsx-as-prop": "error", + "react_perf/jsx-no-new-array-as-prop": "error", + "react_perf/jsx-no-new-function-as-props": "error", + "react_perf/jsx-no-new-object-as-prop": "error", "react/button-has-type": "off", "react/exhaustive-deps": "error", - "react/iframe-missing-sandbox": "error", "react/jsx-filename-extension": [ "error", { "extensions": [".jsx", ".tsx"] } ], - "react/jsx-handler-names": "off", + "react/iframe-missing-sandbox": "error", "react/jsx-key": "error", "react/jsx-no-comment-text-nodes": "error", "react/jsx-no-duplicate-props": "error", @@ -281,7 +258,6 @@ "react/jsx-no-undef": "error", "react/jsx-no-useless-fragment": "off", "react/no-children-prop": "error", - "react/no-danger": "off", "react/no-dangerously-set-inner-html": "error", "react/no-direct-mutation-state": "error", "react/no-find-dom-node": "error", @@ -293,7 +269,13 @@ "react/no-unknown-property": "error", "react/react-in-jsx-scope": "off", "react/require-render-return": "error", - "typescript/explicit-module-boundary-types": "off", + "react/no-danger": "off", + "unicorn/no-instanceof-builtins": "error", + "unicorn/prefer-array-index-of": "off", + "unicorn/prefer-array-find": "off", + "unicorn/no-for-loop": "off", + "unicorn/prefer-object-from-entries": "off", + "unicorn/prefer-global-this": "off", "unicorn/catch-error-name": "error", "unicorn/empty-brace-spaces": "error", "unicorn/error-message": "off", @@ -307,13 +289,12 @@ "unicorn/no-array-reduce": "off", "unicorn/no-await-expression-member": "off", "unicorn/no-await-in-promise-methods": "off", + "unicorn/prefer-set-has": "off", "unicorn/no-console-spaces": "error", "unicorn/no-document-cookie": "off", "unicorn/no-empty-file": "error", - "unicorn/no-for-loop": "off", "unicorn/no-hex-escape": "error", "unicorn/no-instanceof-array": "error", - "unicorn/no-instanceof-builtins": "error", "unicorn/no-invalid-remove-event-listener": "off", "unicorn/no-lonely-if": "error", "unicorn/no-magic-array-flat-depth": "off", @@ -342,10 +323,8 @@ "unicorn/number-literal-case": "off", "unicorn/numeric-separators-style": "off", "unicorn/prefer-add-event-listener": "off", - "unicorn/prefer-array-find": "off", "unicorn/prefer-array-flat": "error", "unicorn/prefer-array-flat-map": "error", - "unicorn/prefer-array-index-of": "off", "unicorn/prefer-array-some": "off", "unicorn/prefer-at": "warn", "unicorn/prefer-blob-reading-methods": "off", @@ -356,7 +335,6 @@ "unicorn/prefer-dom-node-remove": "off", "unicorn/prefer-dom-node-text-content": "error", "unicorn/prefer-event-target": "error", - "unicorn/prefer-global-this": "off", "unicorn/prefer-includes": "error", "unicorn/prefer-logical-operator-over-ternary": "off", "unicorn/prefer-math-trunc": "error", @@ -365,13 +343,11 @@ "unicorn/prefer-native-coercion-functions": "error", "unicorn/prefer-node-protocol": "off", "unicorn/prefer-number-properties": "error", - "unicorn/prefer-object-from-entries": "off", "unicorn/prefer-optional-catch-binding": "error", "unicorn/prefer-prototype-methods": "error", "unicorn/prefer-query-selector": "off", "unicorn/prefer-reflect-apply": "error", "unicorn/prefer-regexp-test": "error", - "unicorn/prefer-set-has": "off", "unicorn/prefer-set-size": "error", "unicorn/prefer-spread": "error", "unicorn/prefer-string-replace-all": "off", @@ -383,7 +359,12 @@ "unicorn/require-number-to-fixed-digits-argument": "error", "unicorn/switch-case-braces": "error", "unicorn/text-encoding-identifier-case": "error", - "unicorn/throw-new-error": "error" + "unicorn/throw-new-error": "error", + "arrow-body-style": "off", + "typescript/explicit-module-boundary-types": "off", + "prefer-destructuring": "off", + "import/no-anonymous-default-export": "off", + "react/jsx-handler-names": "off" }, "settings": { "jsx-a11y": { diff --git a/biome.json b/biome.json index 4f6da6eed1..349a86393b 100644 --- a/biome.json +++ b/biome.json @@ -11,7 +11,6 @@ }, "includes": [ "!**/package.json", - ".oxlintrc.json", "**/*.ts", "**/*.tsx", "!**/dist/**", diff --git a/eslint.config.mjs b/eslint.config.mjs index dfe1f6800f..9210630714 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,7 +16,6 @@ const dirname = path.dirname(filename) const disableRules = { // ---- biome rules ---- 'import/order': 'off', - "sort-imports": "off", 'import/no-unresolved': 'off', '@stylistic/no-extra-semi': 'off', '@stylistic/brace-style': 'off', diff --git a/examples/next-app-router/app/layout.tsx b/examples/next-app-router/app/layout.tsx index b362f30316..f896609c23 100644 --- a/examples/next-app-router/app/layout.tsx +++ b/examples/next-app-router/app/layout.tsx @@ -8,7 +8,6 @@ import '@ultraviolet/fonts/fonts.css' import '@ultraviolet/ui/styles' import '@ultraviolet/themes/global' -// oxlint-disable-next-line react/only-export-components export const metadata: Metadata = { description: 'Design System', title: 'Ultraviolet', diff --git a/examples/next/package.json b/examples/next/package.json index 937ec1da11..7b6b73900a 100644 --- a/examples/next/package.json +++ b/examples/next/package.json @@ -31,7 +31,7 @@ "schema-dts": "1.1.5" }, "devDependencies": { - "@babel/core": "7.28.5", + "@babel/core": "7.28.4", "@types/node": "22.18.11", "@types/react": "19.2.6", "@types/react-syntax-highlighter": "15.5.13", diff --git a/examples/next/src/components/Link.tsx b/examples/next/src/components/Link.tsx index eedc975f62..10cf5a9ab7 100644 --- a/examples/next/src/components/Link.tsx +++ b/examples/next/src/components/Link.tsx @@ -34,3 +34,5 @@ export const Link = forwardRef( ), ) + +export default Link diff --git a/examples/next/src/pages/_document.tsx b/examples/next/src/pages/_document.tsx index 6682b2ecc1..0aacc41404 100644 --- a/examples/next/src/pages/_document.tsx +++ b/examples/next/src/pages/_document.tsx @@ -3,7 +3,6 @@ import { jsonLdScriptProps } from 'react-schemaorg' import type { CreativeWork } from 'schema-dts' class MyDocument extends Document { - // eslint-disable-next-line class-methods-use-this override render() { return ( diff --git a/packages/form/package.json b/packages/form/package.json index cf43bb59d7..703377e1bf 100644 --- a/packages/form/package.json +++ b/packages/form/package.json @@ -68,7 +68,7 @@ "react-dom": "18.x || 19.x" }, "devDependencies": { - "@babel/core": "7.28.5", + "@babel/core": "7.28.4", "@types/final-form-focus": "1.1.7", "@types/react": "19.2.6", "@types/react-dom": "19.2.3", diff --git a/packages/form/src/providers/ErrorContext/index.tsx b/packages/form/src/providers/ErrorContext/index.tsx index b175edb4ba..006343c445 100644 --- a/packages/form/src/providers/ErrorContext/index.tsx +++ b/packages/form/src/providers/ErrorContext/index.tsx @@ -45,7 +45,6 @@ export const ErrorProvider = ({ children, errors }: ErrorProviderProps) => { return {children} } -// oxlint-disable-next-line react/only-export-components export const useErrors = () => { const context = useContext(ErrorContext) diff --git a/packages/plus/src/components/EstimateCost/EstimateCostProvider.tsx b/packages/plus/src/components/EstimateCost/EstimateCostProvider.tsx index 49ae466408..4de7ef5057 100644 --- a/packages/plus/src/components/EstimateCost/EstimateCostProvider.tsx +++ b/packages/plus/src/components/EstimateCost/EstimateCostProvider.tsx @@ -9,7 +9,6 @@ const EstimateCostContext = createContext<{ formatNumber: (number: number, options: FormatNumberOption) => string }>({ formatNumber: () => '', locales: EstimateCostLocales }) -// oxlint-disable-next-line react/only-export-components export const useEstimateCost = () => useContext(EstimateCostContext) type EstimateCostProviderProps = { diff --git a/packages/plus/src/components/EstimateCost/OverlayContext.tsx b/packages/plus/src/components/EstimateCost/OverlayContext.tsx index d0cba00b99..9ffe17f31a 100644 --- a/packages/plus/src/components/EstimateCost/OverlayContext.tsx +++ b/packages/plus/src/components/EstimateCost/OverlayContext.tsx @@ -4,7 +4,6 @@ import type { ReactNode } from 'react' import { createContext, useContext } from 'react' const OverlayContext = createContext({ isOverlay: false }) -// oxlint-disable-next-line react/only-export-components export const useOverlay = () => useContext(OverlayContext) type OverlayContextProviderProps = { diff --git a/packages/plus/src/components/Navigation/NavigationProvider.tsx b/packages/plus/src/components/Navigation/NavigationProvider.tsx index 47c5c16754..f06945dbeb 100644 --- a/packages/plus/src/components/Navigation/NavigationProvider.tsx +++ b/packages/plus/src/components/Navigation/NavigationProvider.tsx @@ -62,7 +62,6 @@ type ContextProps = { animationType?: AnimationType } -// oxlint-disable-next-line react/only-export-components export const NavigationContext = createContext({ allowNavigationResize: true, animation: false, @@ -89,7 +88,6 @@ export const NavigationContext = createContext({ width: NAVIGATION_WIDTH, }) -// oxlint-disable-next-line react/only-export-components export const useNavigation = () => useContext(NavigationContext) type NavigationProviderProps = { diff --git a/packages/plus/src/components/Navigation/components/Item.tsx b/packages/plus/src/components/Navigation/components/Item.tsx index d56037503a..5275bd28e5 100644 --- a/packages/plus/src/components/Navigation/components/Item.tsx +++ b/packages/plus/src/components/Navigation/components/Item.tsx @@ -148,6 +148,7 @@ type ItemProps = { } const onDragStopTrigger = (event: DragEvent) => { + // eslint-disable-next-line no-param-reassign event.currentTarget.style.opacity = '1' } @@ -317,7 +318,7 @@ export const Item = memo( 'text/plain', JSON.stringify({ index, label }), ) - + // eslint-disable-next-line no-param-reassign event.currentTarget.style.opacity = '0.5' } diff --git a/packages/plus/src/components/Navigation/components/ItemProvider.tsx b/packages/plus/src/components/Navigation/components/ItemProvider.tsx index e8d88a8c77..3eccabbb73 100644 --- a/packages/plus/src/components/Navigation/components/ItemProvider.tsx +++ b/packages/plus/src/components/Navigation/components/ItemProvider.tsx @@ -4,7 +4,6 @@ import type { ReactNode } from 'react' import { createContext, useMemo } from 'react' // Create the context with a default value -// oxlint-disable-next-line react/only-export-components export const ItemContext = createContext(false) type ItemProviderProps = { diff --git a/packages/plus/src/components/Navigation/components/PinnedItems.tsx b/packages/plus/src/components/Navigation/components/PinnedItems.tsx index 8433b66325..7eeed88aa1 100644 --- a/packages/plus/src/components/Navigation/components/PinnedItems.tsx +++ b/packages/plus/src/components/Navigation/components/PinnedItems.tsx @@ -65,6 +65,7 @@ export const PinnedItems = ({ (event: DragEvent, index: number) => { event.preventDefault() if (event?.dataTransfer) { + // eslint-disable-next-line no-param-reassign event.currentTarget.style.borderColor = 'transparent' const data = JSON.parse( event.dataTransfer.getData('text'), @@ -85,7 +86,7 @@ export const PinnedItems = ({ const onDragOver = useCallback( (event: DragEvent) => { event.preventDefault() - + // eslint-disable-next-line no-param-reassign event.currentTarget.style.borderColor = theme.colors.primary.border }, [theme.colors.primary.border], @@ -93,7 +94,7 @@ export const PinnedItems = ({ const onDragLeave = useCallback((event: DragEvent) => { event.preventDefault() - + // eslint-disable-next-line no-param-reassign event.currentTarget.style.borderColor = 'transparent' }, []) diff --git a/packages/plus/src/components/OfferList/OfferListProvider.tsx b/packages/plus/src/components/OfferList/OfferListProvider.tsx index 032838c5a4..7e56e41d1e 100644 --- a/packages/plus/src/components/OfferList/OfferListProvider.tsx +++ b/packages/plus/src/components/OfferList/OfferListProvider.tsx @@ -62,7 +62,6 @@ export const OfferListProvider = ({ ) -// oxlint-disable-next-line react/only-export-components export const useOfferListContext = () => { const context = useContext(OfferListContext) diff --git a/packages/plus/src/components/OfferList/__stories__/Example.stories.tsx b/packages/plus/src/components/OfferList/__stories__/Example.stories.tsx index 39eb60e7ff..399bdd5e93 100644 --- a/packages/plus/src/components/OfferList/__stories__/Example.stories.tsx +++ b/packages/plus/src/components/OfferList/__stories__/Example.stories.tsx @@ -62,7 +62,7 @@ export const Example: StoryFn> = props => { const sortedData = useMemo(() => { const orderMultiplicator = currentOrder.order === 'asc' ? 1 : -1 - return [...data].toSorted((a, b) => { + return [...data].sort((a, b) => { if (a[currentOrder.columnId] < b[currentOrder.columnId]) { return -1 * orderMultiplicator } diff --git a/packages/plus/src/components/OrderSummary/helpers.ts b/packages/plus/src/components/OrderSummary/helpers.tsx similarity index 100% rename from packages/plus/src/components/OrderSummary/helpers.ts rename to packages/plus/src/components/OrderSummary/helpers.tsx diff --git a/packages/themes/src/ThemeProvider.tsx b/packages/themes/src/ThemeProvider.tsx index 044eb42cd7..a7a06f66b6 100644 --- a/packages/themes/src/ThemeProvider.tsx +++ b/packages/themes/src/ThemeProvider.tsx @@ -11,7 +11,6 @@ const ThemeContext = createContext(consoleLightTheme) /** * Provide an object of the theme variables. */ -// oxlint-disable-next-line react/only-export-components export const useTheme = () => { const context = useContext(ThemeContext) if (!context) { diff --git a/packages/ui/src/__stories__/Properties/Properties.tsx b/packages/ui/src/__stories__/Properties/Properties.tsx index 04aa67bf15..e564352736 100644 --- a/packages/ui/src/__stories__/Properties/Properties.tsx +++ b/packages/ui/src/__stories__/Properties/Properties.tsx @@ -95,7 +95,7 @@ const Properties = () => { const sortedPropertiesUsagesCountAndComponentsName = Object.entries( propertiesUsagesCountAndComponentsName, ) - .toSorted(([, { count: countA }], [, { count: countB }]) => countB - countA) + .sort(([, { count: countA }], [, { count: countB }]) => countB - countA) .reduce>( (acc, [property, value]) => ({ ...acc, @@ -163,10 +163,10 @@ const Properties = () => { } const reversedLocalProperty = [...lowerCaseLocalProperty] - .toReversed() + .reverse() .join('') const reversedLowerCaseProperty = [...lowerCaseProperty] - .toReversed() + .reverse() .join('') for ( diff --git a/packages/ui/src/__stories__/Tools/ThemeGenerator/contants.ts b/packages/ui/src/__stories__/Tools/ThemeGenerator/contants.ts index 6a5a26ed00..f50d40f358 100644 --- a/packages/ui/src/__stories__/Tools/ThemeGenerator/contants.ts +++ b/packages/ui/src/__stories__/Tools/ThemeGenerator/contants.ts @@ -15,7 +15,7 @@ export const SHADES_KEYS = [ '1200', '1300', '1400', -].toReversed() +].reverse() /** * This is the mapping between shades name and sentiments names. diff --git a/packages/ui/src/components/Avatar/constants.ts b/packages/ui/src/components/Avatar/constants.ts index 6c35f92876..487aee0ddd 100644 --- a/packages/ui/src/components/Avatar/constants.ts +++ b/packages/ui/src/components/Avatar/constants.ts @@ -11,7 +11,7 @@ export const TEXT_VARIANT_BY_SIZE = { // Match the container size with actual px size export const sizes = (theme: typeof UVTheme) => ({ - large: '7rem', // Note: add this value to tokens + large: '7rem', // TODO: add this value to tokens medium: theme.sizing['800'], small: theme.sizing['400'], xsmall: theme.sizing['250'], diff --git a/packages/ui/src/components/BarChart/Tooltip.tsx b/packages/ui/src/components/BarChart/Tooltip.tsx index 0fde8fb0b3..9f2f3a4670 100644 --- a/packages/ui/src/components/BarChart/Tooltip.tsx +++ b/packages/ui/src/components/BarChart/Tooltip.tsx @@ -4,7 +4,7 @@ import { assignInlineVars } from '@vanilla-extract/dynamic' import { Text } from '../Text' import { barColorSquare, barTooltipContainer, colorBar } from './styles.css' -type BarChartTooltipProps = { +type BarChartToolTipProps = { color: string indexValue: string formattedValue: string @@ -12,13 +12,13 @@ type BarChartTooltipProps = { 'data-testid'?: string } -export const BarChartTooltip = ({ +const BarChartToolTip = ({ formattedValue, indexValue, color, className, 'data-testid': dataTestId, -}: BarChartTooltipProps) => ( +}: BarChartToolTipProps) => (
) + +export default BarChartToolTip diff --git a/packages/ui/src/components/BarChart/__tests__/Tooltip.test.tsx b/packages/ui/src/components/BarChart/__tests__/Tooltip.test.tsx index 9c4a257307..7396fdd01a 100644 --- a/packages/ui/src/components/BarChart/__tests__/Tooltip.test.tsx +++ b/packages/ui/src/components/BarChart/__tests__/Tooltip.test.tsx @@ -1,6 +1,6 @@ import { shouldMatchSnapshot } from '@utils/test' import { describe, test } from 'vitest' -import { BarChartTooltip } from '../Tooltip' +import BarChartTooltip from '../Tooltip' describe('barChartTooltip', () => { test('renders correctly', () => diff --git a/packages/ui/src/components/BarChart/index.tsx b/packages/ui/src/components/BarChart/index.tsx index e6a1366d0c..5bce6e6309 100644 --- a/packages/ui/src/components/BarChart/index.tsx +++ b/packages/ui/src/components/BarChart/index.tsx @@ -9,7 +9,7 @@ import type { ComponentProps, CSSProperties } from 'react' import { useCallback } from 'react' import { getLegendColor } from '../../helpers/legend' import { getNivoTheme } from '../../helpers/nivoTheme' -import { BarChartTooltip } from './Tooltip' +import BarChartTooltip from './Tooltip' type Formatter = ValueFormat diff --git a/packages/ui/src/components/Button/__stories__/Size.stories.tsx b/packages/ui/src/components/Button/__stories__/Size.stories.tsx index ec6202b30d..d2fad49623 100644 --- a/packages/ui/src/components/Button/__stories__/Size.stories.tsx +++ b/packages/ui/src/components/Button/__stories__/Size.stories.tsx @@ -1,12 +1,11 @@ import type { StoryFn } from '@storybook/react-vite' import { PencilIcon } from '@ultraviolet/icons' import { Stack } from '../..' -import { Button } from '..' -import { SIZE_KEY } from '../constants' +import { Button, buttonSizes } from '..' export const Size: StoryFn = args => ( - {SIZE_KEY.map(size => ( + {buttonSizes.map(size => ( -) - -export const notification = ( - children: ((props: CloseButtonProps) => ReactNode) | ReactNode, - title: string, - icon?: ReactNode, - isClosable?: boolean, - containerId?: string, - options?: ToastOptions, -) => - baseToast('', { - ...options, - closeButton: props => ( - -
{icon}
- - - {title} - - {typeof children === 'function' ? children(props) : children} - - {isClosable ? closeButton(props) : null} -
- ), - containerId: containerId ?? 'notification', - }) diff --git a/packages/ui/src/components/Notification/NotificationContainer.tsx b/packages/ui/src/components/Notification/NotificationContainer.tsx deleted file mode 100644 index 6ca18b0a97..0000000000 --- a/packages/ui/src/components/Notification/NotificationContainer.tsx +++ /dev/null @@ -1,59 +0,0 @@ -'use client' - -import type { CSSProperties } from 'react' -import type { ToastOptions } from 'react-toastify' -import { ToastContainer as BaseToastContainer, Slide } from 'react-toastify' -import { notification as notificationStyle } from './styles.css' - -type NotificationContainerProps = { - /** - * Delay (in ms) before the notification autocloses. To disable autoclose, set to false - */ - autoClose?: false | number - /** - * Whether to display the newest toast on top. - * `Default: false` - */ - newestOnTop?: boolean - /** - * Limit the number of toast displayed at the same time - */ - limit?: number - /** - * Position on the notification container - */ - position?: ToastOptions['position'] - 'data-testid'?: string - className?: string - /** - * Give a personalized containerId in case there are multiple notifications with different styled to display - */ - containerId?: string - style?: CSSProperties -} - -export const NotificationContainer = ({ - newestOnTop, - limit, - autoClose = false, - position = 'top-right', - 'data-testid': dataTestId, - className, - style, - containerId = 'notification', -}: NotificationContainerProps) => ( - -) diff --git a/packages/ui/src/components/Notification/index.ts b/packages/ui/src/components/Notification/index.ts deleted file mode 100644 index 043a353e2c..0000000000 --- a/packages/ui/src/components/Notification/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { notification } from './Notification' -export { NotificationContainer } from './NotificationContainer' diff --git a/packages/ui/src/components/Notification/index.tsx b/packages/ui/src/components/Notification/index.tsx new file mode 100644 index 0000000000..b4332d81f3 --- /dev/null +++ b/packages/ui/src/components/Notification/index.tsx @@ -0,0 +1,115 @@ +'use client' + +import { CloseIcon } from '@ultraviolet/icons' +import type { CSSProperties, ReactNode } from 'react' +import type { + Theme as ThemeToastify, + ToastOptions, + TypeOptions, +} from 'react-toastify' +import { + ToastContainer as BaseToastContainer, + toast as baseToast, + Slide, +} from 'react-toastify' +import { Button } from '../Button' +import { Stack } from '../Stack' +import { Text } from '../Text' +import { notification as notificationStyle } from './styles.css' + +type CloseButtonProps = { + closeToast: (event: React.MouseEvent) => void + type: TypeOptions + ariaLabel?: string + theme: ThemeToastify +} + +const closeButton = (props: CloseButtonProps) => ( + +) + +export const notification = ( + children: ((props: CloseButtonProps) => ReactNode) | ReactNode, + title: string, + icon?: ReactNode, + isClosable?: boolean, + containerId?: string, + options?: ToastOptions, +) => + baseToast('', { + ...options, + closeButton: props => ( + +
{icon}
+ + + {title} + + {typeof children === 'function' ? children(props) : children} + + {isClosable ? closeButton(props) : null} +
+ ), + containerId: containerId ?? 'notification', + }) + +type NotificationContainerProps = { + /** + * Delay (in ms) before the notification autocloses. To disable autoclose, set to false + */ + autoClose?: false | number + /** + * Whether to display the newest toast on top. + * `Default: false` + */ + newestOnTop?: boolean + /** + * Limit the number of toast displayed at the same time + */ + limit?: number + /** + * Position on the notification container + */ + position?: ToastOptions['position'] + 'data-testid'?: string + className?: string + /** + * Give a personalized containerId in case there are multiple notifications with different styled to display + */ + containerId?: string + style?: CSSProperties +} + +export const NotificationContainer = ({ + newestOnTop, + limit, + autoClose = false, + position = 'top-right', + 'data-testid': dataTestId, + className, + style, + containerId = 'notification', +}: NotificationContainerProps) => ( + +) diff --git a/packages/ui/src/components/PieChart/Legends.tsx b/packages/ui/src/components/PieChart/Legends.tsx index 9adc678861..b1a86b96e7 100644 --- a/packages/ui/src/components/PieChart/Legends.tsx +++ b/packages/ui/src/components/PieChart/Legends.tsx @@ -14,7 +14,7 @@ import { toggleBoxPie, valuePie, } from './styles.css' -import { Tooltip as TooltipContainer } from './Tooltip' +import TooltipContainer from './Tooltip' import type { Data } from './types' type LegendsProps = { @@ -24,12 +24,7 @@ type LegendsProps = { colors: string[] } -export const Legends = ({ - focused, - data, - onFocusChange, - colors, -}: LegendsProps) => ( +const Legends = ({ focused, data, onFocusChange, colors }: LegendsProps) => (
    {data?.map((item, index) => { const isSegmentFocused = focused !== undefined && item.id === focused @@ -83,3 +78,5 @@ export const Legends = ({ })}
) + +export default Legends diff --git a/packages/ui/src/components/PieChart/Tooltip.tsx b/packages/ui/src/components/PieChart/Tooltip.tsx index 43fbdef449..df1b4ba3f4 100644 --- a/packages/ui/src/components/PieChart/Tooltip.tsx +++ b/packages/ui/src/components/PieChart/Tooltip.tsx @@ -16,7 +16,7 @@ type TooltipProps = { } } -export const Tooltip = ({ data }: TooltipProps) => ( +const Tooltip = ({ data }: TooltipProps) => (
  • @@ -40,3 +40,5 @@ export const Tooltip = ({ data }: TooltipProps) => (
) + +export default Tooltip diff --git a/packages/ui/src/components/PieChart/__tests__/index.test.tsx b/packages/ui/src/components/PieChart/__tests__/index.test.tsx index 1a594caaca..fa69a24e15 100644 --- a/packages/ui/src/components/PieChart/__tests__/index.test.tsx +++ b/packages/ui/src/components/PieChart/__tests__/index.test.tsx @@ -65,6 +65,7 @@ describe('pieChart', () => { expect(asFragment()).toMatchSnapshot() }) + // TODO: fixme test.skip('renders correctly when chart is hovered', async () => { const { container } = renderWithTheme( , diff --git a/packages/ui/src/components/PieChart/index.tsx b/packages/ui/src/components/PieChart/index.tsx index 3521af58c7..c674dc34a1 100644 --- a/packages/ui/src/components/PieChart/index.tsx +++ b/packages/ui/src/components/PieChart/index.tsx @@ -10,7 +10,7 @@ import { useCallback, useState } from 'react' import { getLegendColor } from '../../helpers/legend' import { getNivoTheme } from '../../helpers/nivoTheme' import { Text } from '../Text' -import { Legends } from './Legends' +import Legends from './Legends' import { containerPie, contentPie, diff --git a/packages/ui/src/components/Row/styles.css.ts b/packages/ui/src/components/Row/styles.css.ts index 88c5c73c64..7b38441856 100644 --- a/packages/ui/src/components/Row/styles.css.ts +++ b/packages/ui/src/components/Row/styles.css.ts @@ -44,11 +44,13 @@ const breakpoints = Object.keys( ) as (keyof typeof consoleLightTheme.breakpoints)[] // Get the keys and sort them by their pixel value. It's important to define breakpoints priority -const orderedBreakpointKeys = breakpoints.toSorted( - (a, b) => - Number.parseInt(consoleLightTheme.breakpoints[a], 10) - - Number.parseInt(consoleLightTheme.breakpoints[b], 10), -) +const orderedBreakpointKeys = [ + ...breakpoints.sort( + (a, b) => + Number.parseInt(consoleLightTheme.breakpoints[a], 10) - + Number.parseInt(consoleLightTheme.breakpoints[b], 10), + ), +] as const const themeBreakpoints = orderedBreakpointKeys.reduce( (acc, key) => ({ diff --git a/packages/ui/src/components/SelectInput/SelectInputProvider.tsx b/packages/ui/src/components/SelectInput/SelectInputProvider.tsx index 9cd75e856c..8966523ce4 100644 --- a/packages/ui/src/components/SelectInput/SelectInputProvider.tsx +++ b/packages/ui/src/components/SelectInput/SelectInputProvider.tsx @@ -53,7 +53,6 @@ const SelectInputContext = createContext({ setSelectedData: () => {}, }) -// oxlint-disable-next-line react/only-export-components export const useSelectInput = () => useContext(SelectInputContext) type SelectInputProviderProps = { diff --git a/packages/ui/src/components/SelectInput/components/SearchBarDropdown.tsx b/packages/ui/src/components/SelectInput/components/SearchBarDropdown.tsx index 1692196c71..4748f57f60 100644 --- a/packages/ui/src/components/SelectInput/components/SearchBarDropdown.tsx +++ b/packages/ui/src/components/SelectInput/components/SearchBarDropdown.tsx @@ -15,7 +15,7 @@ type SearchBarProps = { setSearchBarActive: Dispatch> } -const getReferenceText = (option: OptionType) => { +export const getReferenceText = (option: OptionType) => { if (option.searchText) { return normalizeString(option.searchText) } @@ -27,7 +27,7 @@ const getReferenceText = (option: OptionType) => { } // It uses Levenshtein distance so that the search is typo-tolerant for a simple fuzzy-search -const searchRegex = (data: OptionType[], query: string) => +export const searchRegex = (data: OptionType[], query: string) => data.filter(option => { const referenceText = getReferenceText(option) const regex = new RegExp(query, 'i') @@ -158,17 +158,11 @@ export const SearchBarDropdown = ({ } useEffect(() => { - // note: Remove me and use autoFocus when popup is fixed + // TODO: Remove me and use autoFocus when popup is fixed // Autofocus on the search bar create some scroll issues - const timeout = setTimeout(() => { + setTimeout(() => { searchInputRef.current?.focus() }, 50) - - return () => { - if (timeout) { - clearTimeout(timeout) - } - } }, []) return ( diff --git a/packages/ui/src/components/Skeleton/index.tsx b/packages/ui/src/components/Skeleton/index.tsx index fad46319fd..63238abfdd 100644 --- a/packages/ui/src/components/Skeleton/index.tsx +++ b/packages/ui/src/components/Skeleton/index.tsx @@ -66,5 +66,4 @@ export const Skeleton = ({ ) } -// oxlint-disable-next-line react/only-export-components export const skeletonTypes = Object.keys(variants) as SkeletonVariant[] diff --git a/packages/ui/src/components/Stack/styles.css.ts b/packages/ui/src/components/Stack/styles.css.ts index 596890151a..d0b39fbbd1 100644 --- a/packages/ui/src/components/Stack/styles.css.ts +++ b/packages/ui/src/components/Stack/styles.css.ts @@ -12,9 +12,7 @@ export const stack = style({ }) // Get the keys and sort them by their pixel value. It's important to define breakpoints priority -const orderedBreakpointKeys = Object.keys( - consoleLightTheme.breakpoints, -).toSorted( +const orderedBreakpointKeys = Object.keys(consoleLightTheme.breakpoints).sort( (a, b) => Number.parseInt( consoleLightTheme.breakpoints[ diff --git a/packages/ui/src/components/Stepper/StepperProvider.tsx b/packages/ui/src/components/Stepper/StepperProvider.tsx index 796ddb513e..d5dd471667 100644 --- a/packages/ui/src/components/Stepper/StepperProvider.tsx +++ b/packages/ui/src/components/Stepper/StepperProvider.tsx @@ -33,7 +33,6 @@ type StepperProviderProps = { separator: boolean } -// oxlint-disable-next-line react/only-export-components export const useStepper = () => useContext(StepperContext) /** * Stepper component to show the progress of a process in a linear way. diff --git a/packages/ui/src/components/Stepper/index.tsx b/packages/ui/src/components/Stepper/index.tsx index 66ba2495b8..062b8e3a32 100644 --- a/packages/ui/src/components/Stepper/index.tsx +++ b/packages/ui/src/components/Stepper/index.tsx @@ -37,9 +37,7 @@ export const Stepper = ({ separator = true, style, }: StepperProps) => { - const cleanChildren = Children.toArray(children).filter(child => - isValidElement(child), - ) + const cleanChildren = Children.toArray(children).filter(isValidElement) const lastStep = Children.count(cleanChildren) - 1 return ( diff --git a/packages/ui/src/components/Table/TableContext.tsx b/packages/ui/src/components/Table/TableContext.tsx index 836ab2c715..3f778ede7a 100644 --- a/packages/ui/src/components/Table/TableContext.tsx +++ b/packages/ui/src/components/Table/TableContext.tsx @@ -61,7 +61,6 @@ export const TableProvider = ({ ) -// oxlint-disable-next-line react/only-export-components export const useTableContext = () => { const context = useContext(TableContext) if (!context) { diff --git a/packages/ui/src/components/Table/__stories__/Ordering.stories.tsx b/packages/ui/src/components/Table/__stories__/Ordering.stories.tsx index ec458b31f2..579bcdb9f4 100644 --- a/packages/ui/src/components/Table/__stories__/Ordering.stories.tsx +++ b/packages/ui/src/components/Table/__stories__/Ordering.stories.tsx @@ -12,7 +12,7 @@ export const Ordering: StoryFn = args => { const sortedData = useMemo(() => { const orderMultiplicator = currentOrder.order === 'asc' ? 1 : -1 - return [...sourceData].toSorted((a, b) => { + return [...sourceData].sort((a, b) => { if (a[currentOrder.columnId] < b[currentOrder.columnId]) { return -1 * orderMultiplicator } diff --git a/packages/ui/src/components/Table/index.tsx b/packages/ui/src/components/Table/index.tsx index 1c52bd7d2b..cc09ef35d6 100644 --- a/packages/ui/src/components/Table/index.tsx +++ b/packages/ui/src/components/Table/index.tsx @@ -23,7 +23,7 @@ import type { ColumnProps } from './types' // type OptionalOnly = Pick> -// Note: Get type optional from omit values +// TODO: Get type optional from omit values type TableProps = Omit< TableProviderProps, | 'selectable' diff --git a/packages/ui/src/components/TagList/index.tsx b/packages/ui/src/components/TagList/index.tsx index 753dd8baf3..0f3e1a35dc 100644 --- a/packages/ui/src/components/TagList/index.tsx +++ b/packages/ui/src/components/TagList/index.tsx @@ -223,7 +223,7 @@ export const TagList = ({ const visibleTagsCopy = visibleTags.filter( (_, index) => index < visibleTags.length - 1, ) - const tagToMove = visibleTags.at(-1) ?? '' + const tagToMove = visibleTags[visibleTags.length - 1] setVisibleTags(visibleTagsCopy) setHiddenTags([tagToMove, ...hiddenTags]) diff --git a/packages/ui/src/components/Text/__stories__/Variants.stories.tsx b/packages/ui/src/components/Text/__stories__/Variants.stories.tsx index 6432594db5..b98f488532 100644 --- a/packages/ui/src/components/Text/__stories__/Variants.stories.tsx +++ b/packages/ui/src/components/Text/__stories__/Variants.stories.tsx @@ -1,6 +1,5 @@ import type { StoryFn } from '@storybook/react-vite' -import { Text } from '..' -import { textVariants } from '../constants' +import { Text, textVariants } from '../index' export const Variants: StoryFn = props => ( <> diff --git a/packages/ui/src/components/Text/__tests__/index.test.tsx b/packages/ui/src/components/Text/__tests__/index.test.tsx index 57ebc976dd..1858779d7b 100644 --- a/packages/ui/src/components/Text/__tests__/index.test.tsx +++ b/packages/ui/src/components/Text/__tests__/index.test.tsx @@ -1,7 +1,6 @@ import { shouldMatchSnapshot } from '@utils/test' import { describe, test } from 'vitest' -import { Text } from '..' -import { textVariants } from '../constants' +import { Text, textVariants } from '..' describe('text', () => { test.each(textVariants)('renders correctly with type="%s"', variant => diff --git a/packages/ui/src/components/Text/constant.ts b/packages/ui/src/components/Text/constant.ts new file mode 100644 index 0000000000..af6a846cbf --- /dev/null +++ b/packages/ui/src/components/Text/constant.ts @@ -0,0 +1,6 @@ +export const PROMINENCES = { + default: '', + strong: 'strong', + stronger: 'stronger', + weak: 'weak', +} diff --git a/packages/ui/src/components/Text/constants.ts b/packages/ui/src/components/Text/constants.ts deleted file mode 100644 index 508d41cbb4..0000000000 --- a/packages/ui/src/components/Text/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { typography } from '../../theme' - -export const PROMINENCES = { - default: '', - strong: 'strong', - stronger: 'stronger', - weak: 'weak', -} - -export type TextVariant = keyof typeof typography - -export const textVariants = Object.keys(typography) as TextVariant[] diff --git a/packages/ui/src/components/Text/index.tsx b/packages/ui/src/components/Text/index.tsx index d251eb0809..0d7bbfe43e 100644 --- a/packages/ui/src/components/Text/index.tsx +++ b/packages/ui/src/components/Text/index.tsx @@ -6,14 +6,17 @@ import { useRef } from 'react' import recursivelyGetChildrenString from '../../helpers/recursivelyGetChildrenString' import { useIsOverflowing } from '../../hooks/useIsOverflowing' import type { ExtendedColor } from '../../theme' +import { typography } from '../../theme' import { Tooltip } from '../Tooltip' -import type { PROMINENCES, TextVariant } from './constants' +import type { PROMINENCES } from './constant' import { text } from './style.css' import { placementText, whiteSpaceText } from './variables.css' type ProminenceProps = keyof typeof PROMINENCES type PlacementProps = CSSProperties['textAlign'] type WhiteSpaceProps = CSSProperties['whiteSpace'] +type TextVariant = keyof typeof typography +export const textVariants = Object.keys(typography) as TextVariant[] type TextProps = { className?: string diff --git a/packages/ui/src/components/Text/style.css.ts b/packages/ui/src/components/Text/style.css.ts index 35ccb0d152..eb97eed8d4 100644 --- a/packages/ui/src/components/Text/style.css.ts +++ b/packages/ui/src/components/Text/style.css.ts @@ -3,7 +3,7 @@ import type { RecipeVariants } from '@vanilla-extract/recipes' import { recipe } from '@vanilla-extract/recipes' import type { ExtendedColor } from 'src/theme' import { typography } from '../../theme' -import { PROMINENCES } from './constants' +import { PROMINENCES } from './constant' import capitalize from '../../utils/capitalize' import { placementText, whiteSpaceText } from './variables.css' diff --git a/packages/ui/src/components/Toaster/Toaster.tsx b/packages/ui/src/components/Toaster/Toaster.tsx deleted file mode 100644 index 61c01c32d6..0000000000 --- a/packages/ui/src/components/Toaster/Toaster.tsx +++ /dev/null @@ -1,42 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import type { ToastOptions } from 'react-toastify' -import { toast as baseToast } from 'react-toastify' -import { CloseButton } from './components/CloseButton' -import { Content } from './components/Content' - -export const toast = { - error: ( - children: ReactNode, - options?: ToastOptions, - containerId?: string, - ): number | string => - baseToast.error({children}, { - ...options, - closeButton: , - containerId: containerId ?? 'toaster', - }), - - success: ( - children: ReactNode, - options?: ToastOptions, - containerId?: string, - ): number | string => - baseToast.success({children}, { - ...options, - closeButton: , - containerId: containerId ?? 'toaster', - }), - - warning: ( - children: ReactNode, - options?: ToastOptions, - containerId?: string, - ): number | string => - baseToast.warn({children}, { - ...options, - closeButton: , - containerId: containerId ?? 'toaster', - }), -} diff --git a/packages/ui/src/components/Toaster/ToasterContainer.tsx b/packages/ui/src/components/Toaster/ToasterContainer.tsx deleted file mode 100644 index 1c3a4e0412..0000000000 --- a/packages/ui/src/components/Toaster/ToasterContainer.tsx +++ /dev/null @@ -1,72 +0,0 @@ -'use client' - -import type { CSSProperties } from 'react' -import type { ToastOptions } from 'react-toastify' -import { ToastContainer as BaseToastContainer, Slide } from 'react-toastify' -import { AUTOCLOSE_DELAY } from './constants' -import { toaster } from './styles.css' - -type ToastContainerProps = { - /** - * Whether to display the newest toast on top. - * `Default: false` - */ - newestOnTop?: boolean - /** - * Limit the number of toast displayed at the same time - */ - limit?: number - /** - * Position of the toast container - */ - position?: ToastOptions['position'] - 'data-testid'?: string - className?: string - /** - * Delay before the toast is automatically closed, if not set the default value is 6000ms - */ - autoClose?: number - - /** - * Set a custom containerId to be able to define multiple ToastContainers - */ - containerId?: string - style?: CSSProperties -} - -/** - * Display short information about an event that happen in the interface in a floating alert. - * Toaster is based on **react-tostify**, you can find a complete documentation - * [here](https://fkhadra.github.io/react-toastify/introduction/). - * - * Toaster is separated in two parts, first the `ToastContainer` which is where the div of the toast will be rendered, - * and second the `toast()` function which is used to display the toast. - */ -export const ToastContainer = ({ - newestOnTop, - limit, - position = 'top-right', - 'data-testid': dataTestId, - className, - autoClose, - containerId = 'toaster', - style, -}: ToastContainerProps) => ( - -) diff --git a/packages/ui/src/components/Toaster/components/CloseButton.tsx b/packages/ui/src/components/Toaster/components/CloseButton.tsx deleted file mode 100644 index bd758ecb1e..0000000000 --- a/packages/ui/src/components/Toaster/components/CloseButton.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client' - -import { CloseIcon } from '@ultraviolet/icons' -import type { SENTIMENTS } from '../../../theme' -import { Button } from '../../Button' -import { closeButtonToaster } from '../styles.css' - -type SENTIMENT = (typeof SENTIMENTS)[number] - -type CloseButtonProps = { - closeToast?: () => void - sentiment: SENTIMENT -} - -export const CloseButton = ({ - closeToast, - sentiment = 'success', -}: CloseButtonProps) => ( - -) diff --git a/packages/ui/src/components/Toaster/components/Content.tsx b/packages/ui/src/components/Toaster/components/Content.tsx deleted file mode 100644 index 24ffe30a19..0000000000 --- a/packages/ui/src/components/Toaster/components/Content.tsx +++ /dev/null @@ -1,21 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import { Stack } from '../../Stack' -import { Text } from '../../Text' - -type ContentProps = { - children?: ReactNode -} - -export const Content = ({ children }: ContentProps) => ( - - {typeof children === 'string' ? ( - - {children} - - ) : ( - children - )} - -) diff --git a/packages/ui/src/components/Toaster/constants.ts b/packages/ui/src/components/Toaster/constants.ts deleted file mode 100644 index f1e675cefc..0000000000 --- a/packages/ui/src/components/Toaster/constants.ts +++ /dev/null @@ -1,2 +0,0 @@ -// Delay to close the toast in ms -export const AUTOCLOSE_DELAY = 6_000 diff --git a/packages/ui/src/components/Toaster/index.ts b/packages/ui/src/components/Toaster/index.ts deleted file mode 100644 index 2092e42207..0000000000 --- a/packages/ui/src/components/Toaster/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ToastButton } from './components/Button' -import { ToastLink } from './components/Link' - -export const Toast = { - Button: ToastButton, - Link: ToastLink, -} - -export { toast } from './Toaster' -export { ToastContainer } from './ToasterContainer' diff --git a/packages/ui/src/components/Toaster/index.tsx b/packages/ui/src/components/Toaster/index.tsx new file mode 100644 index 0000000000..bb3f096f08 --- /dev/null +++ b/packages/ui/src/components/Toaster/index.tsx @@ -0,0 +1,160 @@ +'use client' + +import { CloseIcon } from '@ultraviolet/icons' +import type { CSSProperties, ReactNode } from 'react' +import type { ToastOptions } from 'react-toastify' +import { + ToastContainer as BaseToastContainer, + toast as baseToast, + Slide, +} from 'react-toastify' +import type { SENTIMENTS } from '../../theme' +import { Button } from '../Button' +import { Stack } from '../Stack' +import { Text } from '../Text' +import { ToastButton } from './components/Button' +import { ToastLink } from './components/Link' +import { closeButtonToaster, toaster } from './styles.css' + +const AUTOCLOSE_DELAY = 6000 // Delay to close the toast in ms +type SENTIMENT = (typeof SENTIMENTS)[number] + +type CloseButtonProps = { + closeToast?: () => void + sentiment: SENTIMENT +} +const CloseButton = ({ + closeToast, + sentiment = 'success', +}: CloseButtonProps) => ( + +) + +type ContentProps = { + children?: ReactNode +} + +const Content = ({ children }: ContentProps) => ( + + {typeof children === 'string' ? ( + + {children} + + ) : ( + children + )} + +) + +export const toast = { + error: ( + children: ReactNode, + options?: ToastOptions, + containerId?: string, + ): number | string => + baseToast.error({children}, { + ...options, + closeButton: , + containerId: containerId ?? 'toaster', + }), + + success: ( + children: ReactNode, + options?: ToastOptions, + containerId?: string, + ): number | string => + baseToast.success({children}, { + ...options, + closeButton: , + containerId: containerId ?? 'toaster', + }), + + warning: ( + children: ReactNode, + options?: ToastOptions, + containerId?: string, + ): number | string => + baseToast.warn({children}, { + ...options, + closeButton: , + containerId: containerId ?? 'toaster', + }), +} + +type ToastContainerProps = { + /** + * Whether to display the newest toast on top. + * `Default: false` + */ + newestOnTop?: boolean + /** + * Limit the number of toast displayed at the same time + */ + limit?: number + /** + * Position of the toast container + */ + position?: ToastOptions['position'] + 'data-testid'?: string + className?: string + /** + * Delay before the toast is automatically closed, if not set the default value is 6000ms + */ + autoClose?: number + + /** + * Set a custom containerId to be able to define multiple ToastContainers + */ + containerId?: string + style?: CSSProperties +} + +/** + * Display short information about an event that happen in the interface in a floating alert. + * Toaster is based on **react-tostify**, you can find a complete documentation + * [here](https://fkhadra.github.io/react-toastify/introduction/). + * + * Toaster is separated in two parts, first the `ToastContainer` which is where the div of the toast will be rendered, + * and second the `toast()` function which is used to display the toast. + */ +export const ToastContainer = ({ + newestOnTop, + limit, + position = 'top-right', + 'data-testid': dataTestId, + className, + autoClose, + containerId = 'toaster', + style, +}: ToastContainerProps) => ( + +) + +export const Toast = { + Button: ToastButton, + Link: ToastLink, +} diff --git a/packages/ui/src/components/VerificationCode/__stories__/Size.stories.tsx b/packages/ui/src/components/VerificationCode/__stories__/Size.stories.tsx index 6fe1e77e9d..ec1cbdadfa 100644 --- a/packages/ui/src/components/VerificationCode/__stories__/Size.stories.tsx +++ b/packages/ui/src/components/VerificationCode/__stories__/Size.stories.tsx @@ -1,7 +1,6 @@ import type { StoryFn } from '@storybook/react-vite' import { Stack } from '../..' -import { VerificationCode } from '..' -import { verificationCodeSizes } from '../constants' +import { VerificationCode, verificationCodeSizes } from '..' export const Size: StoryFn = args => ( diff --git a/packages/ui/src/components/VerificationCode/constants.ts b/packages/ui/src/components/VerificationCode/constants.ts index 9fe913832f..0e9b71f4fe 100644 --- a/packages/ui/src/components/VerificationCode/constants.ts +++ b/packages/ui/src/components/VerificationCode/constants.ts @@ -1,5 +1,3 @@ -const sizes = ['small', 'medium', 'large', 'xlarge'] as const - export const SIZE_HEIGHT = { large: '600', medium: '500', @@ -13,5 +11,3 @@ export const SIZE_WIDTH = { small: '300', xlarge: '700', } as const - -export const verificationCodeSizes = sizes diff --git a/packages/ui/src/components/VerificationCode/index.tsx b/packages/ui/src/components/VerificationCode/index.tsx index bfa05cb806..b2d720e805 100644 --- a/packages/ui/src/components/VerificationCode/index.tsx +++ b/packages/ui/src/components/VerificationCode/index.tsx @@ -11,8 +11,13 @@ import type { import { createRef, useId, useMemo, useState } from 'react' import { Label } from '../Label' import { Text } from '../Text' +import { SIZE_HEIGHT } from './constants' import { filedSetClass, inputClass, inputSizes } from './styles.css' +type Size = 'small' | 'medium' | 'large' | 'xlarge' + +export const verificationCodeSizes = Object.keys(SIZE_HEIGHT) as Size[] + const DEFAULT_ON_FUNCTION = () => {} const inputOnFocus: FocusEventHandler = event => @@ -133,7 +138,7 @@ export const VerificationCode = ({ const prevIndex = index - 1 const nextIndex = index + 1 const first = inputRefs[0] - const last = inputRefs.at(-1) + const last = inputRefs[inputRefs.length - 1] const prev = inputRefs[prevIndex] const next = inputRefs[nextIndex] const vals = [...values] diff --git a/packages/ui/src/helpers/legend.ts b/packages/ui/src/helpers/legend.ts index a62ce63855..d78e544580 100644 --- a/packages/ui/src/helpers/legend.ts +++ b/packages/ui/src/helpers/legend.ts @@ -5,7 +5,7 @@ export const getLegendColor = (theme: typeof UVTheme): string[] => { return Object.keys(colors.other.data.charts) .filter(key => !['success', 'danger'].includes(key)) - .toSorted((a, b) => { + .sort((a, b) => { if (Number(a.replace('data', '')) < Number(b.replace('data', ''))) { return -1 } diff --git a/packages/ui/src/hooks/useIsOverflowing.ts b/packages/ui/src/hooks/useIsOverflowing.ts index fe2b424d0b..4befdc6b6a 100644 --- a/packages/ui/src/hooks/useIsOverflowing.ts +++ b/packages/ui/src/hooks/useIsOverflowing.ts @@ -27,7 +27,7 @@ export const useIsOverflowing = ( // This will add the function into the browser event queue after the DOM is painted // which is needed to get the correct offsetWidth and scrollWidth - const timeout = setTimeout(() => handleResize(), 0) + setTimeout(() => handleResize(), 0) // Listen for resize events window.addEventListener('resize', handleResize) @@ -35,7 +35,6 @@ export const useIsOverflowing = ( // Cleanup the event listener return () => { window.removeEventListener('resize', handleResize) - clearTimeout(timeout) } }, [ref, callback]) diff --git a/packages/ui/src/theme/ThemeProvider.tsx b/packages/ui/src/theme/ThemeProvider.tsx index d416f05d46..3500326075 100644 --- a/packages/ui/src/theme/ThemeProvider.tsx +++ b/packages/ui/src/theme/ThemeProvider.tsx @@ -10,7 +10,6 @@ const ThemeContext = createContext(consoleLightTheme) /** * Provide an object of the theme variables. */ -// oxlint-disable-next-line react/only-export-components export const useTheme = () => { const context = useContext(ThemeContext) if (!context) { diff --git a/utils/scripts/analyse-deps.ts b/utils/scripts/analyse-deps.ts index 406c45d94b..44c4404eb3 100644 --- a/utils/scripts/analyse-deps.ts +++ b/utils/scripts/analyse-deps.ts @@ -62,7 +62,7 @@ for (const file of filesToAnalyze) { string => normalizedFile.endsWith(string), ) ) { - const importedComponent = normalizedFile.split('/').toReversed()[0] + const importedComponent = normalizedFile.split('/').reverse()[0] if (!graph[componentName]) { graph[componentName] = { dependsOn: [] } @@ -123,7 +123,7 @@ const asRecord = Object.fromEntries( return [name, filteredDeps.length] as const }) - .toSorted(([, aCount], [, bCount]) => aCount - bCount), + .sort(([, aCount], [, bCount]) => aCount - bCount), ) fs.writeFileSync('depsFiltered.json', JSON.stringify(asRecord, null, 2)) diff --git a/utils/scripts/figma-synchronise-tokens.tsx b/utils/scripts/figma-synchronise-tokens.tsx index b63e3ff222..3267031fcc 100755 --- a/utils/scripts/figma-synchronise-tokens.tsx +++ b/utils/scripts/figma-synchronise-tokens.tsx @@ -70,12 +70,13 @@ const createCSSFile = (theme: string, content: UvThemeType) => { } const alphaOrder = (obj: JsonType) => { - const orderedKeys = Object.keys(obj ?? {}).toSorted() + const orderedKeys = Object.keys(obj ?? {}).sort() return orderedKeys.reduce((newObj: Record, key) => { const result = typeof obj[key] === 'string' ? obj[key] : alphaOrder(obj[key] as JsonType) + // eslint-disable-next-line no-param-reassign newObj[key] = result return newObj @@ -170,6 +171,7 @@ const getValues = ( ) if (newValue !== null) { + // eslint-disable-next-line no-param-reassign values[key.replaceAll(/,/g, '.')] = newValue } @@ -343,11 +345,8 @@ const writeFiles = async () => { }), ) } - -try { +;(async () => { if (typeof process !== 'undefined' && process.versions?.node) { await writeFiles() } -} catch (error) { - console.error(error) -} +})().catch(console.error) diff --git a/vite.config.ts b/vite.config.ts index 7b9b26e9a1..162480cc69 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,7 +20,7 @@ const external = (id: string) => { } const match = (dependency: string) => new RegExp(`^${dependency}`).test(id) - const isExternal = externalPkgs.some(dep => match(dep)) + const isExternal = externalPkgs.some(match) // alias of bundledDependencies package.json field array const isBundled = pkg.bundleDependencies?.some(match) From d73497ef5b258a2586372291099e0f29c5174f57 Mon Sep 17 00:00:00 2001 From: philibeaux Date: Thu, 13 Nov 2025 11:30:32 +0100 Subject: [PATCH 2/3] chore(oxlint): upgrade rule (#5778) * chore(devdeps): update oxlint monorepo to v1.16.0 * fix: oxlint errors Signed-off-by: aphilibeaux * chore(devdeps): update oxlint monorepo to v1.16.0 (#5566) * chore(devdeps): update oxlint monorepo to v1.16.0 * fix: oxlint errors Signed-off-by: aphilibeaux --------- Signed-off-by: aphilibeaux Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Matthias Signed-off-by: aphilibeaux --------- Signed-off-by: aphilibeaux Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Matthias --- .oxlintrc.json | 179 ++++++++++-------- biome.json | 1 + eslint.config.mjs | 1 + examples/next-app-router/app/layout.tsx | 1 + examples/next/package.json | 2 +- examples/next/src/components/Link.tsx | 2 - examples/next/src/pages/_document.tsx | 1 + packages/form/package.json | 2 +- .../form/src/providers/ErrorContext/index.tsx | 1 + .../EstimateCost/EstimateCostProvider.tsx | 1 + .../EstimateCost/OverlayContext.tsx | 1 + .../Navigation/NavigationProvider.tsx | 2 + .../components/Navigation/components/Item.tsx | 3 +- .../Navigation/components/ItemProvider.tsx | 1 + .../Navigation/components/PinnedItems.tsx | 5 +- .../OfferList/OfferListProvider.tsx | 1 + .../OfferList/__stories__/Example.stories.tsx | 2 +- .../OrderSummary/{helpers.tsx => helpers.ts} | 0 packages/themes/src/ThemeProvider.tsx | 1 + .../src/__stories__/Properties/Properties.tsx | 6 +- .../Tools/ThemeGenerator/contants.ts | 2 +- .../ui/src/components/Avatar/constants.ts | 2 +- .../ui/src/components/BarChart/Tooltip.tsx | 8 +- .../BarChart/__tests__/Tooltip.test.tsx | 2 +- packages/ui/src/components/BarChart/index.tsx | 2 +- .../Button/__stories__/Size.stories.tsx | 5 +- .../Button/__tests__/index.test.tsx | 4 +- .../ui/src/components/Button/constants.ts | 3 + packages/ui/src/components/Button/index.tsx | 5 - .../__stories__/Size.stories.tsx | 3 +- .../components/ExpandableCard/constants.ts | 1 + .../src/components/ExpandableCard/index.tsx | 3 +- .../Key/__stories__/SpeciaKeys.stories.tsx | 3 +- .../components/Key/__tests__/index.test.tsx | 3 +- packages/ui/src/components/Key/constants.ts | 8 + packages/ui/src/components/Key/index.tsx | 10 +- .../ui/src/components/List/ColumnProvider.tsx | 1 + .../ui/src/components/List/ListContext.tsx | 1 + .../List/__stories__/Example.stories.tsx | 2 +- .../List/__stories__/Ordering.stories.tsx | 2 +- packages/ui/src/components/List/index.tsx | 2 +- .../Loader/__stories__/Sizes.stories.tsx | 3 +- .../Loader/__tests__/index.test.tsx | 3 +- .../ui/src/components/Loader/constants.ts | 8 + .../ui/src/components/Menu/MenuProvider.tsx | 4 +- .../src/components/Menu/components/Item.tsx | 4 +- packages/ui/src/components/Menu/index.tsx | 2 +- .../ui/src/components/Modal/ModalProvider.tsx | 2 + .../components/Notification/Notification.tsx | 57 ++++++ .../Notification/NotificationContainer.tsx | 59 ++++++ .../ui/src/components/Notification/index.ts | 2 + .../ui/src/components/Notification/index.tsx | 115 ----------- .../ui/src/components/PieChart/Legends.tsx | 11 +- .../ui/src/components/PieChart/Tooltip.tsx | 4 +- .../PieChart/__tests__/index.test.tsx | 1 - packages/ui/src/components/PieChart/index.tsx | 2 +- packages/ui/src/components/Row/styles.css.ts | 12 +- .../SelectInput/SelectInputProvider.tsx | 1 + .../components/SearchBarDropdown.tsx | 14 +- packages/ui/src/components/Skeleton/index.tsx | 1 + .../ui/src/components/Stack/styles.css.ts | 4 +- .../components/Stepper/StepperProvider.tsx | 1 + packages/ui/src/components/Stepper/index.tsx | 4 +- .../ui/src/components/Table/TableContext.tsx | 1 + .../Table/__stories__/Ordering.stories.tsx | 2 +- packages/ui/src/components/Table/index.tsx | 2 +- packages/ui/src/components/TagList/index.tsx | 2 +- .../Text/__stories__/Variants.stories.tsx | 3 +- .../components/Text/__tests__/index.test.tsx | 3 +- packages/ui/src/components/Text/constant.ts | 6 - packages/ui/src/components/Text/constants.ts | 12 ++ packages/ui/src/components/Text/index.tsx | 5 +- packages/ui/src/components/Text/style.css.ts | 2 +- .../ui/src/components/Toaster/Toaster.tsx | 42 ++++ .../components/Toaster/ToasterContainer.tsx | 72 +++++++ .../Toaster/components/CloseButton.tsx | 28 +++ .../components/Toaster/components/Content.tsx | 21 ++ .../ui/src/components/Toaster/constants.ts | 2 + packages/ui/src/components/Toaster/index.ts | 10 + packages/ui/src/components/Toaster/index.tsx | 160 ---------------- .../__stories__/Size.stories.tsx | 3 +- .../components/VerificationCode/constants.ts | 4 + .../src/components/VerificationCode/index.tsx | 5 - packages/ui/src/helpers/legend.ts | 2 +- packages/ui/src/hooks/useIsOverflowing.ts | 3 +- packages/ui/src/theme/ThemeProvider.tsx | 1 + utils/scripts/analyse-deps.ts | 4 +- utils/scripts/figma-synchronise-tokens.tsx | 11 +- vite.config.ts | 2 +- 89 files changed, 544 insertions(+), 463 deletions(-) rename packages/plus/src/components/OrderSummary/{helpers.tsx => helpers.ts} (100%) create mode 100644 packages/ui/src/components/ExpandableCard/constants.ts create mode 100644 packages/ui/src/components/Key/constants.ts create mode 100644 packages/ui/src/components/Loader/constants.ts create mode 100644 packages/ui/src/components/Notification/Notification.tsx create mode 100644 packages/ui/src/components/Notification/NotificationContainer.tsx create mode 100644 packages/ui/src/components/Notification/index.ts delete mode 100644 packages/ui/src/components/Notification/index.tsx delete mode 100644 packages/ui/src/components/Text/constant.ts create mode 100644 packages/ui/src/components/Text/constants.ts create mode 100644 packages/ui/src/components/Toaster/Toaster.tsx create mode 100644 packages/ui/src/components/Toaster/ToasterContainer.tsx create mode 100644 packages/ui/src/components/Toaster/components/CloseButton.tsx create mode 100644 packages/ui/src/components/Toaster/components/Content.tsx create mode 100644 packages/ui/src/components/Toaster/constants.ts create mode 100644 packages/ui/src/components/Toaster/index.ts delete mode 100644 packages/ui/src/components/Toaster/index.tsx diff --git a/.oxlintrc.json b/.oxlintrc.json index d0913ecd34..5184e2c6be 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -1,5 +1,14 @@ { "$schema": "./node_modules/oxlint/configuration_schema.json", + "categories": { + "correctness": "deny", + "nursery": "off", + "pedantic": "deny", + "perf": "deny", + "restriction": "deny", + "style": "deny", + "suspicious": "deny" + }, "ignorePatterns": [ "**/app.config.d.ts", "**/package.config.d.ts", @@ -8,32 +17,23 @@ "*.d.ts", "svgo.config.mjs" ], - "categories": { - "correctness": "deny", - "style": "deny", - "suspicious": "deny", - "perf": "deny", - "pedantic": "deny", - "restriction": "deny", - "nursery": "off" - }, - "plugins": [ - "import", - "node", - "oxc", - "react", - "typescript", - "unicorn", - "jsx-a11y" - ], "overrides": [ { - "files": ["**/__stories__/**/*.{ts,tsx}", "**/__tests__/**/*.{ts,tsx}", ".storybook/**/*.{ts,tsx}"], + "files": [ + "**/__stories__/**/*.{ts,tsx}", + "**/__tests__/**/*.{ts,tsx}", + ".storybook/**/*.{ts,tsx}", + "exemples/**", + ".storybook/**/*.{ts,tsx}" + ], "rules": { - "eslint/no-console": "off", "eslint/no-alert": "off", + "eslint/no-console": "off", "import/no-anonymous-default-export": "off", - "import/no-unassigned-import": "off" + "import/no-named-export": "off", + "import/no-unassigned-import": "off", + "react/jsx-pascal-case": "off", + "react/only-export-components": "off" } }, { @@ -45,46 +45,40 @@ "setup.ts", "vitest.setup.ts", "**/vitest.setup.ts", - "*.config.ts" + "*.config.ts", + "**/vitest/setup.ts" ], "plugins": ["import", "oxc", "vitest"], "rules": { "@typescript-eslint/consistent-type-imports": "off", - "import/no-namespace": "off", "import/export": "off", "import/no-anonymous-default-export": "off", + "import/no-named-export": "off", + "import/no-namespace": "off", "import/no-unassigned-import": "off", "jsx_a11y/label-has-associated-control": "off", - "vitest/prefer-lowercase-title": "error", - "no-console": "off" + "no-console": "off", + "node/no-process-env": "off", + "vitest/prefer-lowercase-title": "error" + } + }, + { + "files": ["e2e/**/*.{ts,tsx}"], + "rules": { + "node/no-process-env": "off" } } ], + "plugins": [ + "import", + "node", + "oxc", + "react", + "typescript", + "unicorn", + "jsx-a11y" + ], "rules": { - "jsx_a11y/label-has-associated-control": "off", - "jsx_a11y/no-autofocus": "off", - "jsx_a11y/prefer-tag-over-role": "off", - "import/default": "error", - "import/export": "off", - "import/exports-last": "off", - "import/max-dependencies": "off", - "import/named": "error", - "import/no-amd": "error", - "import/consistent-type-specifier-style": "error", - "import/group-exports": "off", - "import/import-no-namespace": "off", - "import/namespace": "off", - "import/no-cycle": "error", - "import/no-default-export": "off", - "import/no-duplicates": "error", - "import/no-named-as-default-member": "error", - "import/no-named-as-default": "error", - "import/no-self-import": "error", - "import/no-unassigned-import": "off", - "import/no-unused-modules": "off", - "import/prefer-default-export": "off", - "import/no-namespace": "off", - "import/unambiguous": "error", "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": "error", "@typescript-eslint/ban-ts-comment": "error", @@ -97,8 +91,8 @@ "@typescript-eslint/no-duplicate-enum-values": "error", "@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-explicit-any": "error", - "@typescript-eslint/no-magic-numbers": "off", "@typescript-eslint/no-extra-non-null-assertion": "error", + "@typescript-eslint/no-magic-numbers": "off", "@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-non-null-asserted-optional-chain": "error", @@ -106,33 +100,38 @@ "@typescript-eslint/no-this-alias": "error", "@typescript-eslint/no-unnecessary-type-constraint": "error", "@typescript-eslint/no-unsafe-declaration-merging": "error", + "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-var-requires": "error", "@typescript-eslint/prefer-as-const": "error", - "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/prefer-enum-initializers": "off", "@typescript-eslint/prefer-for-of": "error", "@typescript-eslint/prefer-function-type": "off", "@typescript-eslint/prefer-literal-enum-member": "off", "@typescript-eslint/prefer-ts-expect-error": "off", "@typescript-eslint/triple-slash-reference": "error", - "eslint/yoda": "error", + "arrow-body-style": "off", "eslint/array-callback-return": "error", "eslint/constructor-super": "error", "eslint/default-case-last": "error", "eslint/default-param-last": "off", "eslint/eqeqeq": "error", "eslint/for-direction": "error", + "eslint/func-style": "off", "eslint/getter-return": "error", "eslint/guard-for-in": "error", + "eslint/id-length": "off", + "eslint/init-declarations": "off", "eslint/max-classes-per-file": "error", + "eslint/max-depth": "off", "eslint/max-lines": "off", + "eslint/max-lines-per-function": "off", + "eslint/max-nested-callbacks": "off", "eslint/max-params": "off", "eslint/no-array-constructor": "error", "eslint/no-async-promise-executor": "error", "eslint/no-await-in-loop": "off", "eslint/no-bitwise": "error", "eslint/no-caller": "error", - "eslint/id-length": "off", "eslint/no-case-declarations": "error", "eslint/no-class-assign": "error", "eslint/no-compare-neg-zero": "error", @@ -150,11 +149,11 @@ "eslint/no-dupe-keys": "error", "eslint/no-duplicate-case": "error", "eslint/no-duplicate-imports": "off", + "eslint/no-empty": "error", "eslint/no-empty-character-class": "error", "eslint/no-empty-function": "off", "eslint/no-empty-pattern": "error", "eslint/no-empty-static-block": "error", - "eslint/no-empty": "error", "eslint/no-eq-null": "error", "eslint/no-eval": "error", "eslint/no-ex-assign": "error", @@ -195,8 +194,6 @@ "eslint/no-unsafe-optional-chaining": "error", "eslint/no-unused-labels": "error", "eslint/no-unused-private-class-members": "error", - "eslint/sort-keys": "off", - "eslint/react-in-jsx-scope": "off", "eslint/no-unused-vars": [ "error", { @@ -210,6 +207,7 @@ "eslint/no-void": "error", "eslint/no-with": "error", "eslint/radix": "error", + "eslint/react-in-jsx-scope": "off", "eslint/require-await": "off", "eslint/require-yield": "error", "eslint/sort-imports": [ @@ -219,14 +217,37 @@ "memberSyntaxSortOrder": ["single", "multiple", "all", "none"] } ], + "eslint/sort-keys": "off", "eslint/unicode-bom": "error", "eslint/use-isnan": "error", "eslint/valid-typeof": "error", - "eslint/init-declarations": "off", - "eslint/func-style": "off", - "eslint/max-lines-per-function": "off", - "eslint/max-nested-callbacks": "off", - "eslint/max-depth": "off", + "eslint/yoda": "error", + "import/consistent-type-specifier-style": "error", + "import/default": "error", + "import/export": "off", + "import/exports-last": "off", + "import/group-exports": "off", + "import/import-no-namespace": "off", + "import/max-dependencies": "off", + "import/named": "error", + "import/namespace": "off", + "import/no-amd": "error", + "import/no-anonymous-default-export": "off", + "import/no-cycle": "error", + "import/no-default-export": "off", + "import/no-duplicates": "error", + "import/no-named-as-default": "error", + "import/no-named-as-default-member": "error", + "import/no-named-export": "off", + "import/no-namespace": "off", + "import/no-self-import": "error", + "import/no-unassigned-import": "off", + "import/no-unused-modules": "off", + "import/prefer-default-export": "off", + "import/unambiguous": "error", + "jsx_a11y/label-has-associated-control": "off", + "jsx_a11y/no-autofocus": "off", + "jsx_a11y/prefer-tag-over-role": "off", "oxc/bad-bitwise-operator": "error", "oxc/no-accumulating-spread": "off", "oxc/no-async-await": "off", @@ -234,23 +255,25 @@ "oxc/no-const-enum": "error", "oxc/no-optional-chaining": "off", "oxc/no-rest-spread-properties": "off", - "react-perf/jsx-no-jsx-as-prop": "off", - "react-perf/jsx-no-new-array-as-prop": "off", - "react-perf/jsx-no-new-function-as-prop": "off", - "react-perf/jsx-no-new-object-as-prop": "off", + "prefer-destructuring": "off", "react_perf/jsx-no-jsx-as-prop": "error", "react_perf/jsx-no-new-array-as-prop": "error", "react_perf/jsx-no-new-function-as-props": "error", "react_perf/jsx-no-new-object-as-prop": "error", + "react-perf/jsx-no-jsx-as-prop": "off", + "react-perf/jsx-no-new-array-as-prop": "off", + "react-perf/jsx-no-new-function-as-prop": "off", + "react-perf/jsx-no-new-object-as-prop": "off", "react/button-has-type": "off", "react/exhaustive-deps": "error", + "react/iframe-missing-sandbox": "error", "react/jsx-filename-extension": [ "error", { "extensions": [".jsx", ".tsx"] } ], - "react/iframe-missing-sandbox": "error", + "react/jsx-handler-names": "off", "react/jsx-key": "error", "react/jsx-no-comment-text-nodes": "error", "react/jsx-no-duplicate-props": "error", @@ -258,6 +281,7 @@ "react/jsx-no-undef": "error", "react/jsx-no-useless-fragment": "off", "react/no-children-prop": "error", + "react/no-danger": "off", "react/no-dangerously-set-inner-html": "error", "react/no-direct-mutation-state": "error", "react/no-find-dom-node": "error", @@ -269,13 +293,7 @@ "react/no-unknown-property": "error", "react/react-in-jsx-scope": "off", "react/require-render-return": "error", - "react/no-danger": "off", - "unicorn/no-instanceof-builtins": "error", - "unicorn/prefer-array-index-of": "off", - "unicorn/prefer-array-find": "off", - "unicorn/no-for-loop": "off", - "unicorn/prefer-object-from-entries": "off", - "unicorn/prefer-global-this": "off", + "typescript/explicit-module-boundary-types": "off", "unicorn/catch-error-name": "error", "unicorn/empty-brace-spaces": "error", "unicorn/error-message": "off", @@ -289,12 +307,13 @@ "unicorn/no-array-reduce": "off", "unicorn/no-await-expression-member": "off", "unicorn/no-await-in-promise-methods": "off", - "unicorn/prefer-set-has": "off", "unicorn/no-console-spaces": "error", "unicorn/no-document-cookie": "off", "unicorn/no-empty-file": "error", + "unicorn/no-for-loop": "off", "unicorn/no-hex-escape": "error", "unicorn/no-instanceof-array": "error", + "unicorn/no-instanceof-builtins": "error", "unicorn/no-invalid-remove-event-listener": "off", "unicorn/no-lonely-if": "error", "unicorn/no-magic-array-flat-depth": "off", @@ -323,8 +342,10 @@ "unicorn/number-literal-case": "off", "unicorn/numeric-separators-style": "off", "unicorn/prefer-add-event-listener": "off", + "unicorn/prefer-array-find": "off", "unicorn/prefer-array-flat": "error", "unicorn/prefer-array-flat-map": "error", + "unicorn/prefer-array-index-of": "off", "unicorn/prefer-array-some": "off", "unicorn/prefer-at": "warn", "unicorn/prefer-blob-reading-methods": "off", @@ -335,6 +356,7 @@ "unicorn/prefer-dom-node-remove": "off", "unicorn/prefer-dom-node-text-content": "error", "unicorn/prefer-event-target": "error", + "unicorn/prefer-global-this": "off", "unicorn/prefer-includes": "error", "unicorn/prefer-logical-operator-over-ternary": "off", "unicorn/prefer-math-trunc": "error", @@ -343,11 +365,13 @@ "unicorn/prefer-native-coercion-functions": "error", "unicorn/prefer-node-protocol": "off", "unicorn/prefer-number-properties": "error", + "unicorn/prefer-object-from-entries": "off", "unicorn/prefer-optional-catch-binding": "error", "unicorn/prefer-prototype-methods": "error", "unicorn/prefer-query-selector": "off", "unicorn/prefer-reflect-apply": "error", "unicorn/prefer-regexp-test": "error", + "unicorn/prefer-set-has": "off", "unicorn/prefer-set-size": "error", "unicorn/prefer-spread": "error", "unicorn/prefer-string-replace-all": "off", @@ -359,12 +383,7 @@ "unicorn/require-number-to-fixed-digits-argument": "error", "unicorn/switch-case-braces": "error", "unicorn/text-encoding-identifier-case": "error", - "unicorn/throw-new-error": "error", - "arrow-body-style": "off", - "typescript/explicit-module-boundary-types": "off", - "prefer-destructuring": "off", - "import/no-anonymous-default-export": "off", - "react/jsx-handler-names": "off" + "unicorn/throw-new-error": "error" }, "settings": { "jsx-a11y": { diff --git a/biome.json b/biome.json index 349a86393b..4f6da6eed1 100644 --- a/biome.json +++ b/biome.json @@ -11,6 +11,7 @@ }, "includes": [ "!**/package.json", + ".oxlintrc.json", "**/*.ts", "**/*.tsx", "!**/dist/**", diff --git a/eslint.config.mjs b/eslint.config.mjs index 9210630714..dfe1f6800f 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -16,6 +16,7 @@ const dirname = path.dirname(filename) const disableRules = { // ---- biome rules ---- 'import/order': 'off', + "sort-imports": "off", 'import/no-unresolved': 'off', '@stylistic/no-extra-semi': 'off', '@stylistic/brace-style': 'off', diff --git a/examples/next-app-router/app/layout.tsx b/examples/next-app-router/app/layout.tsx index f896609c23..b362f30316 100644 --- a/examples/next-app-router/app/layout.tsx +++ b/examples/next-app-router/app/layout.tsx @@ -8,6 +8,7 @@ import '@ultraviolet/fonts/fonts.css' import '@ultraviolet/ui/styles' import '@ultraviolet/themes/global' +// oxlint-disable-next-line react/only-export-components export const metadata: Metadata = { description: 'Design System', title: 'Ultraviolet', diff --git a/examples/next/package.json b/examples/next/package.json index 7b6b73900a..937ec1da11 100644 --- a/examples/next/package.json +++ b/examples/next/package.json @@ -31,7 +31,7 @@ "schema-dts": "1.1.5" }, "devDependencies": { - "@babel/core": "7.28.4", + "@babel/core": "7.28.5", "@types/node": "22.18.11", "@types/react": "19.2.6", "@types/react-syntax-highlighter": "15.5.13", diff --git a/examples/next/src/components/Link.tsx b/examples/next/src/components/Link.tsx index 10cf5a9ab7..eedc975f62 100644 --- a/examples/next/src/components/Link.tsx +++ b/examples/next/src/components/Link.tsx @@ -34,5 +34,3 @@ export const Link = forwardRef( ), ) - -export default Link diff --git a/examples/next/src/pages/_document.tsx b/examples/next/src/pages/_document.tsx index 0aacc41404..6682b2ecc1 100644 --- a/examples/next/src/pages/_document.tsx +++ b/examples/next/src/pages/_document.tsx @@ -3,6 +3,7 @@ import { jsonLdScriptProps } from 'react-schemaorg' import type { CreativeWork } from 'schema-dts' class MyDocument extends Document { + // eslint-disable-next-line class-methods-use-this override render() { return ( diff --git a/packages/form/package.json b/packages/form/package.json index 703377e1bf..cf43bb59d7 100644 --- a/packages/form/package.json +++ b/packages/form/package.json @@ -68,7 +68,7 @@ "react-dom": "18.x || 19.x" }, "devDependencies": { - "@babel/core": "7.28.4", + "@babel/core": "7.28.5", "@types/final-form-focus": "1.1.7", "@types/react": "19.2.6", "@types/react-dom": "19.2.3", diff --git a/packages/form/src/providers/ErrorContext/index.tsx b/packages/form/src/providers/ErrorContext/index.tsx index 006343c445..b175edb4ba 100644 --- a/packages/form/src/providers/ErrorContext/index.tsx +++ b/packages/form/src/providers/ErrorContext/index.tsx @@ -45,6 +45,7 @@ export const ErrorProvider = ({ children, errors }: ErrorProviderProps) => { return {children} } +// oxlint-disable-next-line react/only-export-components export const useErrors = () => { const context = useContext(ErrorContext) diff --git a/packages/plus/src/components/EstimateCost/EstimateCostProvider.tsx b/packages/plus/src/components/EstimateCost/EstimateCostProvider.tsx index 4de7ef5057..49ae466408 100644 --- a/packages/plus/src/components/EstimateCost/EstimateCostProvider.tsx +++ b/packages/plus/src/components/EstimateCost/EstimateCostProvider.tsx @@ -9,6 +9,7 @@ const EstimateCostContext = createContext<{ formatNumber: (number: number, options: FormatNumberOption) => string }>({ formatNumber: () => '', locales: EstimateCostLocales }) +// oxlint-disable-next-line react/only-export-components export const useEstimateCost = () => useContext(EstimateCostContext) type EstimateCostProviderProps = { diff --git a/packages/plus/src/components/EstimateCost/OverlayContext.tsx b/packages/plus/src/components/EstimateCost/OverlayContext.tsx index 9ffe17f31a..d0cba00b99 100644 --- a/packages/plus/src/components/EstimateCost/OverlayContext.tsx +++ b/packages/plus/src/components/EstimateCost/OverlayContext.tsx @@ -4,6 +4,7 @@ import type { ReactNode } from 'react' import { createContext, useContext } from 'react' const OverlayContext = createContext({ isOverlay: false }) +// oxlint-disable-next-line react/only-export-components export const useOverlay = () => useContext(OverlayContext) type OverlayContextProviderProps = { diff --git a/packages/plus/src/components/Navigation/NavigationProvider.tsx b/packages/plus/src/components/Navigation/NavigationProvider.tsx index f06945dbeb..47c5c16754 100644 --- a/packages/plus/src/components/Navigation/NavigationProvider.tsx +++ b/packages/plus/src/components/Navigation/NavigationProvider.tsx @@ -62,6 +62,7 @@ type ContextProps = { animationType?: AnimationType } +// oxlint-disable-next-line react/only-export-components export const NavigationContext = createContext({ allowNavigationResize: true, animation: false, @@ -88,6 +89,7 @@ export const NavigationContext = createContext({ width: NAVIGATION_WIDTH, }) +// oxlint-disable-next-line react/only-export-components export const useNavigation = () => useContext(NavigationContext) type NavigationProviderProps = { diff --git a/packages/plus/src/components/Navigation/components/Item.tsx b/packages/plus/src/components/Navigation/components/Item.tsx index 5275bd28e5..d56037503a 100644 --- a/packages/plus/src/components/Navigation/components/Item.tsx +++ b/packages/plus/src/components/Navigation/components/Item.tsx @@ -148,7 +148,6 @@ type ItemProps = { } const onDragStopTrigger = (event: DragEvent) => { - // eslint-disable-next-line no-param-reassign event.currentTarget.style.opacity = '1' } @@ -318,7 +317,7 @@ export const Item = memo( 'text/plain', JSON.stringify({ index, label }), ) - // eslint-disable-next-line no-param-reassign + event.currentTarget.style.opacity = '0.5' } diff --git a/packages/plus/src/components/Navigation/components/ItemProvider.tsx b/packages/plus/src/components/Navigation/components/ItemProvider.tsx index 3eccabbb73..e8d88a8c77 100644 --- a/packages/plus/src/components/Navigation/components/ItemProvider.tsx +++ b/packages/plus/src/components/Navigation/components/ItemProvider.tsx @@ -4,6 +4,7 @@ import type { ReactNode } from 'react' import { createContext, useMemo } from 'react' // Create the context with a default value +// oxlint-disable-next-line react/only-export-components export const ItemContext = createContext(false) type ItemProviderProps = { diff --git a/packages/plus/src/components/Navigation/components/PinnedItems.tsx b/packages/plus/src/components/Navigation/components/PinnedItems.tsx index 7eeed88aa1..8433b66325 100644 --- a/packages/plus/src/components/Navigation/components/PinnedItems.tsx +++ b/packages/plus/src/components/Navigation/components/PinnedItems.tsx @@ -65,7 +65,6 @@ export const PinnedItems = ({ (event: DragEvent, index: number) => { event.preventDefault() if (event?.dataTransfer) { - // eslint-disable-next-line no-param-reassign event.currentTarget.style.borderColor = 'transparent' const data = JSON.parse( event.dataTransfer.getData('text'), @@ -86,7 +85,7 @@ export const PinnedItems = ({ const onDragOver = useCallback( (event: DragEvent) => { event.preventDefault() - // eslint-disable-next-line no-param-reassign + event.currentTarget.style.borderColor = theme.colors.primary.border }, [theme.colors.primary.border], @@ -94,7 +93,7 @@ export const PinnedItems = ({ const onDragLeave = useCallback((event: DragEvent) => { event.preventDefault() - // eslint-disable-next-line no-param-reassign + event.currentTarget.style.borderColor = 'transparent' }, []) diff --git a/packages/plus/src/components/OfferList/OfferListProvider.tsx b/packages/plus/src/components/OfferList/OfferListProvider.tsx index 7e56e41d1e..032838c5a4 100644 --- a/packages/plus/src/components/OfferList/OfferListProvider.tsx +++ b/packages/plus/src/components/OfferList/OfferListProvider.tsx @@ -62,6 +62,7 @@ export const OfferListProvider = ({ ) +// oxlint-disable-next-line react/only-export-components export const useOfferListContext = () => { const context = useContext(OfferListContext) diff --git a/packages/plus/src/components/OfferList/__stories__/Example.stories.tsx b/packages/plus/src/components/OfferList/__stories__/Example.stories.tsx index 399bdd5e93..39eb60e7ff 100644 --- a/packages/plus/src/components/OfferList/__stories__/Example.stories.tsx +++ b/packages/plus/src/components/OfferList/__stories__/Example.stories.tsx @@ -62,7 +62,7 @@ export const Example: StoryFn> = props => { const sortedData = useMemo(() => { const orderMultiplicator = currentOrder.order === 'asc' ? 1 : -1 - return [...data].sort((a, b) => { + return [...data].toSorted((a, b) => { if (a[currentOrder.columnId] < b[currentOrder.columnId]) { return -1 * orderMultiplicator } diff --git a/packages/plus/src/components/OrderSummary/helpers.tsx b/packages/plus/src/components/OrderSummary/helpers.ts similarity index 100% rename from packages/plus/src/components/OrderSummary/helpers.tsx rename to packages/plus/src/components/OrderSummary/helpers.ts diff --git a/packages/themes/src/ThemeProvider.tsx b/packages/themes/src/ThemeProvider.tsx index a7a06f66b6..044eb42cd7 100644 --- a/packages/themes/src/ThemeProvider.tsx +++ b/packages/themes/src/ThemeProvider.tsx @@ -11,6 +11,7 @@ const ThemeContext = createContext(consoleLightTheme) /** * Provide an object of the theme variables. */ +// oxlint-disable-next-line react/only-export-components export const useTheme = () => { const context = useContext(ThemeContext) if (!context) { diff --git a/packages/ui/src/__stories__/Properties/Properties.tsx b/packages/ui/src/__stories__/Properties/Properties.tsx index e564352736..04aa67bf15 100644 --- a/packages/ui/src/__stories__/Properties/Properties.tsx +++ b/packages/ui/src/__stories__/Properties/Properties.tsx @@ -95,7 +95,7 @@ const Properties = () => { const sortedPropertiesUsagesCountAndComponentsName = Object.entries( propertiesUsagesCountAndComponentsName, ) - .sort(([, { count: countA }], [, { count: countB }]) => countB - countA) + .toSorted(([, { count: countA }], [, { count: countB }]) => countB - countA) .reduce>( (acc, [property, value]) => ({ ...acc, @@ -163,10 +163,10 @@ const Properties = () => { } const reversedLocalProperty = [...lowerCaseLocalProperty] - .reverse() + .toReversed() .join('') const reversedLowerCaseProperty = [...lowerCaseProperty] - .reverse() + .toReversed() .join('') for ( diff --git a/packages/ui/src/__stories__/Tools/ThemeGenerator/contants.ts b/packages/ui/src/__stories__/Tools/ThemeGenerator/contants.ts index f50d40f358..6a5a26ed00 100644 --- a/packages/ui/src/__stories__/Tools/ThemeGenerator/contants.ts +++ b/packages/ui/src/__stories__/Tools/ThemeGenerator/contants.ts @@ -15,7 +15,7 @@ export const SHADES_KEYS = [ '1200', '1300', '1400', -].reverse() +].toReversed() /** * This is the mapping between shades name and sentiments names. diff --git a/packages/ui/src/components/Avatar/constants.ts b/packages/ui/src/components/Avatar/constants.ts index 487aee0ddd..6c35f92876 100644 --- a/packages/ui/src/components/Avatar/constants.ts +++ b/packages/ui/src/components/Avatar/constants.ts @@ -11,7 +11,7 @@ export const TEXT_VARIANT_BY_SIZE = { // Match the container size with actual px size export const sizes = (theme: typeof UVTheme) => ({ - large: '7rem', // TODO: add this value to tokens + large: '7rem', // Note: add this value to tokens medium: theme.sizing['800'], small: theme.sizing['400'], xsmall: theme.sizing['250'], diff --git a/packages/ui/src/components/BarChart/Tooltip.tsx b/packages/ui/src/components/BarChart/Tooltip.tsx index 9f2f3a4670..0fde8fb0b3 100644 --- a/packages/ui/src/components/BarChart/Tooltip.tsx +++ b/packages/ui/src/components/BarChart/Tooltip.tsx @@ -4,7 +4,7 @@ import { assignInlineVars } from '@vanilla-extract/dynamic' import { Text } from '../Text' import { barColorSquare, barTooltipContainer, colorBar } from './styles.css' -type BarChartToolTipProps = { +type BarChartTooltipProps = { color: string indexValue: string formattedValue: string @@ -12,13 +12,13 @@ type BarChartToolTipProps = { 'data-testid'?: string } -const BarChartToolTip = ({ +export const BarChartTooltip = ({ formattedValue, indexValue, color, className, 'data-testid': dataTestId, -}: BarChartToolTipProps) => ( +}: BarChartTooltipProps) => (
) - -export default BarChartToolTip diff --git a/packages/ui/src/components/BarChart/__tests__/Tooltip.test.tsx b/packages/ui/src/components/BarChart/__tests__/Tooltip.test.tsx index 7396fdd01a..9c4a257307 100644 --- a/packages/ui/src/components/BarChart/__tests__/Tooltip.test.tsx +++ b/packages/ui/src/components/BarChart/__tests__/Tooltip.test.tsx @@ -1,6 +1,6 @@ import { shouldMatchSnapshot } from '@utils/test' import { describe, test } from 'vitest' -import BarChartTooltip from '../Tooltip' +import { BarChartTooltip } from '../Tooltip' describe('barChartTooltip', () => { test('renders correctly', () => diff --git a/packages/ui/src/components/BarChart/index.tsx b/packages/ui/src/components/BarChart/index.tsx index 5bce6e6309..e6a1366d0c 100644 --- a/packages/ui/src/components/BarChart/index.tsx +++ b/packages/ui/src/components/BarChart/index.tsx @@ -9,7 +9,7 @@ import type { ComponentProps, CSSProperties } from 'react' import { useCallback } from 'react' import { getLegendColor } from '../../helpers/legend' import { getNivoTheme } from '../../helpers/nivoTheme' -import BarChartTooltip from './Tooltip' +import { BarChartTooltip } from './Tooltip' type Formatter = ValueFormat diff --git a/packages/ui/src/components/Button/__stories__/Size.stories.tsx b/packages/ui/src/components/Button/__stories__/Size.stories.tsx index d2fad49623..ec6202b30d 100644 --- a/packages/ui/src/components/Button/__stories__/Size.stories.tsx +++ b/packages/ui/src/components/Button/__stories__/Size.stories.tsx @@ -1,11 +1,12 @@ import type { StoryFn } from '@storybook/react-vite' import { PencilIcon } from '@ultraviolet/icons' import { Stack } from '../..' -import { Button, buttonSizes } from '..' +import { Button } from '..' +import { SIZE_KEY } from '../constants' export const Size: StoryFn = args => ( - {buttonSizes.map(size => ( + {SIZE_KEY.map(size => ( +) + +export const notification = ( + children: ((props: CloseButtonProps) => ReactNode) | ReactNode, + title: string, + icon?: ReactNode, + isClosable?: boolean, + containerId?: string, + options?: ToastOptions, +) => + baseToast('', { + ...options, + closeButton: props => ( + +
{icon}
+ + + {title} + + {typeof children === 'function' ? children(props) : children} + + {isClosable ? closeButton(props) : null} +
+ ), + containerId: containerId ?? 'notification', + }) diff --git a/packages/ui/src/components/Notification/NotificationContainer.tsx b/packages/ui/src/components/Notification/NotificationContainer.tsx new file mode 100644 index 0000000000..6ca18b0a97 --- /dev/null +++ b/packages/ui/src/components/Notification/NotificationContainer.tsx @@ -0,0 +1,59 @@ +'use client' + +import type { CSSProperties } from 'react' +import type { ToastOptions } from 'react-toastify' +import { ToastContainer as BaseToastContainer, Slide } from 'react-toastify' +import { notification as notificationStyle } from './styles.css' + +type NotificationContainerProps = { + /** + * Delay (in ms) before the notification autocloses. To disable autoclose, set to false + */ + autoClose?: false | number + /** + * Whether to display the newest toast on top. + * `Default: false` + */ + newestOnTop?: boolean + /** + * Limit the number of toast displayed at the same time + */ + limit?: number + /** + * Position on the notification container + */ + position?: ToastOptions['position'] + 'data-testid'?: string + className?: string + /** + * Give a personalized containerId in case there are multiple notifications with different styled to display + */ + containerId?: string + style?: CSSProperties +} + +export const NotificationContainer = ({ + newestOnTop, + limit, + autoClose = false, + position = 'top-right', + 'data-testid': dataTestId, + className, + style, + containerId = 'notification', +}: NotificationContainerProps) => ( + +) diff --git a/packages/ui/src/components/Notification/index.ts b/packages/ui/src/components/Notification/index.ts new file mode 100644 index 0000000000..043a353e2c --- /dev/null +++ b/packages/ui/src/components/Notification/index.ts @@ -0,0 +1,2 @@ +export { notification } from './Notification' +export { NotificationContainer } from './NotificationContainer' diff --git a/packages/ui/src/components/Notification/index.tsx b/packages/ui/src/components/Notification/index.tsx deleted file mode 100644 index b4332d81f3..0000000000 --- a/packages/ui/src/components/Notification/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -'use client' - -import { CloseIcon } from '@ultraviolet/icons' -import type { CSSProperties, ReactNode } from 'react' -import type { - Theme as ThemeToastify, - ToastOptions, - TypeOptions, -} from 'react-toastify' -import { - ToastContainer as BaseToastContainer, - toast as baseToast, - Slide, -} from 'react-toastify' -import { Button } from '../Button' -import { Stack } from '../Stack' -import { Text } from '../Text' -import { notification as notificationStyle } from './styles.css' - -type CloseButtonProps = { - closeToast: (event: React.MouseEvent) => void - type: TypeOptions - ariaLabel?: string - theme: ThemeToastify -} - -const closeButton = (props: CloseButtonProps) => ( - -) - -export const notification = ( - children: ((props: CloseButtonProps) => ReactNode) | ReactNode, - title: string, - icon?: ReactNode, - isClosable?: boolean, - containerId?: string, - options?: ToastOptions, -) => - baseToast('', { - ...options, - closeButton: props => ( - -
{icon}
- - - {title} - - {typeof children === 'function' ? children(props) : children} - - {isClosable ? closeButton(props) : null} -
- ), - containerId: containerId ?? 'notification', - }) - -type NotificationContainerProps = { - /** - * Delay (in ms) before the notification autocloses. To disable autoclose, set to false - */ - autoClose?: false | number - /** - * Whether to display the newest toast on top. - * `Default: false` - */ - newestOnTop?: boolean - /** - * Limit the number of toast displayed at the same time - */ - limit?: number - /** - * Position on the notification container - */ - position?: ToastOptions['position'] - 'data-testid'?: string - className?: string - /** - * Give a personalized containerId in case there are multiple notifications with different styled to display - */ - containerId?: string - style?: CSSProperties -} - -export const NotificationContainer = ({ - newestOnTop, - limit, - autoClose = false, - position = 'top-right', - 'data-testid': dataTestId, - className, - style, - containerId = 'notification', -}: NotificationContainerProps) => ( - -) diff --git a/packages/ui/src/components/PieChart/Legends.tsx b/packages/ui/src/components/PieChart/Legends.tsx index b1a86b96e7..9adc678861 100644 --- a/packages/ui/src/components/PieChart/Legends.tsx +++ b/packages/ui/src/components/PieChart/Legends.tsx @@ -14,7 +14,7 @@ import { toggleBoxPie, valuePie, } from './styles.css' -import TooltipContainer from './Tooltip' +import { Tooltip as TooltipContainer } from './Tooltip' import type { Data } from './types' type LegendsProps = { @@ -24,7 +24,12 @@ type LegendsProps = { colors: string[] } -const Legends = ({ focused, data, onFocusChange, colors }: LegendsProps) => ( +export const Legends = ({ + focused, + data, + onFocusChange, + colors, +}: LegendsProps) => (
    {data?.map((item, index) => { const isSegmentFocused = focused !== undefined && item.id === focused @@ -78,5 +83,3 @@ const Legends = ({ focused, data, onFocusChange, colors }: LegendsProps) => ( })}
) - -export default Legends diff --git a/packages/ui/src/components/PieChart/Tooltip.tsx b/packages/ui/src/components/PieChart/Tooltip.tsx index df1b4ba3f4..43fbdef449 100644 --- a/packages/ui/src/components/PieChart/Tooltip.tsx +++ b/packages/ui/src/components/PieChart/Tooltip.tsx @@ -16,7 +16,7 @@ type TooltipProps = { } } -const Tooltip = ({ data }: TooltipProps) => ( +export const Tooltip = ({ data }: TooltipProps) => (
  • @@ -40,5 +40,3 @@ const Tooltip = ({ data }: TooltipProps) => (
) - -export default Tooltip diff --git a/packages/ui/src/components/PieChart/__tests__/index.test.tsx b/packages/ui/src/components/PieChart/__tests__/index.test.tsx index fa69a24e15..1a594caaca 100644 --- a/packages/ui/src/components/PieChart/__tests__/index.test.tsx +++ b/packages/ui/src/components/PieChart/__tests__/index.test.tsx @@ -65,7 +65,6 @@ describe('pieChart', () => { expect(asFragment()).toMatchSnapshot() }) - // TODO: fixme test.skip('renders correctly when chart is hovered', async () => { const { container } = renderWithTheme( , diff --git a/packages/ui/src/components/PieChart/index.tsx b/packages/ui/src/components/PieChart/index.tsx index c674dc34a1..3521af58c7 100644 --- a/packages/ui/src/components/PieChart/index.tsx +++ b/packages/ui/src/components/PieChart/index.tsx @@ -10,7 +10,7 @@ import { useCallback, useState } from 'react' import { getLegendColor } from '../../helpers/legend' import { getNivoTheme } from '../../helpers/nivoTheme' import { Text } from '../Text' -import Legends from './Legends' +import { Legends } from './Legends' import { containerPie, contentPie, diff --git a/packages/ui/src/components/Row/styles.css.ts b/packages/ui/src/components/Row/styles.css.ts index 7b38441856..88c5c73c64 100644 --- a/packages/ui/src/components/Row/styles.css.ts +++ b/packages/ui/src/components/Row/styles.css.ts @@ -44,13 +44,11 @@ const breakpoints = Object.keys( ) as (keyof typeof consoleLightTheme.breakpoints)[] // Get the keys and sort them by their pixel value. It's important to define breakpoints priority -const orderedBreakpointKeys = [ - ...breakpoints.sort( - (a, b) => - Number.parseInt(consoleLightTheme.breakpoints[a], 10) - - Number.parseInt(consoleLightTheme.breakpoints[b], 10), - ), -] as const +const orderedBreakpointKeys = breakpoints.toSorted( + (a, b) => + Number.parseInt(consoleLightTheme.breakpoints[a], 10) - + Number.parseInt(consoleLightTheme.breakpoints[b], 10), +) const themeBreakpoints = orderedBreakpointKeys.reduce( (acc, key) => ({ diff --git a/packages/ui/src/components/SelectInput/SelectInputProvider.tsx b/packages/ui/src/components/SelectInput/SelectInputProvider.tsx index 8966523ce4..9cd75e856c 100644 --- a/packages/ui/src/components/SelectInput/SelectInputProvider.tsx +++ b/packages/ui/src/components/SelectInput/SelectInputProvider.tsx @@ -53,6 +53,7 @@ const SelectInputContext = createContext({ setSelectedData: () => {}, }) +// oxlint-disable-next-line react/only-export-components export const useSelectInput = () => useContext(SelectInputContext) type SelectInputProviderProps = { diff --git a/packages/ui/src/components/SelectInput/components/SearchBarDropdown.tsx b/packages/ui/src/components/SelectInput/components/SearchBarDropdown.tsx index 4748f57f60..1692196c71 100644 --- a/packages/ui/src/components/SelectInput/components/SearchBarDropdown.tsx +++ b/packages/ui/src/components/SelectInput/components/SearchBarDropdown.tsx @@ -15,7 +15,7 @@ type SearchBarProps = { setSearchBarActive: Dispatch> } -export const getReferenceText = (option: OptionType) => { +const getReferenceText = (option: OptionType) => { if (option.searchText) { return normalizeString(option.searchText) } @@ -27,7 +27,7 @@ export const getReferenceText = (option: OptionType) => { } // It uses Levenshtein distance so that the search is typo-tolerant for a simple fuzzy-search -export const searchRegex = (data: OptionType[], query: string) => +const searchRegex = (data: OptionType[], query: string) => data.filter(option => { const referenceText = getReferenceText(option) const regex = new RegExp(query, 'i') @@ -158,11 +158,17 @@ export const SearchBarDropdown = ({ } useEffect(() => { - // TODO: Remove me and use autoFocus when popup is fixed + // note: Remove me and use autoFocus when popup is fixed // Autofocus on the search bar create some scroll issues - setTimeout(() => { + const timeout = setTimeout(() => { searchInputRef.current?.focus() }, 50) + + return () => { + if (timeout) { + clearTimeout(timeout) + } + } }, []) return ( diff --git a/packages/ui/src/components/Skeleton/index.tsx b/packages/ui/src/components/Skeleton/index.tsx index 63238abfdd..fad46319fd 100644 --- a/packages/ui/src/components/Skeleton/index.tsx +++ b/packages/ui/src/components/Skeleton/index.tsx @@ -66,4 +66,5 @@ export const Skeleton = ({ ) } +// oxlint-disable-next-line react/only-export-components export const skeletonTypes = Object.keys(variants) as SkeletonVariant[] diff --git a/packages/ui/src/components/Stack/styles.css.ts b/packages/ui/src/components/Stack/styles.css.ts index d0b39fbbd1..596890151a 100644 --- a/packages/ui/src/components/Stack/styles.css.ts +++ b/packages/ui/src/components/Stack/styles.css.ts @@ -12,7 +12,9 @@ export const stack = style({ }) // Get the keys and sort them by their pixel value. It's important to define breakpoints priority -const orderedBreakpointKeys = Object.keys(consoleLightTheme.breakpoints).sort( +const orderedBreakpointKeys = Object.keys( + consoleLightTheme.breakpoints, +).toSorted( (a, b) => Number.parseInt( consoleLightTheme.breakpoints[ diff --git a/packages/ui/src/components/Stepper/StepperProvider.tsx b/packages/ui/src/components/Stepper/StepperProvider.tsx index d5dd471667..796ddb513e 100644 --- a/packages/ui/src/components/Stepper/StepperProvider.tsx +++ b/packages/ui/src/components/Stepper/StepperProvider.tsx @@ -33,6 +33,7 @@ type StepperProviderProps = { separator: boolean } +// oxlint-disable-next-line react/only-export-components export const useStepper = () => useContext(StepperContext) /** * Stepper component to show the progress of a process in a linear way. diff --git a/packages/ui/src/components/Stepper/index.tsx b/packages/ui/src/components/Stepper/index.tsx index 062b8e3a32..66ba2495b8 100644 --- a/packages/ui/src/components/Stepper/index.tsx +++ b/packages/ui/src/components/Stepper/index.tsx @@ -37,7 +37,9 @@ export const Stepper = ({ separator = true, style, }: StepperProps) => { - const cleanChildren = Children.toArray(children).filter(isValidElement) + const cleanChildren = Children.toArray(children).filter(child => + isValidElement(child), + ) const lastStep = Children.count(cleanChildren) - 1 return ( diff --git a/packages/ui/src/components/Table/TableContext.tsx b/packages/ui/src/components/Table/TableContext.tsx index 3f778ede7a..836ab2c715 100644 --- a/packages/ui/src/components/Table/TableContext.tsx +++ b/packages/ui/src/components/Table/TableContext.tsx @@ -61,6 +61,7 @@ export const TableProvider = ({ ) +// oxlint-disable-next-line react/only-export-components export const useTableContext = () => { const context = useContext(TableContext) if (!context) { diff --git a/packages/ui/src/components/Table/__stories__/Ordering.stories.tsx b/packages/ui/src/components/Table/__stories__/Ordering.stories.tsx index 579bcdb9f4..ec458b31f2 100644 --- a/packages/ui/src/components/Table/__stories__/Ordering.stories.tsx +++ b/packages/ui/src/components/Table/__stories__/Ordering.stories.tsx @@ -12,7 +12,7 @@ export const Ordering: StoryFn = args => { const sortedData = useMemo(() => { const orderMultiplicator = currentOrder.order === 'asc' ? 1 : -1 - return [...sourceData].sort((a, b) => { + return [...sourceData].toSorted((a, b) => { if (a[currentOrder.columnId] < b[currentOrder.columnId]) { return -1 * orderMultiplicator } diff --git a/packages/ui/src/components/Table/index.tsx b/packages/ui/src/components/Table/index.tsx index cc09ef35d6..1c52bd7d2b 100644 --- a/packages/ui/src/components/Table/index.tsx +++ b/packages/ui/src/components/Table/index.tsx @@ -23,7 +23,7 @@ import type { ColumnProps } from './types' // type OptionalOnly = Pick> -// TODO: Get type optional from omit values +// Note: Get type optional from omit values type TableProps = Omit< TableProviderProps, | 'selectable' diff --git a/packages/ui/src/components/TagList/index.tsx b/packages/ui/src/components/TagList/index.tsx index 0f3e1a35dc..6ced8a91ca 100644 --- a/packages/ui/src/components/TagList/index.tsx +++ b/packages/ui/src/components/TagList/index.tsx @@ -223,7 +223,7 @@ export const TagList = ({ const visibleTagsCopy = visibleTags.filter( (_, index) => index < visibleTags.length - 1, ) - const tagToMove = visibleTags[visibleTags.length - 1] + const tagToMove = visibleTags[visibleTags.length - 1] ?? '' setVisibleTags(visibleTagsCopy) setHiddenTags([tagToMove, ...hiddenTags]) diff --git a/packages/ui/src/components/Text/__stories__/Variants.stories.tsx b/packages/ui/src/components/Text/__stories__/Variants.stories.tsx index b98f488532..6432594db5 100644 --- a/packages/ui/src/components/Text/__stories__/Variants.stories.tsx +++ b/packages/ui/src/components/Text/__stories__/Variants.stories.tsx @@ -1,5 +1,6 @@ import type { StoryFn } from '@storybook/react-vite' -import { Text, textVariants } from '../index' +import { Text } from '..' +import { textVariants } from '../constants' export const Variants: StoryFn = props => ( <> diff --git a/packages/ui/src/components/Text/__tests__/index.test.tsx b/packages/ui/src/components/Text/__tests__/index.test.tsx index 1858779d7b..57ebc976dd 100644 --- a/packages/ui/src/components/Text/__tests__/index.test.tsx +++ b/packages/ui/src/components/Text/__tests__/index.test.tsx @@ -1,6 +1,7 @@ import { shouldMatchSnapshot } from '@utils/test' import { describe, test } from 'vitest' -import { Text, textVariants } from '..' +import { Text } from '..' +import { textVariants } from '../constants' describe('text', () => { test.each(textVariants)('renders correctly with type="%s"', variant => diff --git a/packages/ui/src/components/Text/constant.ts b/packages/ui/src/components/Text/constant.ts deleted file mode 100644 index af6a846cbf..0000000000 --- a/packages/ui/src/components/Text/constant.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const PROMINENCES = { - default: '', - strong: 'strong', - stronger: 'stronger', - weak: 'weak', -} diff --git a/packages/ui/src/components/Text/constants.ts b/packages/ui/src/components/Text/constants.ts new file mode 100644 index 0000000000..508d41cbb4 --- /dev/null +++ b/packages/ui/src/components/Text/constants.ts @@ -0,0 +1,12 @@ +import { typography } from '../../theme' + +export const PROMINENCES = { + default: '', + strong: 'strong', + stronger: 'stronger', + weak: 'weak', +} + +export type TextVariant = keyof typeof typography + +export const textVariants = Object.keys(typography) as TextVariant[] diff --git a/packages/ui/src/components/Text/index.tsx b/packages/ui/src/components/Text/index.tsx index 0d7bbfe43e..d251eb0809 100644 --- a/packages/ui/src/components/Text/index.tsx +++ b/packages/ui/src/components/Text/index.tsx @@ -6,17 +6,14 @@ import { useRef } from 'react' import recursivelyGetChildrenString from '../../helpers/recursivelyGetChildrenString' import { useIsOverflowing } from '../../hooks/useIsOverflowing' import type { ExtendedColor } from '../../theme' -import { typography } from '../../theme' import { Tooltip } from '../Tooltip' -import type { PROMINENCES } from './constant' +import type { PROMINENCES, TextVariant } from './constants' import { text } from './style.css' import { placementText, whiteSpaceText } from './variables.css' type ProminenceProps = keyof typeof PROMINENCES type PlacementProps = CSSProperties['textAlign'] type WhiteSpaceProps = CSSProperties['whiteSpace'] -type TextVariant = keyof typeof typography -export const textVariants = Object.keys(typography) as TextVariant[] type TextProps = { className?: string diff --git a/packages/ui/src/components/Text/style.css.ts b/packages/ui/src/components/Text/style.css.ts index eb97eed8d4..35ccb0d152 100644 --- a/packages/ui/src/components/Text/style.css.ts +++ b/packages/ui/src/components/Text/style.css.ts @@ -3,7 +3,7 @@ import type { RecipeVariants } from '@vanilla-extract/recipes' import { recipe } from '@vanilla-extract/recipes' import type { ExtendedColor } from 'src/theme' import { typography } from '../../theme' -import { PROMINENCES } from './constant' +import { PROMINENCES } from './constants' import capitalize from '../../utils/capitalize' import { placementText, whiteSpaceText } from './variables.css' diff --git a/packages/ui/src/components/Toaster/Toaster.tsx b/packages/ui/src/components/Toaster/Toaster.tsx new file mode 100644 index 0000000000..61c01c32d6 --- /dev/null +++ b/packages/ui/src/components/Toaster/Toaster.tsx @@ -0,0 +1,42 @@ +'use client' + +import type { ReactNode } from 'react' +import type { ToastOptions } from 'react-toastify' +import { toast as baseToast } from 'react-toastify' +import { CloseButton } from './components/CloseButton' +import { Content } from './components/Content' + +export const toast = { + error: ( + children: ReactNode, + options?: ToastOptions, + containerId?: string, + ): number | string => + baseToast.error({children}, { + ...options, + closeButton: , + containerId: containerId ?? 'toaster', + }), + + success: ( + children: ReactNode, + options?: ToastOptions, + containerId?: string, + ): number | string => + baseToast.success({children}, { + ...options, + closeButton: , + containerId: containerId ?? 'toaster', + }), + + warning: ( + children: ReactNode, + options?: ToastOptions, + containerId?: string, + ): number | string => + baseToast.warn({children}, { + ...options, + closeButton: , + containerId: containerId ?? 'toaster', + }), +} diff --git a/packages/ui/src/components/Toaster/ToasterContainer.tsx b/packages/ui/src/components/Toaster/ToasterContainer.tsx new file mode 100644 index 0000000000..1c3a4e0412 --- /dev/null +++ b/packages/ui/src/components/Toaster/ToasterContainer.tsx @@ -0,0 +1,72 @@ +'use client' + +import type { CSSProperties } from 'react' +import type { ToastOptions } from 'react-toastify' +import { ToastContainer as BaseToastContainer, Slide } from 'react-toastify' +import { AUTOCLOSE_DELAY } from './constants' +import { toaster } from './styles.css' + +type ToastContainerProps = { + /** + * Whether to display the newest toast on top. + * `Default: false` + */ + newestOnTop?: boolean + /** + * Limit the number of toast displayed at the same time + */ + limit?: number + /** + * Position of the toast container + */ + position?: ToastOptions['position'] + 'data-testid'?: string + className?: string + /** + * Delay before the toast is automatically closed, if not set the default value is 6000ms + */ + autoClose?: number + + /** + * Set a custom containerId to be able to define multiple ToastContainers + */ + containerId?: string + style?: CSSProperties +} + +/** + * Display short information about an event that happen in the interface in a floating alert. + * Toaster is based on **react-tostify**, you can find a complete documentation + * [here](https://fkhadra.github.io/react-toastify/introduction/). + * + * Toaster is separated in two parts, first the `ToastContainer` which is where the div of the toast will be rendered, + * and second the `toast()` function which is used to display the toast. + */ +export const ToastContainer = ({ + newestOnTop, + limit, + position = 'top-right', + 'data-testid': dataTestId, + className, + autoClose, + containerId = 'toaster', + style, +}: ToastContainerProps) => ( + +) diff --git a/packages/ui/src/components/Toaster/components/CloseButton.tsx b/packages/ui/src/components/Toaster/components/CloseButton.tsx new file mode 100644 index 0000000000..bd758ecb1e --- /dev/null +++ b/packages/ui/src/components/Toaster/components/CloseButton.tsx @@ -0,0 +1,28 @@ +'use client' + +import { CloseIcon } from '@ultraviolet/icons' +import type { SENTIMENTS } from '../../../theme' +import { Button } from '../../Button' +import { closeButtonToaster } from '../styles.css' + +type SENTIMENT = (typeof SENTIMENTS)[number] + +type CloseButtonProps = { + closeToast?: () => void + sentiment: SENTIMENT +} + +export const CloseButton = ({ + closeToast, + sentiment = 'success', +}: CloseButtonProps) => ( + +) diff --git a/packages/ui/src/components/Toaster/components/Content.tsx b/packages/ui/src/components/Toaster/components/Content.tsx new file mode 100644 index 0000000000..24ffe30a19 --- /dev/null +++ b/packages/ui/src/components/Toaster/components/Content.tsx @@ -0,0 +1,21 @@ +'use client' + +import type { ReactNode } from 'react' +import { Stack } from '../../Stack' +import { Text } from '../../Text' + +type ContentProps = { + children?: ReactNode +} + +export const Content = ({ children }: ContentProps) => ( + + {typeof children === 'string' ? ( + + {children} + + ) : ( + children + )} + +) diff --git a/packages/ui/src/components/Toaster/constants.ts b/packages/ui/src/components/Toaster/constants.ts new file mode 100644 index 0000000000..f1e675cefc --- /dev/null +++ b/packages/ui/src/components/Toaster/constants.ts @@ -0,0 +1,2 @@ +// Delay to close the toast in ms +export const AUTOCLOSE_DELAY = 6_000 diff --git a/packages/ui/src/components/Toaster/index.ts b/packages/ui/src/components/Toaster/index.ts new file mode 100644 index 0000000000..2092e42207 --- /dev/null +++ b/packages/ui/src/components/Toaster/index.ts @@ -0,0 +1,10 @@ +import { ToastButton } from './components/Button' +import { ToastLink } from './components/Link' + +export const Toast = { + Button: ToastButton, + Link: ToastLink, +} + +export { toast } from './Toaster' +export { ToastContainer } from './ToasterContainer' diff --git a/packages/ui/src/components/Toaster/index.tsx b/packages/ui/src/components/Toaster/index.tsx deleted file mode 100644 index bb3f096f08..0000000000 --- a/packages/ui/src/components/Toaster/index.tsx +++ /dev/null @@ -1,160 +0,0 @@ -'use client' - -import { CloseIcon } from '@ultraviolet/icons' -import type { CSSProperties, ReactNode } from 'react' -import type { ToastOptions } from 'react-toastify' -import { - ToastContainer as BaseToastContainer, - toast as baseToast, - Slide, -} from 'react-toastify' -import type { SENTIMENTS } from '../../theme' -import { Button } from '../Button' -import { Stack } from '../Stack' -import { Text } from '../Text' -import { ToastButton } from './components/Button' -import { ToastLink } from './components/Link' -import { closeButtonToaster, toaster } from './styles.css' - -const AUTOCLOSE_DELAY = 6000 // Delay to close the toast in ms -type SENTIMENT = (typeof SENTIMENTS)[number] - -type CloseButtonProps = { - closeToast?: () => void - sentiment: SENTIMENT -} -const CloseButton = ({ - closeToast, - sentiment = 'success', -}: CloseButtonProps) => ( - -) - -type ContentProps = { - children?: ReactNode -} - -const Content = ({ children }: ContentProps) => ( - - {typeof children === 'string' ? ( - - {children} - - ) : ( - children - )} - -) - -export const toast = { - error: ( - children: ReactNode, - options?: ToastOptions, - containerId?: string, - ): number | string => - baseToast.error({children}, { - ...options, - closeButton: , - containerId: containerId ?? 'toaster', - }), - - success: ( - children: ReactNode, - options?: ToastOptions, - containerId?: string, - ): number | string => - baseToast.success({children}, { - ...options, - closeButton: , - containerId: containerId ?? 'toaster', - }), - - warning: ( - children: ReactNode, - options?: ToastOptions, - containerId?: string, - ): number | string => - baseToast.warn({children}, { - ...options, - closeButton: , - containerId: containerId ?? 'toaster', - }), -} - -type ToastContainerProps = { - /** - * Whether to display the newest toast on top. - * `Default: false` - */ - newestOnTop?: boolean - /** - * Limit the number of toast displayed at the same time - */ - limit?: number - /** - * Position of the toast container - */ - position?: ToastOptions['position'] - 'data-testid'?: string - className?: string - /** - * Delay before the toast is automatically closed, if not set the default value is 6000ms - */ - autoClose?: number - - /** - * Set a custom containerId to be able to define multiple ToastContainers - */ - containerId?: string - style?: CSSProperties -} - -/** - * Display short information about an event that happen in the interface in a floating alert. - * Toaster is based on **react-tostify**, you can find a complete documentation - * [here](https://fkhadra.github.io/react-toastify/introduction/). - * - * Toaster is separated in two parts, first the `ToastContainer` which is where the div of the toast will be rendered, - * and second the `toast()` function which is used to display the toast. - */ -export const ToastContainer = ({ - newestOnTop, - limit, - position = 'top-right', - 'data-testid': dataTestId, - className, - autoClose, - containerId = 'toaster', - style, -}: ToastContainerProps) => ( - -) - -export const Toast = { - Button: ToastButton, - Link: ToastLink, -} diff --git a/packages/ui/src/components/VerificationCode/__stories__/Size.stories.tsx b/packages/ui/src/components/VerificationCode/__stories__/Size.stories.tsx index ec1cbdadfa..6fe1e77e9d 100644 --- a/packages/ui/src/components/VerificationCode/__stories__/Size.stories.tsx +++ b/packages/ui/src/components/VerificationCode/__stories__/Size.stories.tsx @@ -1,6 +1,7 @@ import type { StoryFn } from '@storybook/react-vite' import { Stack } from '../..' -import { VerificationCode, verificationCodeSizes } from '..' +import { VerificationCode } from '..' +import { verificationCodeSizes } from '../constants' export const Size: StoryFn = args => ( diff --git a/packages/ui/src/components/VerificationCode/constants.ts b/packages/ui/src/components/VerificationCode/constants.ts index 0e9b71f4fe..9fe913832f 100644 --- a/packages/ui/src/components/VerificationCode/constants.ts +++ b/packages/ui/src/components/VerificationCode/constants.ts @@ -1,3 +1,5 @@ +const sizes = ['small', 'medium', 'large', 'xlarge'] as const + export const SIZE_HEIGHT = { large: '600', medium: '500', @@ -11,3 +13,5 @@ export const SIZE_WIDTH = { small: '300', xlarge: '700', } as const + +export const verificationCodeSizes = sizes diff --git a/packages/ui/src/components/VerificationCode/index.tsx b/packages/ui/src/components/VerificationCode/index.tsx index b2d720e805..a03920b2f8 100644 --- a/packages/ui/src/components/VerificationCode/index.tsx +++ b/packages/ui/src/components/VerificationCode/index.tsx @@ -11,13 +11,8 @@ import type { import { createRef, useId, useMemo, useState } from 'react' import { Label } from '../Label' import { Text } from '../Text' -import { SIZE_HEIGHT } from './constants' import { filedSetClass, inputClass, inputSizes } from './styles.css' -type Size = 'small' | 'medium' | 'large' | 'xlarge' - -export const verificationCodeSizes = Object.keys(SIZE_HEIGHT) as Size[] - const DEFAULT_ON_FUNCTION = () => {} const inputOnFocus: FocusEventHandler = event => diff --git a/packages/ui/src/helpers/legend.ts b/packages/ui/src/helpers/legend.ts index d78e544580..a62ce63855 100644 --- a/packages/ui/src/helpers/legend.ts +++ b/packages/ui/src/helpers/legend.ts @@ -5,7 +5,7 @@ export const getLegendColor = (theme: typeof UVTheme): string[] => { return Object.keys(colors.other.data.charts) .filter(key => !['success', 'danger'].includes(key)) - .sort((a, b) => { + .toSorted((a, b) => { if (Number(a.replace('data', '')) < Number(b.replace('data', ''))) { return -1 } diff --git a/packages/ui/src/hooks/useIsOverflowing.ts b/packages/ui/src/hooks/useIsOverflowing.ts index 4befdc6b6a..fe2b424d0b 100644 --- a/packages/ui/src/hooks/useIsOverflowing.ts +++ b/packages/ui/src/hooks/useIsOverflowing.ts @@ -27,7 +27,7 @@ export const useIsOverflowing = ( // This will add the function into the browser event queue after the DOM is painted // which is needed to get the correct offsetWidth and scrollWidth - setTimeout(() => handleResize(), 0) + const timeout = setTimeout(() => handleResize(), 0) // Listen for resize events window.addEventListener('resize', handleResize) @@ -35,6 +35,7 @@ export const useIsOverflowing = ( // Cleanup the event listener return () => { window.removeEventListener('resize', handleResize) + clearTimeout(timeout) } }, [ref, callback]) diff --git a/packages/ui/src/theme/ThemeProvider.tsx b/packages/ui/src/theme/ThemeProvider.tsx index 3500326075..d416f05d46 100644 --- a/packages/ui/src/theme/ThemeProvider.tsx +++ b/packages/ui/src/theme/ThemeProvider.tsx @@ -10,6 +10,7 @@ const ThemeContext = createContext(consoleLightTheme) /** * Provide an object of the theme variables. */ +// oxlint-disable-next-line react/only-export-components export const useTheme = () => { const context = useContext(ThemeContext) if (!context) { diff --git a/utils/scripts/analyse-deps.ts b/utils/scripts/analyse-deps.ts index 44c4404eb3..406c45d94b 100644 --- a/utils/scripts/analyse-deps.ts +++ b/utils/scripts/analyse-deps.ts @@ -62,7 +62,7 @@ for (const file of filesToAnalyze) { string => normalizedFile.endsWith(string), ) ) { - const importedComponent = normalizedFile.split('/').reverse()[0] + const importedComponent = normalizedFile.split('/').toReversed()[0] if (!graph[componentName]) { graph[componentName] = { dependsOn: [] } @@ -123,7 +123,7 @@ const asRecord = Object.fromEntries( return [name, filteredDeps.length] as const }) - .sort(([, aCount], [, bCount]) => aCount - bCount), + .toSorted(([, aCount], [, bCount]) => aCount - bCount), ) fs.writeFileSync('depsFiltered.json', JSON.stringify(asRecord, null, 2)) diff --git a/utils/scripts/figma-synchronise-tokens.tsx b/utils/scripts/figma-synchronise-tokens.tsx index 3267031fcc..b63e3ff222 100755 --- a/utils/scripts/figma-synchronise-tokens.tsx +++ b/utils/scripts/figma-synchronise-tokens.tsx @@ -70,13 +70,12 @@ const createCSSFile = (theme: string, content: UvThemeType) => { } const alphaOrder = (obj: JsonType) => { - const orderedKeys = Object.keys(obj ?? {}).sort() + const orderedKeys = Object.keys(obj ?? {}).toSorted() return orderedKeys.reduce((newObj: Record, key) => { const result = typeof obj[key] === 'string' ? obj[key] : alphaOrder(obj[key] as JsonType) - // eslint-disable-next-line no-param-reassign newObj[key] = result return newObj @@ -171,7 +170,6 @@ const getValues = ( ) if (newValue !== null) { - // eslint-disable-next-line no-param-reassign values[key.replaceAll(/,/g, '.')] = newValue } @@ -345,8 +343,11 @@ const writeFiles = async () => { }), ) } -;(async () => { + +try { if (typeof process !== 'undefined' && process.versions?.node) { await writeFiles() } -})().catch(console.error) +} catch (error) { + console.error(error) +} diff --git a/vite.config.ts b/vite.config.ts index 162480cc69..7b9b26e9a1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -20,7 +20,7 @@ const external = (id: string) => { } const match = (dependency: string) => new RegExp(`^${dependency}`).test(id) - const isExternal = externalPkgs.some(match) + const isExternal = externalPkgs.some(dep => match(dep)) // alias of bundledDependencies package.json field array const isBundled = pkg.bundleDependencies?.some(match) From c00eacb1b75e0cf768364fbe6be064cce217f539 Mon Sep 17 00:00:00 2001 From: aphilibeaux Date: Mon, 17 Nov 2025 12:16:33 +0100 Subject: [PATCH 3/3] fix(popup): handle auto scroll and remove useless deps effect Signed-off-by: aphilibeaux --- .../__stories__/Checked.stories.tsx | 2 + .../ToggleField/__stories__/index.stories.tsx | 5 +- .../components/DateInput/components/Popup.tsx | 4 +- packages/ui/src/components/Popup/index.tsx | 101 +++++++++----- .../SelectInput/components/Dropdown.tsx | 132 +++++++++++------- .../SwitchButton/__tests__/index.test.tsx | 4 +- packages/ui/src/components/TagList/index.tsx | 2 +- .../src/components/VerificationCode/index.tsx | 2 +- utils/test/src/vitest/setup.ts | 2 + 9 files changed, 169 insertions(+), 85 deletions(-) diff --git a/packages/form/src/components/ToggleField/__stories__/Checked.stories.tsx b/packages/form/src/components/ToggleField/__stories__/Checked.stories.tsx index 29c9a9f99a..f8b708917f 100644 --- a/packages/form/src/components/ToggleField/__stories__/Checked.stories.tsx +++ b/packages/form/src/components/ToggleField/__stories__/Checked.stories.tsx @@ -3,6 +3,8 @@ import { Template } from './Template.stories' export const Checked = Template.bind({}) Checked.args = { + label: 'label', name: 'checked', + tooltip: 'checked', value: true, } diff --git a/packages/form/src/components/ToggleField/__stories__/index.stories.tsx b/packages/form/src/components/ToggleField/__stories__/index.stories.tsx index 5f28f0680b..9b5652d958 100644 --- a/packages/form/src/components/ToggleField/__stories__/index.stories.tsx +++ b/packages/form/src/components/ToggleField/__stories__/index.stories.tsx @@ -1,6 +1,7 @@ import type { Meta } from '@storybook/react-vite' import { Snippet, Stack, Text } from '@ultraviolet/ui' -import { Form, ToggleField } from '../..' +import { Form } from '../..' +import { ToggleField } from '..' import { useForm } from '../../..' import { mockErrors } from '../../../mocks' @@ -80,3 +81,5 @@ export default { export { Playground } from './Playground.stories' export { Required } from './Required.stories' +export { Checked } from './Checked.stories' +export { ActAsRadio } from './ActAsRadio.stories' diff --git a/packages/ui/src/components/DateInput/components/Popup.tsx b/packages/ui/src/components/DateInput/components/Popup.tsx index 9e346f2bd9..4750ba6224 100644 --- a/packages/ui/src/components/DateInput/components/Popup.tsx +++ b/packages/ui/src/components/DateInput/components/Popup.tsx @@ -1,7 +1,7 @@ 'use client' import type { Dispatch, ReactNode, RefObject, SetStateAction } from 'react' -import { useEffect, useRef } from 'react' +import { useLayoutEffect, useRef } from 'react' import { Popup } from '../../Popup' import { POPUP_WIDTH } from '../constants' import { dateinputPopup } from './styles.css' @@ -38,7 +38,7 @@ export const CalendarPopup = ({ }: PopupProps) => { const ref = useRef(null) - useEffect(() => { + useLayoutEffect(() => { document.addEventListener('mousedown', event => handleClickOutside(event, ref, setVisible, refInput), ) diff --git a/packages/ui/src/components/Popup/index.tsx b/packages/ui/src/components/Popup/index.tsx index 6ac6ae50d1..64e2a55d16 100644 --- a/packages/ui/src/components/Popup/index.tsx +++ b/packages/ui/src/components/Popup/index.tsx @@ -17,6 +17,7 @@ import { useEffect, useId, useImperativeHandle, + useLayoutEffect, useMemo, useRef, useState, @@ -64,6 +65,8 @@ const stopClickPropagation: MouseEventHandler = event => { event.nativeEvent.stopImmediatePropagation() } +type PopupRole = 'dialog' | 'tooltip' | 'popup' | string + type PopupProps = { /** * Id is automatically generated if not set. It is used for associating popup wrapper with popup portal. @@ -106,7 +109,7 @@ type PopupProps = { */ visible?: boolean innerRef?: Ref - role?: string + role?: PopupRole 'data-testid'?: string hasArrow?: boolean onClose?: () => void @@ -141,6 +144,38 @@ type PopupProps = { style?: CSSProperties } +const getPopupPortalTarget = ({ + node, + role, + portalTarget, +}: { + node: HTMLElement | null + role: PopupRole + portalTarget?: HTMLElement +}) => { + if (portalTarget) { + return portalTarget + } + + if (role === 'dialog') { + if (node) { + return node + } + if (isClientSide) { + return document.body + } + + return null + } + + // We check if window exists for SSR + if (typeof window !== 'undefined') { + return document.body + } + + return null +} + /** * @experimental This component is experimental and may be subject to breaking changes in the future. */ @@ -182,30 +217,11 @@ export const Popup = forwardRef( useImperativeHandle(ref, () => innerPopupRef.current as HTMLDivElement) const timer = useRef>(undefined) - const popupPortalTarget = useMemo(() => { - if (portalTarget) { - return portalTarget - } - - if (role === 'dialog') { - if (childrenRef.current) { - return childrenRef.current - } - if (isClientSide) { - return document.body - } - - return null - } - - // We check if window exists for SSR - if (typeof window !== 'undefined') { - return document.body - } - - return null - // oxlint-disable react/exhaustive-deps - }, [portalTarget, role, childrenRef.current]) + const popupPortalTarget = getPopupPortalTarget({ + node: childrenRef.current, + portalTarget, + role, + }) // There are some issue when mixing animation and maxHeight on some browsers, so we disable animation if maxHeight is set. const animationDuration = @@ -227,19 +243,19 @@ export const Popup = forwardRef( ) const generatePopupPositions = useCallback(() => { - if (childrenRef.current && innerPopupRef.current) { + if (childrenRef.current && innerPopupRef.current && popupPortalTarget) { setPositions( computePositions({ align, childrenRef, hasArrow, placement, - popupPortalTarget: popupPortalTarget as HTMLElement, + popupPortalTarget, popupRef: innerPopupRef, }), ) } - }, [hasArrow, placement, popupPortalTarget, align, children]) + }, [hasArrow, placement, align, popupPortalTarget]) /** * This function is called when we need to recompute positions of popup due to window scroll or resize. @@ -346,11 +362,33 @@ export const Popup = forwardRef( [closePopup, debounceDelay, visible], ) + useLayoutEffect(() => { + const currentRef = childrenRef.current + const mutationObserver = new MutationObserver(() => { + generatePopupPositions() + }) + const resizeObserver = new ResizeObserver(() => { + generatePopupPositions() + }) + + if (currentRef) { + resizeObserver.observe(currentRef) + mutationObserver.observe(currentRef, { characterData: true }) + } + + return () => { + if (currentRef) { + resizeObserver.unobserve(currentRef) + mutationObserver.disconnect() + } + } + }, [visibleInDom, generatePopupPositions]) + /** * Once popup is visible in the dom we can compute positions, then set it visible on screen and add event to * recompute positions on scroll or screen resize. */ - useEffect(() => { + useLayoutEffect(() => { if (visibleInDom) { generatePopupPositions() @@ -379,12 +417,11 @@ export const Popup = forwardRef( ]) // This will be triggered when positions are computed and popup is visible in the dom. - useEffect(() => { + useLayoutEffect(() => { if (visibleInDom && innerPopupRef.current) { innerPopupRef.current.style.opacity = '1' } - // oxlint-disable react/exhaustive-deps - }, [positions]) + }, [visibleInDom, positions]) /** * If popup has `visible` prop it means the popup is manually controlled through this prop. diff --git a/packages/ui/src/components/SelectInput/components/Dropdown.tsx b/packages/ui/src/components/SelectInput/components/Dropdown.tsx index 76c2a58cc9..458e9ed7a1 100644 --- a/packages/ui/src/components/SelectInput/components/Dropdown.tsx +++ b/packages/ui/src/components/SelectInput/components/Dropdown.tsx @@ -2,17 +2,20 @@ import { useTheme } from '@ultraviolet/themes' import type { + ChangeEvent, ComponentProps, Dispatch, KeyboardEvent, + MouseEvent, ReactNode, RefObject, SetStateAction, } from 'react' import { + use, useCallback, - useContext, useEffect, + useLayoutEffect, useMemo, useRef, useState, @@ -219,13 +222,26 @@ const CreateDropdown = ({ ) } - const handleClick = (clickedOption: OptionType, group?: string) => { + const handleClick = ({ + clickedOption, + group, + event, + }: { + clickedOption: OptionType + group?: string + event: + | MouseEvent + | KeyboardEvent + | ChangeEvent + }) => { + event.stopPropagation() + setSelectedData({ clickedOption, group, type: 'selectOption' }) if (multiselect) { if (selectedData.selectedValues.includes(clickedOption.value)) { onChange?.( selectedData.selectedValues.filter( - val => val !== clickedOption.value, + value => value !== clickedOption.value, ), ) } else { @@ -420,16 +436,25 @@ const CreateDropdown = ({ data-testid={`option-${option.value}`} id={`option-${indexOption}`} key={option.value} - onClick={() => { + onClick={event => { if (!option.disabled) { - handleClick(option, group) + handleClick({ + clickedOption: option, + event, + group, + }) + } + }} + onKeyDown={event => { + const shouldClick = [' ', 'Enter'].includes(event.key) + if (shouldClick) { + handleClick({ + clickedOption: option, + event, + group, + }) } }} - onKeyDown={event => - [' ', 'Enter'].includes(event.key) - ? handleClick(option, group) - : null - } ref={ option.value === defaultSearchValue || option.searchText === defaultSearchValue @@ -447,9 +472,13 @@ const CreateDropdown = ({ } className={dropdownCheckbox} disabled={option.disabled} - onChange={() => { + onChange={event => { if (!option.disabled) { - handleClick(option, group) + handleClick({ + clickedOption: option, + event, + group, + }) } }} tabIndex={-1} @@ -548,14 +577,23 @@ const CreateDropdown = ({ data-testid={`option-${option.value}`} id={`option-${index}`} key={option.value} - onClick={() => { + onClick={event => { if (!option.disabled) { - handleClick(option) + handleClick({ + clickedOption: option, + event, + }) + } + }} + onKeyDown={event => { + const shouldClick = [' ', 'Enter'].includes(event.key) + if (shouldClick) { + handleClick({ + clickedOption: option, + event, + }) } }} - onKeyDown={event => - [' ', 'Enter'].includes(event.key) ? handleClick(option) : null - } ref={ option.value === defaultSearchValue || option.searchText === defaultSearchValue @@ -573,9 +611,12 @@ const CreateDropdown = ({ } className={dropdownCheckbox} disabled={option.disabled} - onChange={() => { + onChange={event => { if (!option.disabled) { - handleClick(option) + handleClick({ + clickedOption: option, + event, + }) } }} tabIndex={-1} @@ -637,9 +678,9 @@ export const Dropdown = ({ const [maxWidth, setWidth] = useState( refSelect.current?.offsetWidth ?? '100%', ) - const modalContext = useContext(ModalContext) + const modalContext = use(ModalContext) - useEffect(() => { + useLayoutEffect(() => { if (refSelect.current && isDropdownVisible) { const position = refSelect.current.getBoundingClientRect().bottom + @@ -647,25 +688,31 @@ export const Dropdown = ({ Number(theme.sizing[INPUT_SIZE_HEIGHT[size]].replace('rem', '')) * 16 + Number.parseInt(theme.space['5'], 10) const overflow = position - window.innerHeight + 32 + if (overflow > 0 && modalContext) { const currentModal = modalContext.openedModals[0] const modalElement = currentModal?.ref.current if (modalElement) { - const parentElement = modalElement.parentNode as HTMLElement - if (parentElement) { + const parentElement = modalElement.parentNode + + if (parentElement instanceof HTMLElement) { parentElement.scrollBy({ behavior: 'smooth', top: overflow, }) + } else { + modalElement.scrollBy({ + behavior: 'smooth', + top: overflow, + }) } } else { window.scrollBy({ behavior: 'smooth', top: overflow }) } } } - // oxlint-disable react/exhaustive-deps - }, [isDropdownVisible, refSelect, size, ref.current]) + }, [isDropdownVisible, refSelect, size, modalContext, theme]) const resizeDropdown = useCallback(() => { if ( @@ -696,33 +743,24 @@ export const Dropdown = ({ setSearch('') } - if (!searchable) { - document.addEventListener('keydown', event => - handleKeyDown( - event, - ref, - options, - searchBarActive, - setSearch, - setDefaultSearch, - search, - ), + const eventKeydown = (event: globalThis.KeyboardEvent) => + handleKeyDown( + event, + ref, + options, + searchBarActive, + setSearch, + setDefaultSearch, + search, ) + + if (!searchable) { + document.addEventListener('keydown', eventKeydown) } return () => { if (!searchable) { - document.removeEventListener('keydown', event => - handleKeyDown( - event, - ref, - options, - searchBarActive, - setSearch, - setDefaultSearch, - search, - ), - ) + document.removeEventListener('keydown', eventKeydown) } } }, [ diff --git a/packages/ui/src/components/SwitchButton/__tests__/index.test.tsx b/packages/ui/src/components/SwitchButton/__tests__/index.test.tsx index bf6448212f..bd76104b1e 100644 --- a/packages/ui/src/components/SwitchButton/__tests__/index.test.tsx +++ b/packages/ui/src/components/SwitchButton/__tests__/index.test.tsx @@ -43,7 +43,8 @@ describe('switchButton', () => { )) test('renders correctly with children changing', () => { - ResizeObserver = vi.fn((cb: ResizeObserverCallback) => { + const tempResizeObserver = window.ResizeObserver + window.ResizeObserver = vi.fn((cb: ResizeObserverCallback) => { resizeCallback = cb return { @@ -72,6 +73,7 @@ describe('switchButton', () => { ) expect(asFragment()).toMatchSnapshot() + window.ResizeObserver = tempResizeObserver }) test('renders with tooltip', () => diff --git a/packages/ui/src/components/TagList/index.tsx b/packages/ui/src/components/TagList/index.tsx index 6ced8a91ca..753dd8baf3 100644 --- a/packages/ui/src/components/TagList/index.tsx +++ b/packages/ui/src/components/TagList/index.tsx @@ -223,7 +223,7 @@ export const TagList = ({ const visibleTagsCopy = visibleTags.filter( (_, index) => index < visibleTags.length - 1, ) - const tagToMove = visibleTags[visibleTags.length - 1] ?? '' + const tagToMove = visibleTags.at(-1) ?? '' setVisibleTags(visibleTagsCopy) setHiddenTags([tagToMove, ...hiddenTags]) diff --git a/packages/ui/src/components/VerificationCode/index.tsx b/packages/ui/src/components/VerificationCode/index.tsx index a03920b2f8..bfa05cb806 100644 --- a/packages/ui/src/components/VerificationCode/index.tsx +++ b/packages/ui/src/components/VerificationCode/index.tsx @@ -133,7 +133,7 @@ export const VerificationCode = ({ const prevIndex = index - 1 const nextIndex = index + 1 const first = inputRefs[0] - const last = inputRefs[inputRefs.length - 1] + const last = inputRefs.at(-1) const prev = inputRefs[prevIndex] const next = inputRefs[nextIndex] const vals = [...values] diff --git a/utils/test/src/vitest/setup.ts b/utils/test/src/vitest/setup.ts index ebb021e697..20fb06e586 100644 --- a/utils/test/src/vitest/setup.ts +++ b/utils/test/src/vitest/setup.ts @@ -21,6 +21,8 @@ export const setup = () => { beforeEach(() => { setupIntersectionMocking(vi.fn()) vi.spyOn(global.Math, 'random').mockReturnValue(0.4155913669444804) + + window.ResizeObserver = vi.fn().mockImplementation(MockResize) }) // oxlint-disable-next-line no-hooks