diff --git a/package-lock.json b/package-lock.json index 71e1c3333..ef100a464 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,7 +24,7 @@ "@adevinta/tailwind-config-viewer": "1.9.0", "@babel/core": "7.23.0", "@commitlint/cli": "17.8.1", - "@commitlint/config-conventional": "17.6.1", + "@commitlint/config-conventional": "17.8.1", "@commitlint/config-lerna-scopes": "17.8.1", "@commitlint/cz-commitlint": "17.8.1", "@commitlint/prompt-cli": "17.8.1", @@ -146,9 +146,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "node_modules/@alloc/quick-lru": { @@ -2446,12 +2446,12 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "17.6.1", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.6.1.tgz", - "integrity": "sha512-ng/ybaSLuTCH9F+7uavSOnEQ9EFMl7lHEjfAEgRh1hwmEe8SpLKpQeMo2aT1IWvHaGMuTb+gjfbzoRf2IR23NQ==", + "version": "17.8.1", + "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-17.8.1.tgz", + "integrity": "sha512-NxCOHx1kgneig3VLauWJcDWS40DVjg7nKOpBEEK9E5fjJpQqLCilcnKkIIjdBH98kEO1q3NpE5NSrZ2kl/QGJg==", "dev": true, "dependencies": { - "conventional-changelog-conventionalcommits": "^5.0.0" + "conventional-changelog-conventionalcommits": "^6.1.0" }, "engines": { "node": ">=v14" @@ -15431,17 +15431,15 @@ } }, "node_modules/conventional-changelog-conventionalcommits": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-5.0.0.tgz", - "integrity": "sha512-lCDbA+ZqVFQGUj7h9QBKoIpLhl8iihkO0nCTyRNzuXtcd7ubODpYB04IFy31JloiJgG0Uovu8ot8oxRzn7Nwtw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-6.1.0.tgz", + "integrity": "sha512-3cS3GEtR78zTfMzk0AizXKKIdN4OvSh7ibNz6/DPbhWWQu7LqE/8+/GqSodV+sywUR2gpJAdP/1JFf4XtN7Zpw==", "dev": true, "dependencies": { - "compare-func": "^2.0.0", - "lodash": "^4.17.15", - "q": "^1.5.1" + "compare-func": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/conventional-changelog-core": { @@ -28351,16 +28349,6 @@ "async-limiter": "~1.0.0" } }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, "node_modules/qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", @@ -30040,13 +30028,13 @@ } }, "node_modules/selenium-webdriver": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.12.0.tgz", - "integrity": "sha512-zvPzmTsky6WfO6+BGMj2mCJsw7qKnfQONur2b+pGn8jeTiC+WAUOthZOnaK+HkX5wiU6L4uoMF+JIcOVstp25A==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.15.0.tgz", + "integrity": "sha512-BNG1bq+KWiBGHcJ/wULi0eKY0yaDqFIbEmtbsYJmfaEghdCkXBsx1akgOorhNwjBipOr0uwpvNXqT6/nzl+zjg==", "dependencies": { "jszip": "^3.10.1", "tmp": "^0.2.1", - "ws": ">=8.13.0" + "ws": ">=8.14.2" }, "engines": { "node": ">= 14.20.0" @@ -31022,16 +31010,17 @@ } }, "node_modules/svgo": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.2.tgz", - "integrity": "sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.0.5.tgz", + "integrity": "sha512-HQKHEo73pMNOlDlBcLgZRcHW2+1wo7bFYayAXkGN0l/2+h68KjlfZyMRhdhaGvoHV2eApOovl12zoFz42sT6rQ==", "dev": true, "dependencies": { "@trysound/sax": "0.2.0", "commander": "^7.2.0", "css-select": "^5.1.0", "css-tree": "^2.2.1", - "csso": "^5.0.5", + "css-what": "^6.1.0", + "csso": "5.0.5", "picocolors": "^1.0.0" }, "bin": { @@ -33863,9 +33852,9 @@ } }, "node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "version": "8.14.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", + "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", "engines": { "node": ">=10.0.0" }, @@ -34107,11 +34096,11 @@ }, "packages/components/alert-dialog": { "name": "@spark-ui/alert-dialog", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.0.1", - "@spark-ui/dialog": "^1.9.1", + "@spark-ui/dialog": "^1.9.2", "@spark-ui/use-merge-refs": "^0.4.0", "class-variance-authority": "0.7.0" }, @@ -34155,14 +34144,14 @@ }, "packages/components/checkbox": { "name": "@spark-ui/checkbox", - "version": "2.1.7", + "version": "2.1.8", "license": "MIT", "dependencies": { "@radix-ui/react-checkbox": "1.0.4", "@radix-ui/react-id": "1.0.1", "@spark-ui/form-field": "^1.4.0", "@spark-ui/icon": "^2.1.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "@spark-ui/internal-utils": "^2.2.0", "@spark-ui/label": "^1.6.0", "@spark-ui/use-merge-refs": "^0.4.0", @@ -34177,11 +34166,11 @@ }, "packages/components/chip": { "name": "@spark-ui/chip", - "version": "1.2.5", + "version": "1.2.6", "license": "MIT", "dependencies": { "@spark-ui/icon": "^2.1.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "@spark-ui/internal-utils": "^2.2.0", "@spark-ui/slot": "^1.7.0", "emulate-tab": "^1.2.1" @@ -34205,13 +34194,13 @@ }, "packages/components/dialog": { "name": "@spark-ui/dialog", - "version": "1.9.1", + "version": "1.9.2", "license": "MIT", "dependencies": { "@radix-ui/react-dialog": "1.0.5", "@spark-ui/icon": "^2.1.0", "@spark-ui/icon-button": "^2.2.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "class-variance-authority": "0.7.0" }, "peerDependencies": { @@ -34237,13 +34226,13 @@ }, "packages/components/drawer": { "name": "@spark-ui/drawer", - "version": "1.2.1", + "version": "1.2.2", "license": "MIT", "dependencies": { "@radix-ui/react-dialog": "1.0.5", "@spark-ui/icon": "^2.1.0", "@spark-ui/icon-button": "^2.2.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "class-variance-authority": "0.7.0" }, "peerDependencies": { @@ -34255,13 +34244,13 @@ }, "packages/components/dropdown": { "name": "@spark-ui/dropdown", - "version": "0.10.0", + "version": "0.11.1", "license": "MIT", "dependencies": { "@radix-ui/react-id": "1.0.1", "@spark-ui/form-field": "^1.4.0", "@spark-ui/icon": "^2.1.0", - "@spark-ui/popover": "^1.2.7", + "@spark-ui/popover": "^1.4.1", "@spark-ui/visually-hidden": "^1.2.0", "class-variance-authority": "0.7.0", "downshift": "^8.2.3" @@ -34320,13 +34309,13 @@ }, "packages/components/icons": { "name": "@spark-ui/icons", - "version": "1.21.3", + "version": "1.21.4", "license": "MIT", "devDependencies": { "change-case": "4.1.2", "minimatch": "7.4.6", "path-that-svg": "1.2.4", - "svgo": "3.0.2", + "svgo": "3.0.5", "svgson": "5.3.1" }, "peerDependencies": { @@ -34359,12 +34348,12 @@ }, "packages/components/input": { "name": "@spark-ui/input", - "version": "1.5.9", + "version": "1.5.10", "license": "MIT", "dependencies": { "@spark-ui/form-field": "^1.4.0", "@spark-ui/icon": "^2.1.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "@spark-ui/slot": "^1.7.0", "@spark-ui/use-combined-state": "^0.6.2", "@spark-ui/use-merge-refs": "^0.4.0", @@ -34441,14 +34430,14 @@ }, "packages/components/popover": { "name": "@spark-ui/popover", - "version": "1.3.1", + "version": "1.4.1", "license": "MIT", "dependencies": { "@radix-ui/react-id": "1.0.1", "@radix-ui/react-popover": "1.0.7", "@spark-ui/icon": "^2.1.0", "@spark-ui/icon-button": "^2.2.0", - "@spark-ui/icons": "^1.21.3" + "@spark-ui/icons": "^1.21.4" }, "devDependencies": { "jsdom-testing-mocks": "1.9.0" @@ -34568,11 +34557,11 @@ }, "packages/components/rating": { "name": "@spark-ui/rating", - "version": "1.0.3", + "version": "1.0.4", "license": "MIT", "devDependencies": { "@spark-ui/icon": "^2.1.0", - "@spark-ui/icons": "^1.21.3" + "@spark-ui/icons": "^1.21.4" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", @@ -34582,11 +34571,11 @@ }, "packages/components/select": { "name": "@spark-ui/select", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { "@spark-ui/icon": "^2.1.0", - "@spark-ui/icons": "^1.21.3" + "@spark-ui/icons": "^1.21.4" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", @@ -34643,13 +34632,13 @@ }, "packages/components/switch": { "name": "@spark-ui/switch", - "version": "2.2.5", + "version": "2.2.6", "license": "MIT", "dependencies": { "@radix-ui/react-id": "1.0.1", "@radix-ui/react-switch": "1.0.3", "@spark-ui/form-field": "^1.4.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "@spark-ui/internal-utils": "^2.2.0", "@spark-ui/label": "^1.6.0", "@spark-ui/slot": "^1.7.0", @@ -34665,13 +34654,13 @@ }, "packages/components/tabs": { "name": "@spark-ui/tabs", - "version": "2.3.5", + "version": "2.3.6", "license": "MIT", "dependencies": { "@radix-ui/react-tabs": "1.0.4", "@spark-ui/button": "^2.4.0", "@spark-ui/icon": "^2.1.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "class-variance-authority": "0.7.0" }, "devDependencies": { @@ -34715,10 +34704,10 @@ }, "packages/components/textarea": { "name": "@spark-ui/textarea", - "version": "1.3.9", + "version": "1.3.10", "license": "MIT", "dependencies": { - "@spark-ui/input": "^1.5.9" + "@spark-ui/input": "^1.5.10" }, "peerDependencies": { "@spark-ui/theme-utils": "^4.0.0", diff --git a/package.json b/package.json index c5c538b31..3177ddc59 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@adevinta/tailwind-config-viewer": "1.9.0", "@babel/core": "7.23.0", "@commitlint/cli": "17.8.1", - "@commitlint/config-conventional": "17.6.1", + "@commitlint/config-conventional": "17.8.1", "@commitlint/config-lerna-scopes": "17.8.1", "@commitlint/cz-commitlint": "17.8.1", "@commitlint/prompt-cli": "17.8.1", diff --git a/packages/components/alert-dialog/CHANGELOG.md b/packages/components/alert-dialog/CHANGELOG.md index dd82abaf0..489673620 100644 --- a/packages/components/alert-dialog/CHANGELOG.md +++ b/packages/components/alert-dialog/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.0.4](https://github.com/adevinta/spark/compare/@spark-ui/alert-dialog@1.0.3...@spark-ui/alert-dialog@1.0.4) (2023-12-04) + +**Note:** Version bump only for package @spark-ui/alert-dialog + ## [1.0.3](https://github.com/adevinta/spark/compare/@spark-ui/alert-dialog@1.0.2...@spark-ui/alert-dialog@1.0.3) (2023-11-22) **Note:** Version bump only for package @spark-ui/alert-dialog diff --git a/packages/components/alert-dialog/package.json b/packages/components/alert-dialog/package.json index 2b28b3f52..373af0b16 100644 --- a/packages/components/alert-dialog/package.json +++ b/packages/components/alert-dialog/package.json @@ -1,6 +1,6 @@ { "name": "@spark-ui/alert-dialog", - "version": "1.0.3", + "version": "1.0.4", "description": "A modal dialog that interrupts the user with important content and expects a response.", "publishConfig": { "access": "public" @@ -24,7 +24,7 @@ }, "dependencies": { "@radix-ui/primitive": "1.0.1", - "@spark-ui/dialog": "^1.9.1", + "@spark-ui/dialog": "^1.9.2", "@spark-ui/use-merge-refs": "^0.4.0", "class-variance-authority": "0.7.0" }, diff --git a/packages/components/checkbox/CHANGELOG.md b/packages/components/checkbox/CHANGELOG.md index cc0920dee..9581dac6a 100644 --- a/packages/components/checkbox/CHANGELOG.md +++ b/packages/components/checkbox/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.1.8](https://github.com/adevinta/spark/compare/@spark-ui/checkbox@2.1.7...@spark-ui/checkbox@2.1.8) (2023-12-04) + +**Note:** Version bump only for package @spark-ui/checkbox + ## [2.1.7](https://github.com/adevinta/spark/compare/@spark-ui/checkbox@2.1.6...@spark-ui/checkbox@2.1.7) (2023-11-22) **Note:** Version bump only for package @spark-ui/checkbox diff --git a/packages/components/checkbox/package.json b/packages/components/checkbox/package.json index c53e0310b..262a5f8ed 100644 --- a/packages/components/checkbox/package.json +++ b/packages/components/checkbox/package.json @@ -1,6 +1,6 @@ { "name": "@spark-ui/checkbox", - "version": "2.1.7", + "version": "2.1.8", "description": "A control that allows the user to toggle between checked and not checked.", "publishConfig": { "access": "public" @@ -27,7 +27,7 @@ "@radix-ui/react-id": "1.0.1", "@spark-ui/form-field": "^1.4.0", "@spark-ui/icon": "^2.1.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "@spark-ui/internal-utils": "^2.2.0", "@spark-ui/label": "^1.6.0", "@spark-ui/use-merge-refs": "^0.4.0", diff --git a/packages/components/chip/CHANGELOG.md b/packages/components/chip/CHANGELOG.md index 7625c65d2..18bec9f8c 100644 --- a/packages/components/chip/CHANGELOG.md +++ b/packages/components/chip/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.6](https://github.com/adevinta/spark/compare/@spark-ui/chip@1.2.5...@spark-ui/chip@1.2.6) (2023-12-04) + +**Note:** Version bump only for package @spark-ui/chip + ## [1.2.5](https://github.com/adevinta/spark/compare/@spark-ui/chip@1.2.4...@spark-ui/chip@1.2.5) (2023-11-22) **Note:** Version bump only for package @spark-ui/chip diff --git a/packages/components/chip/package.json b/packages/components/chip/package.json index 561277f01..78b9272e3 100644 --- a/packages/components/chip/package.json +++ b/packages/components/chip/package.json @@ -1,6 +1,6 @@ { "name": "@spark-ui/chip", - "version": "1.2.5", + "version": "1.2.6", "description": "To help people enter information, make selections, filter content, or trigger actions.", "publishConfig": { "access": "public" @@ -44,7 +44,7 @@ "license": "MIT", "dependencies": { "@spark-ui/icon": "^2.1.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "@spark-ui/internal-utils": "^2.2.0", "@spark-ui/slot": "^1.7.0", "emulate-tab": "^1.2.1" diff --git a/packages/components/dialog/CHANGELOG.md b/packages/components/dialog/CHANGELOG.md index 0e6f0389a..119fcaa57 100644 --- a/packages/components/dialog/CHANGELOG.md +++ b/packages/components/dialog/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.9.2](https://github.com/adevinta/spark/compare/@spark-ui/dialog@1.9.1...@spark-ui/dialog@1.9.2) (2023-12-04) + +**Note:** Version bump only for package @spark-ui/dialog + ## [1.9.1](https://github.com/adevinta/spark/compare/@spark-ui/dialog@1.9.0...@spark-ui/dialog@1.9.1) (2023-11-22) **Note:** Version bump only for package @spark-ui/dialog diff --git a/packages/components/dialog/package.json b/packages/components/dialog/package.json index bbaf0c8c2..7861e8a75 100644 --- a/packages/components/dialog/package.json +++ b/packages/components/dialog/package.json @@ -1,6 +1,6 @@ { "name": "@spark-ui/dialog", - "version": "1.9.1", + "version": "1.9.2", "description": "A window overlaid on either the primary window or another dialog window, rendering the content underneath inert.", "publishConfig": { "access": "public" @@ -46,7 +46,7 @@ "@radix-ui/react-dialog": "1.0.5", "@spark-ui/icon": "^2.1.0", "@spark-ui/icon-button": "^2.2.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "class-variance-authority": "0.7.0" } } diff --git a/packages/components/drawer/CHANGELOG.md b/packages/components/drawer/CHANGELOG.md index 4a160b12c..2e34e8c0f 100644 --- a/packages/components/drawer/CHANGELOG.md +++ b/packages/components/drawer/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.2.2](https://github.com/adevinta/spark/compare/@spark-ui/drawer@1.2.1...@spark-ui/drawer@1.2.2) (2023-12-04) + +**Note:** Version bump only for package @spark-ui/drawer + ## [1.2.1](https://github.com/adevinta/spark/compare/@spark-ui/drawer@1.2.0...@spark-ui/drawer@1.2.1) (2023-11-22) **Note:** Version bump only for package @spark-ui/drawer diff --git a/packages/components/drawer/package.json b/packages/components/drawer/package.json index dea1c2a74..d47d674df 100644 --- a/packages/components/drawer/package.json +++ b/packages/components/drawer/package.json @@ -1,6 +1,6 @@ { "name": "@spark-ui/drawer", - "version": "1.2.1", + "version": "1.2.2", "description": "A panel that slides out from the edge of the screen. It can be useful when you need users to complete a task or view some details without leaving the current page.", "publishConfig": { "access": "public" @@ -46,7 +46,7 @@ "@radix-ui/react-dialog": "1.0.5", "@spark-ui/icon": "^2.1.0", "@spark-ui/icon-button": "^2.2.0", - "@spark-ui/icons": "^1.21.3", + "@spark-ui/icons": "^1.21.4", "class-variance-authority": "0.7.0" } } diff --git a/packages/components/dropdown/CHANGELOG.md b/packages/components/dropdown/CHANGELOG.md index b2b276aca..a0b41fd4c 100644 --- a/packages/components/dropdown/CHANGELOG.md +++ b/packages/components/dropdown/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [0.11.1](https://github.com/adevinta/spark/compare/@spark-ui/dropdown@0.11.0...@spark-ui/dropdown@0.11.1) (2023-12-04) + +**Note:** Version bump only for package @spark-ui/dropdown + +# [0.11.0](https://github.com/adevinta/spark/compare/@spark-ui/dropdown@0.10.0...@spark-ui/dropdown@0.11.0) (2023-12-01) + +### Bug Fixes + +- **dropdown:** fixed itemText not properly linked to item by ariaLabelledby ([56af96c](https://github.com/adevinta/spark/commit/56af96c89e0cbc3b065cf96f0f6b6ee4d9cb8d01)) + +### Features + +- **dropdown:** multiple selection dropdown ([fe6fa7f](https://github.com/adevinta/spark/commit/fe6fa7fa9ef37a07fe0133de3081b164edc6ae5e)) + # [0.10.0](https://github.com/adevinta/spark/compare/@spark-ui/dropdown@0.9.0...@spark-ui/dropdown@0.10.0) (2023-12-01) ### Features diff --git a/packages/components/dropdown/package.json b/packages/components/dropdown/package.json index af075a857..2b10cf4ae 100644 --- a/packages/components/dropdown/package.json +++ b/packages/components/dropdown/package.json @@ -1,6 +1,6 @@ { "name": "@spark-ui/dropdown", - "version": "0.10.0", + "version": "0.11.1", "description": "Displays a list of options for the user to pick from—triggered by a button. Differs from Select in that it offers multiple select and its list is not native.", "publishConfig": { "access": "public" @@ -31,7 +31,7 @@ "@radix-ui/react-id": "1.0.1", "@spark-ui/form-field": "^1.4.0", "@spark-ui/icon": "^2.1.0", - "@spark-ui/popover": "^1.2.7", + "@spark-ui/popover": "^1.4.1", "@spark-ui/visually-hidden": "^1.2.0", "class-variance-authority": "0.7.0", "downshift": "^8.2.3" diff --git a/packages/components/dropdown/src/Dropdown.doc.mdx b/packages/components/dropdown/src/Dropdown.doc.mdx index c62def7ce..684a39839 100644 --- a/packages/components/dropdown/src/Dropdown.doc.mdx +++ b/packages/components/dropdown/src/Dropdown.doc.mdx @@ -87,10 +87,6 @@ import { Dropdown } from '@spark-ui/dropdown' -### Custom item - - - ### Disabled TODO @@ -107,12 +103,16 @@ TODO TODO -## Trigger leading icon +### Trigger leading icon TODO ### Multiple selection + + +### Multiple selection (controlled) + TODO ### Read only @@ -121,6 +121,10 @@ TODO ## Advanced usage +### Custom item + + + ### With form field label diff --git a/packages/components/dropdown/src/Dropdown.stories.tsx b/packages/components/dropdown/src/Dropdown.stories.tsx index 5bb5f1f4b..5f3c0263c 100644 --- a/packages/components/dropdown/src/Dropdown.stories.tsx +++ b/packages/components/dropdown/src/Dropdown.stories.tsx @@ -16,7 +16,7 @@ export default meta export const Default: StoryFn = _args => { return ( -
+
@@ -44,7 +44,7 @@ export const Controlled: StoryFn = () => { const [value, setValue] = useState('book-1') return ( -
+
@@ -66,7 +66,7 @@ export const Controlled: StoryFn = () => { } export const ControlledOpenState: StoryFn = () => { - const [open, setOpen] = useState(false) + const [open, setOpen] = useState(true) return (
@@ -79,7 +79,7 @@ export const ControlledOpenState: StoryFn = () => {
-
+
@@ -103,7 +103,7 @@ export const ControlledOpenState: StoryFn = () => { export const CustomItem: StoryFn = _args => { return ( -
+
@@ -144,7 +144,7 @@ export const CustomItem: StoryFn = _args => { export const DisabledItem: StoryFn = _args => { return ( -
+
@@ -169,7 +169,7 @@ export const DisabledItem: StoryFn = _args => { export const Grouped: StoryFn = _args => { return ( -
+
@@ -200,7 +200,7 @@ export const Grouped: StoryFn = _args => { export const FormFieldLabel: StoryFn = _args => { return ( -
+
Book @@ -222,3 +222,26 @@ export const FormFieldLabel: StoryFn = _args => {
) } + +export const MultipleSelection: StoryFn = _args => { + return ( +
+ + + + + + + + To Kill a Mockingbird + War and Peace + The Idiot + A Picture of Dorian Gray + 1984 + Pride and Prejudice + + + +
+ ) +} diff --git a/packages/components/dropdown/src/Dropdown.test.tsx b/packages/components/dropdown/src/Dropdown.test.tsx index 650c71e6e..8ac3f46d1 100644 --- a/packages/components/dropdown/src/Dropdown.test.tsx +++ b/packages/components/dropdown/src/Dropdown.test.tsx @@ -1,31 +1,454 @@ import { render, screen } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { useState } from 'react' import { describe, expect, it } from 'vitest' import { Dropdown } from '.' +const getTrigger = (accessibleName: string) => { + return screen.getByRole('combobox', { name: accessibleName }) +} + +const getListbox = (accessibleName: string) => { + return screen.getByRole('listbox', { name: accessibleName }) +} + +const getItemsGroup = (accessibleName: string) => { + return screen.getByRole('group', { name: accessibleName }) +} + +const getItem = (accessibleName: string) => { + return screen.getByRole('option', { name: accessibleName }) +} + describe('Dropdown', () => { - describe('initial rendering', () => { - it('should render trigger and list of options', () => { + it('should render trigger and list of items', () => { + render( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + expect(getTrigger('Book')).toBeInTheDocument() + + expect(getListbox('Book')).toBeInTheDocument() + + expect(getItem('War and Peace')).toBeInTheDocument() + expect(getItem('1984')).toBeInTheDocument() + expect(getItem('Pride and Prejudice')).toBeInTheDocument() + }) + + describe('Popover behaviour', () => { + it('should open/close the popover when interacting with its trigger', async () => { + const user = userEvent.setup() + + // Given a close dropdown (default state) render( - - War and Peace - 1984 - Pride and Prejudice - + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + const trigger = getTrigger('Book') + + expect(trigger).toHaveAttribute('aria-expanded', 'false') + + // When the user interact with the trigger + await user.click(trigger) + + // Then the dropdown has expanded + expect(trigger).toHaveAttribute('aria-expanded', 'true') + + // When the user interact with the trigger while expanded + await user.click(trigger) + + // Then the dropdown is closed again + expect(trigger).toHaveAttribute('aria-expanded', 'false') + }) + + it('should remain forced opened', async () => { + const user = userEvent.setup() + + // Given a dropdown that should remain opened + render( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + expect(getTrigger('Book')).toHaveAttribute('aria-expanded', 'true') + + // When the user interacts with the trigger + await user.click(getTrigger('Book')) + + // Then the dropdown remains opened + expect(getTrigger('Book')).toHaveAttribute('aria-expanded', 'true') + }) + + it('should be opened by default but close upon interaction', async () => { + const user = userEvent.setup() + + // Given a dropdown that should remain opened + render( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + expect(getTrigger('Book')).toHaveAttribute('aria-expanded', 'true') + + // When the user interacts with the trigger + await user.click(getTrigger('Book')) + + // Then the dropdown remains opened + expect(getTrigger('Book')).toHaveAttribute('aria-expanded', 'false') + }) + }) + + describe('Dropdown.Group', () => { + it('should link items groups with their label', () => { + // Given a dropdown with items groups and group labels + render( + + + + + + + + Best-sellers + War and Peace + 1984 + + + + Novelties + Pride and Prejudice + Pride and Prejudice + + + + + ) + + // Then each group have an accessible label + expect(getItemsGroup('Best-sellers')).toBeInTheDocument() + expect(getItemsGroup('Novelties')).toBeInTheDocument() + }) + }) + + describe('Dropdown.Value', () => { + it('should display custom value after selection', async () => { + const user = userEvent.setup() + + // Given a dropdown with no selected value yet + render( + + + You have selected a book + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + // Then placeholder should be displayed + expect(getTrigger('Book')).toHaveTextContent('Pick a book') + + // When the user select an item + await user.click(getTrigger('Book')) + await user.click(getItem('Pride and Prejudice')) + + // Then placeholder is replaced by a custom value + expect(getTrigger('Book')).toHaveTextContent('You have selected a book') + }) + + it('should display text in trigger when selecting an item with custom markup', async () => { + const user = userEvent.setup() + + // Given a dropdown with no selected value yet and custom items markup + render( + + + + + + + + New: + War and Peace + + + New: + 1984 + + + New: + Pride and Prejudice + + + + + ) + + // Then placeholder should be displayed + expect(getTrigger('Book')).toHaveTextContent('Pick a book') + + // When the user select an item + await user.click(getTrigger('Book')) + await user.click(getItem('Pride and Prejudice')) + + // Then placeholder is replaced by the raw text value + expect(getTrigger('Book')).toHaveTextContent('Pride and Prejudice') + }) + }) + + describe('single selection', () => { + it('should select item', async () => { + const user = userEvent.setup() + + // Given a dropdown with no selected value yet + render( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + // Then placeholder should be displayed + expect(getTrigger('Book')).toHaveTextContent('Pick a book') + + // When the user select an item + await user.click(getTrigger('Book')) + await user.click(getItem('Pride and Prejudice')) + + // Then placeholder is replaced by the selected value + expect(getTrigger('Book')).toHaveTextContent('Pride and Prejudice') + + // Then the proper item is selected + expect(getItem('War and Peace')).toHaveAttribute('aria-selected', 'false') + expect(getItem('1984')).toHaveAttribute('aria-selected', 'false') + expect(getItem('Pride and Prejudice')).toHaveAttribute('aria-selected', 'true') + }) + + it('should render default selected option', () => { + // Given a dropdown with a default selected value + render( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + ) - expect(screen.getByRole('combobox', { name: 'Book' })).toBeInTheDocument() + // Then the corresponding item is selected + expect(getItem('1984')).toHaveAttribute('aria-selected', 'true') + }) + + it('should control value', async () => { + const user = userEvent.setup() - expect(screen.getByRole('listbox', { name: 'Book' })).toBeInTheDocument() + // Given we control value by outside state and selected value + const ControlledImplementation = () => { + const [value, setValue] = useState('book-1') - expect(screen.getByRole('option', { name: 'War and Peace' })).toBeInTheDocument() - expect(screen.getByRole('option', { name: '1984' })).toBeInTheDocument() - expect(screen.getByRole('option', { name: 'Pride and Prejudice' })).toBeInTheDocument() + return ( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + } + + render() + + expect(getItem('War and Peace')).toHaveAttribute('aria-selected', 'true') + + expect(getTrigger('Book')).toHaveTextContent('War and Peace') + + // when the user select another item + await user.click(getTrigger('Book')) + await user.click(getItem('Pride and Prejudice')) + + // Then the selected value has been updated + expect(getTrigger('Book')).toHaveTextContent('Pride and Prejudice') }) }) + + describe('multiple selection', () => { + it('should select items', async () => { + const user = userEvent.setup() + + // Given a dropdown with no selected value yet + render( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + // Then placeholder should be displayed + expect(getTrigger('Book')).toHaveTextContent('Pick a book') + + // When the user select two items + await user.click(getTrigger('Book')) + await user.click(getItem('1984')) + await user.click(getItem('Pride and Prejudice')) + + // Then placeholder is replaced by the selected value + expect(getTrigger('Book')).toHaveTextContent('1984') + + // Then the proper items are selected + expect(getItem('War and Peace')).toHaveAttribute('aria-selected', 'false') + expect(getItem('1984')).toHaveAttribute('aria-selected', 'true') + expect(getItem('Pride and Prejudice')).toHaveAttribute('aria-selected', 'true') + }) + + it('should select all items using keyboard navigation', async () => { + const user = userEvent.setup() + + // Given a dropdown with no selected value yet + render( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + // Then placeholder should be displayed + expect(getTrigger('Book')).toHaveTextContent('Pick a book') + + // When the user select all the items one by one using the keyboard + await user.click(getTrigger('Book')) + await user.keyboard('[ArrowDown][Enter]') + await user.keyboard('[ArrowDown][Enter]') + await user.keyboard('[ArrowDown][Enter]') + + // Then all items are selected + expect(getItem('War and Peace')).toHaveAttribute('aria-selected', 'true') + expect(getItem('1984')).toHaveAttribute('aria-selected', 'true') + expect(getItem('Pride and Prejudice')).toHaveAttribute('aria-selected', 'true') + }) + + it('should be able to unselect a selected item', async () => { + const user = userEvent.setup() + + // Given a dropdown with no selected value yet + render( + + + + + + + War and Peace + 1984 + Pride and Prejudice + + + + ) + + // Then placeholder should be displayed + expect(getTrigger('Book')).toHaveTextContent('Pick a book') + + // When the user select an item + await user.click(getTrigger('Book')) + await user.click(getItem('1984')) + + // Then placeholder is replaced by the selected value + expect(getTrigger('Book')).toHaveTextContent('1984') + expect(getItem('1984')).toHaveAttribute('aria-selected', 'true') + + // When the user unselect that item + await user.click(getItem('1984')) + + // Then placeholder is shown again as the item is no longer selected + expect(getTrigger('Book')).toHaveTextContent('Pick a book') + expect(getItem('1984')).toHaveAttribute('aria-selected', 'false') + }) + + // it('should render default selected items', () => {}) + // it('should control value', async () => {}) + }) }) diff --git a/packages/components/dropdown/src/DropdownContext.tsx b/packages/components/dropdown/src/DropdownContext.tsx index 02ca851c8..2e5a3c4d3 100644 --- a/packages/components/dropdown/src/DropdownContext.tsx +++ b/packages/components/dropdown/src/DropdownContext.tsx @@ -1,7 +1,7 @@ import { useId } from '@radix-ui/react-id' import { useFormFieldControl } from '@spark-ui/form-field' import { Popover } from '@spark-ui/popover' -import { useSelect } from 'downshift' +import { useMultipleSelection, useSelect, UseSelectState } from 'downshift' import { createContext, Dispatch, @@ -20,6 +20,7 @@ export interface DropdownContextState extends DownshiftState { highlightedItem: DropdownItem | undefined hasPopover: boolean setHasPopover: Dispatch> + multiple: boolean } export type DropdownContextProps = PropsWithChildren<{ @@ -47,6 +48,7 @@ export type DropdownContextProps = PropsWithChildren<{ * The open state of the select when it is initially rendered. Use when you do not need to control its open state. */ defaultOpen?: boolean + multiple?: boolean }> const DropdownContext = createContext(null) @@ -59,6 +61,7 @@ export const DropdownProvider = ({ open, onOpenChange, defaultOpen, + multiple = false, }: DropdownContextProps) => { const [computedItems, setComputedItems] = useState(getItemsFromChildren(children)) const [hasPopover, setHasPopover] = useState(false) @@ -70,7 +73,11 @@ export const DropdownProvider = ({ const controlledSelectedItem = value ? computedItems.get(value) : undefined const controlledDefaultSelectedItem = defaultValue ? computedItems.get(defaultValue) : undefined - const controlledDefaultOpen = defaultOpen != null ? defaultOpen : false + const controlledDefaultOpen = defaultOpen ?? false + + const downshiftMultipleSelection = useMultipleSelection({ + // initialSelectedItems: [controlledDefaultSelectedItem as DropdownItem], + }) const downshift = useSelect({ items: Array.from(computedItems.values()), @@ -80,7 +87,7 @@ export const DropdownProvider = ({ id, labelId, // Controlled mode (stateful) - selectedItem: controlledSelectedItem, + selectedItem: controlledSelectedItem, // todo: set to null for multiple selection initialSelectedItem: controlledDefaultSelectedItem, onSelectedItemChange: ({ selectedItem }) => { if (selectedItem?.value) { @@ -94,6 +101,34 @@ export const DropdownProvider = ({ } }, initialIsOpen: controlledDefaultOpen, + ...(multiple && { + stateReducer: (state: UseSelectState, { changes, type }) => { + switch (type) { + case useSelect.stateChangeTypes.ToggleButtonKeyDownEnter: + case useSelect.stateChangeTypes.ToggleButtonKeyDownSpaceButton: + case useSelect.stateChangeTypes.ItemClick: + if (changes.selectedItem != null) { + const isAlreadySelected = downshiftMultipleSelection.selectedItems.some( + selectedItem => selectedItem.value === changes.selectedItem?.value + ) + + if (isAlreadySelected) { + downshiftMultipleSelection.removeSelectedItem(changes.selectedItem) + } else { + downshiftMultipleSelection.addSelectedItem(changes.selectedItem) + } + } + + return { + ...changes, + isOpen: true, // keep the menu open after selection. + highlightedIndex: state.highlightedIndex, // preserve highlighted index position + } + default: + return changes + } + }, + }), }) /** @@ -123,7 +158,9 @@ export const DropdownProvider = ({ return ( { - const { computedItems, selectedItem, getItemProps, highlightedItem } = useDropdownContext() +export const Item = ({ children, ...props }: ItemProps) => { + return ( + + {children} + + ) +} + +const ItemContent = ({ className, disabled = false, value, children }: ItemProps) => { + const { multiple, computedItems, selectedItem, selectedItems, getItemProps, highlightedItem } = + useDropdownContext() + + const { textId } = useDropdownItemContext() const index = getIndexByKey(computedItems, value) const itemData: DropdownItem = { disabled, value, text: getItemText(children) } + const isSelected = multiple + ? selectedItems.some(selectedItem => selectedItem.value === value) + : selectedItem?.value === value + return (
  • {children}
  • diff --git a/packages/components/dropdown/src/DropdownItemContext.tsx b/packages/components/dropdown/src/DropdownItemContext.tsx new file mode 100644 index 000000000..8ca45ccab --- /dev/null +++ b/packages/components/dropdown/src/DropdownItemContext.tsx @@ -0,0 +1,30 @@ +import React, { createContext, type PropsWithChildren, useContext, useState } from 'react' + +type ItemTextId = string | undefined + +interface DropdownItemContextState { + textId: ItemTextId + setTextId: React.Dispatch> +} + +const DropdownItemContext = createContext(null) + +export const DropdownItemProvider = ({ children }: PropsWithChildren) => { + const [textId, setTextId] = useState(undefined) + + return ( + + {children} + + ) +} + +export const useDropdownItemContext = () => { + const context = useContext(DropdownItemContext) + + if (!context) { + throw Error('useDropdownItemContext must be used within a DropdownItem provider') + } + + return context +} diff --git a/packages/components/dropdown/src/DropdownItemText.tsx b/packages/components/dropdown/src/DropdownItemText.tsx index 66a874b21..94324394f 100644 --- a/packages/components/dropdown/src/DropdownItemText.tsx +++ b/packages/components/dropdown/src/DropdownItemText.tsx @@ -1,11 +1,29 @@ +import { useId } from '@radix-ui/react-id' import { cx } from 'class-variance-authority' +import { useEffect } from 'react' + +import { useDropdownItemContext } from './DropdownItemContext' export interface ItemTextProps { children: string } export const ItemText = ({ children }: ItemTextProps) => { - return {children} + const id = useId() + + const { setTextId } = useDropdownItemContext() + + useEffect(() => { + setTextId(id) + + return () => setTextId(undefined) + }) + + return ( + + {children} + + ) } ItemText.id = 'ItemText' diff --git a/packages/components/dropdown/src/DropdownTrigger.tsx b/packages/components/dropdown/src/DropdownTrigger.tsx index 34f385bc9..eac47b146 100644 --- a/packages/components/dropdown/src/DropdownTrigger.tsx +++ b/packages/components/dropdown/src/DropdownTrigger.tsx @@ -13,7 +13,8 @@ interface TriggerProps { } export const Trigger = ({ 'aria-label': ariaLabel, children, className }: TriggerProps) => { - const { isOpen, getToggleButtonProps, getLabelProps, hasPopover } = useDropdownContext() + const { isOpen, getToggleButtonProps, getDropdownProps, getLabelProps, hasPopover } = + useDropdownContext() const [WrapperComponent, wrapperProps] = hasPopover ? [Popover.Trigger, { asChild: true }] @@ -34,10 +35,10 @@ export const Trigger = ({ 'aria-label': ariaLabel, children, className }: Trigge