From 0529a5814ed5f7d7263fe0ce665da878bc4b5aa0 Mon Sep 17 00:00:00 2001 From: Adam Dierkens Date: Mon, 26 Jan 2026 16:30:26 -0500 Subject: [PATCH 1/3] Start experimenting w/ adding a reforge pkg --- eslint.config.mjs | 8 + package-lock.json | 850 +++++++++++++++++- package.json | 4 +- packages/react/package.json | 6 +- packages/reforge/.storybook/main.ts | 28 + packages/reforge/.storybook/preview.tsx | 58 ++ packages/reforge/.storybook/styles.css | 95 ++ packages/reforge/package.json | 54 ++ .../src/components/Button/Button.stories.tsx | 167 ++++ .../reforge/src/components/Button/Button.tsx | 189 ++++ .../reforge/src/components/Button/index.tsx | 10 + .../IconButton/IconButton.stories.tsx | 291 ++++++ .../src/components/IconButton/IconButton.tsx | 147 +++ .../src/components/IconButton/index.ts | 3 + .../components/Tooltip/Tooltip.stories.tsx | 144 +++ .../src/components/Tooltip/Tooltip.tsx | 62 ++ .../reforge/src/components/Tooltip/index.ts | 3 + packages/reforge/src/index.ts | 1 + packages/reforge/tsconfig.build.json | 31 + packages/reforge/tsconfig.json | 18 + packages/reforge/vitest.config.browser.mts | 64 ++ packages/styled-react/package.json | 2 +- 22 files changed, 2193 insertions(+), 42 deletions(-) create mode 100644 packages/reforge/.storybook/main.ts create mode 100644 packages/reforge/.storybook/preview.tsx create mode 100644 packages/reforge/.storybook/styles.css create mode 100644 packages/reforge/package.json create mode 100644 packages/reforge/src/components/Button/Button.stories.tsx create mode 100644 packages/reforge/src/components/Button/Button.tsx create mode 100644 packages/reforge/src/components/Button/index.tsx create mode 100644 packages/reforge/src/components/IconButton/IconButton.stories.tsx create mode 100644 packages/reforge/src/components/IconButton/IconButton.tsx create mode 100644 packages/reforge/src/components/IconButton/index.ts create mode 100644 packages/reforge/src/components/Tooltip/Tooltip.stories.tsx create mode 100644 packages/reforge/src/components/Tooltip/Tooltip.tsx create mode 100644 packages/reforge/src/components/Tooltip/index.ts create mode 100644 packages/reforge/src/index.ts create mode 100644 packages/reforge/tsconfig.build.json create mode 100644 packages/reforge/tsconfig.json create mode 100644 packages/reforge/vitest.config.browser.mts diff --git a/eslint.config.mjs b/eslint.config.mjs index bc5b452bd94..381e31cc8a2 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -392,6 +392,14 @@ const config = defineConfig([ 'primer-react/direct-slot-children': 'off', }, }, + + { + files: ['packages/reforge/src/**/*.tsx'], + rules: { + '@typescript-eslint/no-namespace': 'off', + 'import/export': 'warn', + }, + }, ]) export default tseslint.config(config) diff --git a/package-lock.json b/package-lock.json index 083c22b70b4..b41e57a66ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,7 +59,7 @@ "prettier": "3.4.2", "rimraf": "5.0.5", "size-limit": "11.2.0", - "storybook": "^10.1.11", + "storybook": "^10.2.0", "stylelint": "16.20.0", "turbo": "^2.6.3", "typescript": "^5.9.2", @@ -2145,6 +2145,60 @@ "node": ">=6.9.0" } }, + "node_modules/@base-ui/react": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@base-ui/react/-/react-1.1.0.tgz", + "integrity": "sha512-ikcJRNj1mOiF2HZ5jQHrXoVoHcNHdBU5ejJljcBl+VTLoYXR6FidjTN86GjO6hyshi6TZFuNvv0dEOgaOFv6Lw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@base-ui/utils": "0.2.4", + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", + "reselect": "^5.1.1", + "tabbable": "^6.4.0", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@base-ui/utils": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@base-ui/utils/-/utils-0.2.4.tgz", + "integrity": "sha512-smZwpMhjO29v+jrZusBSc5T+IJ3vBb9cjIiBjtKcvWmRj9Z4DWGVR3efr1eHR56/bqY5a4qyY9ElkOY5ljo3ng==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@floating-ui/utils": "^0.2.10", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "@types/react": "^17 || ^18 || ^19", + "react": "^17 || ^18 || ^19", + "react-dom": "^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@changesets/apply-release-plan": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@changesets/apply-release-plan/-/apply-release-plan-7.0.12.tgz", @@ -4659,7 +4713,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" @@ -4669,7 +4722,6 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.3", @@ -4680,7 +4732,6 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.7.4" @@ -4694,7 +4745,6 @@ "version": "0.2.10", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "dev": true, "license": "MIT" }, "node_modules/@github-ui/storybook-addon-performance-panel": { @@ -6447,15 +6497,19 @@ } }, "node_modules/@primer/primitives": { - "version": "11.3.1", - "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-11.3.1.tgz", - "integrity": "sha512-f5FXeUHQ0iyxt1UF6MqeajQ9jRV/7uOGLECU5KuFWhMioCZOho09jS6alZA9tiwksHr2LYXfo7AdFczpEz3Npw==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@primer/primitives/-/primitives-11.3.2.tgz", + "integrity": "sha512-/8EDh3MmF9cbmrLETFmIuNFIdvpSCkvBlx6zzD8AZ4dZ5UYExQzFj8QAtIrRtCFJ2ZmW5QrtrPR3+JVb8KEDpg==", "license": "MIT" }, "node_modules/@primer/react": { "resolved": "packages/react", "link": true }, + "node_modules/@primer/reforge": { + "resolved": "packages/reforge", + "link": true + }, "node_modules/@primer/styled-react": { "resolved": "packages/styled-react", "link": true @@ -6485,6 +6539,15 @@ "@primer/primitives": "10.x || 11.x" } }, + "node_modules/@primer/tailwind-config": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@primer/tailwind-config/-/tailwind-config-0.1.0.tgz", + "integrity": "sha512-t6d+PwlOoqkXWDQcLGy1EkrICr8ndpf3tssd5/9/oXRd74WhgmKnaOSSN+MeMGn67u+jZ5xnpOJz/HEfi+015g==", + "dev": true, + "peerDependencies": { + "tailwindcss": "^4.1.17" + } + }, "node_modules/@publint/pack": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@publint/pack/-/pack-0.1.2.tgz", @@ -7954,6 +8017,17 @@ "storybook": "^10.1.11" } }, + "node_modules/@storybook/addon-actions": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-9.0.8.tgz", + "integrity": "sha512-LFePu7PPnWN0Il/uoUpmA5T0J0C7d6haJIbg0pXrjxW2MQVSYXE4S4LSUz8fOImltBDV3xAl6tLPYHFj6VcrOA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, "node_modules/@storybook/addon-docs": { "version": "10.1.11", "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-10.1.11.tgz", @@ -8019,13 +8093,13 @@ } }, "node_modules/@storybook/builder-vite": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.1.11.tgz", - "integrity": "sha512-MMD09Ap7FyzDfWG961pkIMv/w684XXe1bBEi+wCEpHxvrgAd3j3A9w/Rqp9Am2uRDPCEdi1QgSzS3SGW3aGThQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@storybook/builder-vite/-/builder-vite-10.2.0.tgz", + "integrity": "sha512-S1+62ipGmQzGPZfcbgNqpbrCezsqkvbhj+MBbQ6VS46b2HcPjm4H8V6FzGly0Ja2pSgu8gT1BQ5N+3yOG8UNTw==", "dev": true, "license": "MIT", "dependencies": { - "@storybook/csf-plugin": "10.1.11", + "@storybook/csf-plugin": "10.2.0", "@vitest/mocker": "3.2.4", "ts-dedent": "^2.0.0" }, @@ -8034,10 +8108,45 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^10.1.11", + "storybook": "^10.2.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@storybook/builder-vite/node_modules/@storybook/csf-plugin": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.2.0.tgz", + "integrity": "sha512-Cty+tZ0r1AZhwBBzqI4RyCpMVGt9wHGTtG4YCRUuNgVFO1MnjaFBHKRT+oT7md28+BWYjFz4Qtpge/fcWANJ0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "unplugin": "^2.3.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "esbuild": "*", + "rollup": "*", + "storybook": "^10.2.0", + "vite": "*", + "webpack": "*" + }, + "peerDependenciesMeta": { + "esbuild": { + "optional": true + }, + "rollup": { + "optional": true + }, + "vite": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, "node_modules/@storybook/csf-plugin": { "version": "10.1.11", "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-10.1.11.tgz", @@ -8090,14 +8199,14 @@ } }, "node_modules/@storybook/react": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.1.11.tgz", - "integrity": "sha512-rmMGmEwBaM2YpB8oDk2moM0MNjNMqtwyoPPZxjyruY9WVhYca8EDPGKEdRzUlb4qZJsTgLi7VU4eqg6LD/mL3Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-10.2.0.tgz", + "integrity": "sha512-ciJlh1UGm0GBXQgqrYFeLmiix+KGFB3v37OnAYjGghPS9OP6S99XyshxY/6p0sMOYtS+eWS2gPsOKNXNnLDGYw==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "10.1.11", + "@storybook/react-dom-shim": "10.2.0", "react-docgen": "^8.0.2" }, "funding": { @@ -8107,7 +8216,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.1.11", + "storybook": "^10.2.0", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -8133,16 +8242,16 @@ } }, "node_modules/@storybook/react-vite": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.1.11.tgz", - "integrity": "sha512-qh1BCD25nIoiDfqwha+qBkl7pcG4WuzM+c8tsE63YEm8AFIbNKg5K8lVUoclF+4CpFz7IwBpWe61YUTDfp+91w==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@storybook/react-vite/-/react-vite-10.2.0.tgz", + "integrity": "sha512-tIXRfrA+wREFuA+bIJccMCV1YVFdACENcSnSlnB5Be3m8ynMHukOz6ObX9jI5WsWZnqrk0/eHyiYJyVhpY9rhQ==", "dev": true, "license": "MIT", "dependencies": { "@joshwooding/vite-plugin-react-docgen-typescript": "^0.6.3", "@rollup/pluginutils": "^5.0.2", - "@storybook/builder-vite": "10.1.11", - "@storybook/react": "10.1.11", + "@storybook/builder-vite": "10.2.0", + "@storybook/react": "10.2.0", "empathic": "^2.0.0", "magic-string": "^0.30.0", "react-docgen": "^8.0.0", @@ -8156,7 +8265,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "storybook": "^10.1.11", + "storybook": "^10.2.0", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, @@ -8175,6 +8284,22 @@ "node": ">=6" } }, + "node_modules/@storybook/react/node_modules/@storybook/react-dom-shim": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-10.2.0.tgz", + "integrity": "sha512-PEQofiruE6dBGzUQPXZZREbuh1t62uRBWoUPRFNAZi79zddlk7+b9qu08VV9cvf68mwOqqT1+VJ1P+3ClD2ZVw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "storybook": "^10.2.0" + } + }, "node_modules/@styled-system/background": { "version": "5.1.2", "license": "MIT", @@ -8289,6 +8414,278 @@ "tslib": "^2.8.0" } }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.13.12", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", @@ -12151,6 +12548,26 @@ "version": "3.1.3", "license": "MIT" }, + "node_modules/cva": { + "version": "1.0.0-beta.4", + "resolved": "https://registry.npmjs.org/cva/-/cva-1.0.0-beta.4.tgz", + "integrity": "sha512-F/JS9hScapq4DBVQXcK85l9U91M6ePeXoBMSp7vypzShoefUBxjQTo3g3935PUHgQd+IW77DjbPRIxugy4/GCQ==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + }, + "peerDependencies": { + "typescript": ">= 4.5.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "dev": true, @@ -12421,8 +12838,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, "license": "Apache-2.0", - "optional": true, "engines": { "node": ">=8" } @@ -12576,7 +12993,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.17.1", + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -16408,7 +16827,9 @@ } }, "node_modules/jiti": { - "version": "2.4.2", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "dev": true, "license": "MIT", "bin": { @@ -16711,6 +17132,267 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lilconfig": { "version": "2.1.0", "dev": true, @@ -21603,6 +22285,12 @@ "dev": true, "license": "MIT" }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/reserved-words": { "version": "0.1.2", "dev": true, @@ -22727,14 +23415,14 @@ "license": "MIT" }, "node_modules/storybook": { - "version": "10.1.11", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.1.11.tgz", - "integrity": "sha512-pKP5jXJYM4OjvNklGuHKO53wOCAwfx79KvZyOWHoi9zXUH5WVMFUe/ZfWyxXG/GTcj0maRgHGUjq/0I43r0dDQ==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-10.2.0.tgz", + "integrity": "sha512-fIQnFtpksRRgHR1CO1onGX3djaog4qsW/c5U8arqYTkUEr2TaWpn05mIJDOBoPJFlOdqFrB4Ttv0PZJxV7avhw==", "dev": true, "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/icons": "^2.0.0", + "@storybook/icons": "^2.0.1", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", @@ -22742,7 +23430,7 @@ "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "open": "^10.2.0", "recast": "^0.23.5", - "semver": "^7.6.2", + "semver": "^7.7.3", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, @@ -22762,6 +23450,19 @@ } } }, + "node_modules/storybook/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/streamx": { "version": "2.22.1", "dev": true, @@ -23632,6 +24333,12 @@ "url": "https://opencollective.com/synckit" } }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, "node_modules/table": { "version": "6.9.0", "dev": true, @@ -23658,6 +24365,13 @@ "url": "https://github.com/sponsors/dcastil" } }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, "node_modules/tapable": { "version": "2.2.1", "dev": true, @@ -25230,7 +25944,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "dev": true, "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -26474,7 +27187,7 @@ "@storybook/addon-docs": "^10.1.11", "@storybook/addon-links": "^10.1.11", "@storybook/icons": "^2.0.1", - "@storybook/react-vite": "^10.1.11", + "@storybook/react-vite": "^10.2.0", "@tanstack/react-virtual": "^3.13.12", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.4.5", @@ -26529,7 +27242,7 @@ "rimraf": "5.0.5", "rollup": "4.54.0", "rollup-plugin-import-css": "^0.0.0", - "storybook": "^10.1.11", + "storybook": "^10.2.0", "terser": "5.36.0", "ts-toolbelt": "9.6.0", "tsx": "4.20.3", @@ -26689,6 +27402,71 @@ "typescript": ">=4.0.0" } }, + "packages/reforge": { + "name": "@primer/reforge", + "version": "0.0.0", + "license": "MIT", + "dependencies": { + "@base-ui/react": "1.1.0", + "@primer/octicons-react": "^19.21.0", + "cva": "1.0.0-beta.4" + }, + "devDependencies": { + "@primer/primitives": "^11.3.2", + "@primer/tailwind-config": "^0.1.0", + "@storybook/addon-actions": "^9.0.8", + "@storybook/react-vite": "^10.2.0", + "@tailwindcss/vite": "^4.1.18", + "react": "19.2.3", + "react-dom": "19.2.3", + "storybook": "^10.2.0", + "tailwindcss": "^4.1.18" + }, + "peerDependencies": { + "@types/react": "19.x", + "@types/react-dom": "19.x", + "react": "19.x", + "react-dom": "19.x" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "packages/reforge/node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "packages/reforge/node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "packages/reforge/node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "dev": true, + "license": "MIT" + }, "packages/rollup-plugin-import-css": { "version": "0.0.0", "devDependencies": { @@ -26814,7 +27592,7 @@ "@primer/primitives": "10.x || 11.x", "@primer/react": "^38.4.0", "@rollup/plugin-babel": "^6.1.0", - "@storybook/react-vite": "^10.1.11", + "@storybook/react-vite": "^10.2.0", "@types/react": "18.3.11", "@types/react-dom": "18.3.1", "@types/styled-components": "^5.1.26", diff --git a/package.json b/package.json index 68b4d95b795..2235fe58bd2 100644 --- a/package.json +++ b/package.json @@ -51,11 +51,11 @@ "@eslint/compat": "^2.0.1", "@eslint/eslintrc": "^3.3.3", "@eslint/js": "^9.39.2", + "@github-ui/storybook-addon-performance-panel": "0.1.2", "@github/axe-github": "0.7.0", "@github/markdownlint-github": "^0.8.0", "@github/mini-throttle": "2.1.1", "@github/prettier-config": "0.0.6", - "@github-ui/storybook-addon-performance-panel": "0.1.2", "@mdx-js/react": "1.6.22", "@playwright/test": "^1.56.1", "@prettier/sync": "0.5.5", @@ -88,7 +88,7 @@ "prettier": "3.4.2", "rimraf": "5.0.5", "size-limit": "11.2.0", - "storybook": "^10.1.11", + "storybook": "^10.2.0", "stylelint": "16.20.0", "turbo": "^2.6.3", "typescript": "^5.9.2", diff --git a/packages/react/package.json b/packages/react/package.json index 0d7384cf1f6..8bdd4fc7283 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -107,7 +107,6 @@ "@figma/code-connect": "1.3.2", "@primer/css": "^21.5.1", "@primer/doc-gen": "^0.0.1", - "@tanstack/react-virtual": "^3.13.12", "@rollup/plugin-babel": "6.1.0", "@rollup/plugin-commonjs": "29.0.0", "@rollup/plugin-json": "6.1.0", @@ -118,7 +117,8 @@ "@storybook/addon-docs": "^10.1.11", "@storybook/addon-links": "^10.1.11", "@storybook/icons": "^2.0.1", - "@storybook/react-vite": "^10.1.11", + "@storybook/react-vite": "^10.2.0", + "@tanstack/react-virtual": "^3.13.12", "@testing-library/dom": "^10.4.0", "@testing-library/jest-dom": "^6.4.5", "@testing-library/react": "^16.3.0", @@ -172,7 +172,7 @@ "rimraf": "5.0.5", "rollup": "4.54.0", "rollup-plugin-import-css": "^0.0.0", - "storybook": "^10.1.11", + "storybook": "^10.2.0", "terser": "5.36.0", "ts-toolbelt": "9.6.0", "tsx": "4.20.3", diff --git a/packages/reforge/.storybook/main.ts b/packages/reforge/.storybook/main.ts new file mode 100644 index 00000000000..ac47d2dc67a --- /dev/null +++ b/packages/reforge/.storybook/main.ts @@ -0,0 +1,28 @@ +import type {StorybookConfig} from '@storybook/react-vite' +import tailwindcss from '@tailwindcss/vite' + +const config: StorybookConfig = { + stories: ['../src/**/!(*.dev).stories.@(js|jsx|ts|tsx)'], + + framework: { + name: '@storybook/react-vite', + options: { + strictMode: true, + }, + }, + + async viteFinal(config) { + // Deduplicate React to prevent "Invalid hook call" errors + // This ensures @base-ui/react uses the same React instance as the app + config.resolve = { + ...config.resolve, + dedupe: ['react', 'react-dom'], + } + + config.plugins = [...(config.plugins || []), tailwindcss()] + + return config + }, +} + +export default config diff --git a/packages/reforge/.storybook/preview.tsx b/packages/reforge/.storybook/preview.tsx new file mode 100644 index 00000000000..d63b4f5952d --- /dev/null +++ b/packages/reforge/.storybook/preview.tsx @@ -0,0 +1,58 @@ +import type {Preview, ReactRenderer} from '@storybook/react-vite' +import type {DecoratorFunction} from 'storybook/internal/types' +import React from 'react' + +import './styles.css' + +const ThemeDecorator: DecoratorFunction = (Story, context) => { + const theme = context.globals.theme || 'light' + + return ( +
+ +
+ ) +} + +const preview: Preview = { + tags: ['autodocs'], + decorators: [ThemeDecorator], + parameters: { + layout: 'centered', + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + // Disable Storybook's default backgrounds so they don't interfere + backgrounds: {disabled: true}, + // Configure docs to not apply default background + docs: { + story: { + inline: true, + }, + }, + options: { + storySort: { + method: 'alphabetical', + }, + }, + }, + globalTypes: { + theme: { + name: 'Theme', + description: 'Global theme for components', + defaultValue: 'light', + toolbar: { + icon: 'circlehollow', + items: [ + {value: 'light', title: 'Light', icon: 'sun'}, + {value: 'dark', title: 'Dark', icon: 'moon'}, + ], + }, + }, + }, +} + +export default preview diff --git a/packages/reforge/.storybook/styles.css b/packages/reforge/.storybook/styles.css new file mode 100644 index 00000000000..407f47fe433 --- /dev/null +++ b/packages/reforge/.storybook/styles.css @@ -0,0 +1,95 @@ +@import 'tailwindcss' important; + +@import '@primer/primitives/dist/css/primitives.css'; +@layer theme, base, components, utilities; +@import 'tailwindcss/theme.css' layer(theme); +@import 'tailwindcss/utilities.css' layer(utilities); +@import '@primer/tailwind-config'; + +@import '@primer/primitives/dist/css/primitives.css'; +@import '@primer/primitives/dist/css/functional/themes/light.css'; +@import '@primer/primitives/dist/css/functional/themes/dark.css'; + +/* Extend Tailwind theme with button colors from Primer primitives */ +/* TODO: Move this to @primer/tailwind-config when possible */ +@theme inline { + --shadow-button-default-rest: var(--button-default-shadow-resting); + + --background-color-button-primary-rest: var(--button-primary-bgColor-rest); + --background-color-button-primary-active: var(--button-primary-bgColor-active); + --background-color-button-primary-disabled: var(--button-primary-bgColor-disabled); + --background-color-button-primary-hover: var(--button-primary-bgColor-hover); + + --text-color-button-primary-rest: var(--button-primary-fgColor-rest); + --text-color-button-primary-disabled: var(--button-primary-fgColor-disabled); + + --border-color-button-primary-rest: var(--button-primary-borderColor-rest); + --border-color-button-primary-active: var(--button-primary-borderColor-active); + --border-color-button-primary-disabled: var(--button-primary-borderColor-disabled); + --border-color-button-primary-hover: var(--button-primary-borderColor-hover); + + --background-color-button-danger-rest: var(--button-danger-bgColor-rest); + --background-color-button-danger-active: var(--button-danger-bgColor-active); + --background-color-button-danger-disabled: var(--button-danger-bgColor-disabled); + --background-color-button-danger-hover: var(--button-danger-bgColor-hover); + + --text-color-button-danger-rest: var(--button-danger-fgColor-rest); + --text-color-button-danger-active: var(--button-danger-fgColor-active); + --text-color-button-danger-disabled: var(--button-danger-fgColor-disabled); + --text-color-button-danger-hover: var(--button-danger-fgColor-hover); + + --border-color-button-danger-rest: var(--button-danger-borderColor-rest); + --border-color-button-danger-active: var(--button-danger-borderColor-active); + --border-color-button-danger-hover: var(--button-danger-borderColor-hover); + + --background-color-button-default-rest: var(--button-default-bgColor-rest); + --background-color-button-default-active: var(--button-default-bgColor-active); + --background-color-button-default-disabled: var(--button-default-bgColor-disabled); + --background-color-button-default-hover: var(--button-default-bgColor-hover); + + --text-color-button-default-rest: var(--control-fgColor-rest); + --text-color-button-default-active: var(--control-fgColor-rest); + --text-color-button-default-disabled: var(--control-fgColor-disabled); + --text-color-button-default-hover: var(--control-fgColor-rest); + + --border-color-button-default-rest: var(--button-default-borderColor-rest); + --border-color-button-default-active: var(--button-default-borderColor-active); + --border-color-button-default-disabled: var(--button-default-borderColor-disabled); + --border-color-button-default-hover: var(--button-default-borderColor-hover); + + --background-color-button-outline-rest: var(--button-outline-bgColor-rest); + --background-color-button-outline-active: var(--button-outline-bgColor-active); + --background-color-button-outline-disabled: var(--button-outline-bgColor-disabled); + --background-color-button-outline-hover: var(--button-outline-bgColor-hover); + + --text-color-button-outline-rest: var(--button-outline-fgColor-rest); + --text-color-button-outline-active: var(--button-outline-fgColor-active); + --text-color-button-outline-disabled: var(--button-outline-fgColor-disabled); + --text-color-button-outline-hover: var(--button-outline-fgColor-hover); + + --background-color-button-invisible-rest: var(--button-invisible-bgColor-rest); + --background-color-button-invisible-active: var(--button-invisible-bgColor-active); + --background-color-button-invisible-disabled: var(--button-invisible-bgColor-disabled); + --background-color-button-invisible-hover: var(--button-invisible-bgColor-hover); + + --text-color-button-invisible-rest: var(--button-invisible-fgColor-rest); + --text-color-button-invisible-active: var(--button-invisible-fgColor-active); + --text-color-button-invisible-disabled: var(--button-invisible-fgColor-disabled); + --text-color-button-invisible-hover: var(--button-invisible-fgColor-hover); + + --text-color-button-star: var(--button-star-iconColor); + + --border-color-button-invisible-rest: var(--button-invisible-borderColor-rest); + --border-color-button-invisible-hover: var(--button-invisible-borderColor-hover); + + /* =========================================== + * BORDER RADIUS + * =========================================== */ + --radius-*: initial; + --radius-small: 0.1875rem; + --radius-medium: 0.375rem; + --radius-default: 0.375rem; + --radius-large: 0.75rem; + --radius-full: 624.9375rem; + --radius-overlay: 0.375rem; +} diff --git a/packages/reforge/package.json b/packages/reforge/package.json new file mode 100644 index 00000000000..81a7d4a1390 --- /dev/null +++ b/packages/reforge/package.json @@ -0,0 +1,54 @@ +{ + "name": "@primer/reforge", + "version": "0.0.0", + "type": "module", + "main": "./dist/index.js", + "module": "./dist/index.js", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": [ + "dist", + "CHANGELOG.md" + ], + "author": "GitHub, Inc.", + "license": "MIT", + "typings": "dist/index.d.ts", + "scripts": { + "start": "storybook dev -p 6007", + "test": "vitest --run" + }, + "dependencies": { + "@base-ui/react": "1.1.0", + "@primer/octicons-react": "^19.21.0", + "cva": "1.0.0-beta.4" + }, + "devDependencies": { + "@storybook/react-vite": "^10.2.0", + "@storybook/addon-actions": "^9.0.8", + "@primer/tailwind-config": "^0.1.0", + "@primer/primitives": "^11.3.2", + "tailwindcss": "^4.1.18", + "@tailwindcss/vite": "^4.1.18", + "react": "19.2.3", + "react-dom": "19.2.3", + "storybook": "^10.2.0" + }, + "peerDependencies": { + "@types/react": "19.x", + "@types/react-dom": "19.x", + "react": "19.x", + "react-dom": "19.x" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } +} diff --git a/packages/reforge/src/components/Button/Button.stories.tsx b/packages/reforge/src/components/Button/Button.stories.tsx new file mode 100644 index 00000000000..3e4e3a11fbe --- /dev/null +++ b/packages/reforge/src/components/Button/Button.stories.tsx @@ -0,0 +1,167 @@ +import React from 'react' +import {DownloadIcon, HeartIcon, PlusIcon, SearchIcon, StarIcon, TriangleDownIcon} from '@primer/octicons-react' +import type {Meta, StoryObj} from '@storybook/react-vite' +import {fn} from 'storybook/test' + +import {Button} from '.' +// import {Avatar} from '../avatar' + +const leadingVisualOptions = { + None: undefined, + 'Heart Icon': , + 'Star Icon': , + 'Search Icon': , + 'Plus Icon': , + 'Download Icon': , + // Avatar: , + // 'Avatar (Initials)': , +} + +const meta = { + title: 'Components/Button', + component: Button, + argTypes: { + variant: { + control: 'select', + options: ['default', 'primary', 'danger', 'invisible', 'link'], + }, + size: { + control: 'select', + options: ['small', 'medium', 'large'], + }, + block: { + control: 'boolean', + }, + inactive: { + control: 'boolean', + }, + disabled: { + control: 'boolean', + }, + leadingVisual: { + control: 'select', + options: Object.keys(leadingVisualOptions), + mapping: leadingVisualOptions, + }, + }, + args: { + onClick: fn(), + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + children: 'Button', + }, +} + +export const Primary: Story = { + args: { + variant: 'primary', + children: 'Primary button', + }, +} + +export const Danger: Story = { + args: { + variant: 'danger', + children: 'Danger button', + }, +} + +export const Invisible: Story = { + args: { + variant: 'invisible', + children: 'Invisible button', + }, +} + +export const Link: Story = { + args: { + variant: 'link', + children: 'Link button', + }, +} + +export const Small: Story = { + args: { + size: 'small', + children: 'Small button', + }, +} + +export const Large: Story = { + args: { + size: 'large', + children: 'Large button', + }, +} + +export const WithLeadingVisual: Story = { + args: { + leadingVisual: , + children: 'Like', + }, +} + +export const WithTrailingVisual: Story = { + args: { + trailingVisual: , + children: 'Dropdown', + }, +} + +export const WithBothVisuals: Story = { + args: { + leadingVisual: , + trailingVisual: , + children: 'Star', + }, +} + +export const Block: Story = { + args: { + block: true, + children: 'Full width button', + }, +} + +export const Disabled: Story = { + args: { + disabled: true, + children: 'Disabled button', + }, +} + +export const Inactive: Story = { + args: { + inactive: true, + children: 'Inactive button', + }, +} + +export const AllVariants: Story = { + render: () => ( +
+
+ + + + + +
+
+ + + +
+
+ + +
+
+ ), +} diff --git a/packages/reforge/src/components/Button/Button.tsx b/packages/reforge/src/components/Button/Button.tsx new file mode 100644 index 00000000000..e01aa0e9886 --- /dev/null +++ b/packages/reforge/src/components/Button/Button.tsx @@ -0,0 +1,189 @@ +import React from 'react' +import {Button as BaseButton} from '@base-ui/react/button' +import type {Icon} from '@primer/octicons-react' +import {cva, cx, type VariantProps} from 'cva' + +/** + * Shared base styles for button-like components (Button, IconButton). + */ +const buttonBaseStyles = cx([ + 'relative select-none cursor-pointer', + 'rounded-medium', + 'font-semibold align-middle transition-[background-color,color,border-color,box-shadow] duration-150', + 'disabled:cursor-not-allowed', + 'focus-visible:outline-focus-default focus-visible:outline-2 focus-visible:-outline-offset-1', + 'focus:z-raised', +]) + +/** + * Shared variant styles for button-like components. + * These define the color, background, and border styles for each variant. + */ +const buttonVariantStyles = { + default: [ + 'text-button-default-rest hover:text-button-default-hover active:text-button-default-active disabled:text-button-default-disabled shadow-button-default-rest', + 'bg-button-default-rest hover:bg-button-default-hover active:bg-button-default-active disabled:bg-button-default-disabled ', + 'border border-button-default-rest hover:border-button-default-hover active:border-button-default-active disabled:border-button-default-disabled', + 'aria-expanded:bg-button-default-active', + ], + primary: [ + 'text-button-primary-rest disabled:text-button-primary-disabled shadow-button-default-rest', + 'bg-button-primary-rest hover:bg-button-primary-hover active:bg-button-primary-active disabled:bg-button-primary-disabled', + 'border border-button-primary-rest hover:border-button-primary-hover active:border-button-primary-active disabled:border-button-primary-disabled', + 'aria-expanded:bg-button-primary-active', + ], + danger: [ + 'text-button-danger-rest hover:text-button-danger-hover active:text-button-danger-active disabled:text-button-danger-disabled shadow-button-default-rest', + 'bg-button-danger-rest hover:bg-button-danger-hover active:bg-button-danger-active disabled:bg-button-danger-disabled', + 'border border-button-danger-rest hover:border-button-danger-hover active:border-button-danger-active disabled:border-button-danger-disabled', + 'aria-expanded:bg-button-danger-active', + ], + invisible: [ + 'text-button-invisible-rest hover:text-button-invisible-hover active:text-button-invisible-active disabled:text-button-invisible-disabled', + 'bg-button-invisible-rest hover:bg-button-invisible-hover active:bg-button-invisible-active disabled:bg-button-invisible-disabled', + 'border border-button-invisible-rest hover:border-button-invisible-hover disabled:border-button-invisible-disabled', + 'aria-expanded:bg-button-invisible-active', + ], + link: ['text-link hover:underline hover:underline-offset-3 active:text-button-link-active disabled:text-disabled'], +} + +/** + * Shared inactive state styles. + */ +const buttonInactiveStyles = { + true: 'opacity-60 pointer-events-none', + false: '', +} + +const buttonVariants = cva({ + base: [buttonBaseStyles, 'flex items-stretch', 'min-w-fit'], + variants: { + variant: { + default: [ + ...buttonVariantStyles.default, + '**:data-[component="leadingVisual"]:text-muted **:data-[component="trailingVisual"]:text-muted', + 'disabled:**:data-[component="leadingVisual"]:text-button-default-disabled disabled:**:data-[component="trailingVisual"]:text-button-default-disabled', + ], + primary: buttonVariantStyles.primary, + danger: buttonVariantStyles.danger, + invisible: [ + ...buttonVariantStyles.invisible, + '**:data-[component="leadingVisual"]:text-muted **:data-[component="trailingVisual"]:text-muted', + 'disabled:**:data-[component="leadingVisual"]:text-button-invisible-disabled disabled:**:data-[component="trailingVisual"]:text-button-invisible-disabled', + ], + link: buttonVariantStyles.link, + }, + block: { + true: 'w-full', + false: 'w-auto', + }, + size: { + small: ['text-body-small', 'h-7 px-2'], + medium: ['text-body-medium', 'h-8 px-3'], + large: ['text-body-medium', 'h-10 px-4'], + }, + inactive: buttonInactiveStyles, + }, + defaultVariants: { + variant: 'default', + block: false, + size: 'medium', + inactive: false, + }, +}) + +const buttonContentClassName = 'grid auto-cols-max grid-flow-col items-center justify-center gap-2 w-full' + +type ButtonVariants = VariantProps + +type ButtonProps = BaseButton.Props & + Omit & { + ref?: React.Ref + leadingVisual?: React.ReactElement + trailingVisual?: React.ReactElement + trailingAction?: React.ReactNode + block?: boolean + inactive?: boolean + labelWrap?: boolean + nativeButton?: true + } + +function Button({ + ref, + type = 'button', + className, + variant, + size, + block, + leadingVisual, + trailingVisual, + trailingAction, + children, + inactive = false, + labelWrap: _labelWrap = false, + disabled, + ...rest +}: ButtonProps) { + return ( + + + {leadingVisual && ( + + )} + {children && ( + + {children} + + )} + {trailingVisual && ( + + )} + {trailingAction && ( + + )} + + + ) +} + +export {Button, buttonVariants, buttonBaseStyles, buttonVariantStyles, buttonInactiveStyles, buttonContentClassName} + +export namespace Button { + export type Props = ButtonProps + export type Variant = ButtonVariants['variant'] + export type Size = ButtonVariants['size'] +} diff --git a/packages/reforge/src/components/Button/index.tsx b/packages/reforge/src/components/Button/index.tsx new file mode 100644 index 00000000000..ec2f504d5a6 --- /dev/null +++ b/packages/reforge/src/components/Button/index.tsx @@ -0,0 +1,10 @@ +export { + Button, + buttonBaseStyles, + buttonContentClassName, + buttonInactiveStyles, + buttonVariants, + buttonVariantStyles, +} from './Button' + +export type * from './Button' diff --git a/packages/reforge/src/components/IconButton/IconButton.stories.tsx b/packages/reforge/src/components/IconButton/IconButton.stories.tsx new file mode 100644 index 00000000000..2c198cd635d --- /dev/null +++ b/packages/reforge/src/components/IconButton/IconButton.stories.tsx @@ -0,0 +1,291 @@ +import React from 'react' +import { + BellIcon, + CodeIcon, + DownloadIcon, + GearIcon, + HeartIcon, + InboxIcon, + PencilIcon, + PlusIcon, + SearchIcon, + SyncIcon, + TrashIcon, + XIcon, +} from '@primer/octicons-react' +import type {Meta, StoryObj} from '@storybook/react-vite' + +import {IconButton} from '.' + +const meta: Meta = { + title: 'Components/Icon Button', + component: IconButton, + argTypes: { + variant: { + control: 'select', + options: ['default', 'primary', 'danger', 'invisible'], + }, + size: { + control: 'select', + options: ['small', 'medium', 'large'], + }, + loading: { + control: 'boolean', + }, + inactive: { + control: 'boolean', + }, + disabled: { + control: 'boolean', + }, + }, +} + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + icon: , + 'aria-label': 'Favorite', + }, +} + +export const Primary: Story = { + args: { + icon: , + 'aria-label': 'Add item', + variant: 'primary', + }, +} + +export const Danger: Story = { + args: { + icon: , + 'aria-label': 'Delete', + variant: 'danger', + }, +} + +export const Invisible: Story = { + args: { + icon: , + 'aria-label': 'Close', + variant: 'invisible', + }, +} + +export const Small: Story = { + args: { + icon: , + 'aria-label': 'Settings', + size: 'small', + }, +} + +export const Medium: Story = { + args: { + icon: , + 'aria-label': 'Settings', + size: 'medium', + }, +} + +export const Large: Story = { + args: { + icon: , + 'aria-label': 'Settings', + size: 'large', + }, +} + +export const Loading: Story = { + args: { + icon: , + 'aria-label': 'Sync', + loading: true, + }, +} + +export const LoadingPrimary: Story = { + args: { + icon: , + 'aria-label': 'Sync', + loading: true, + variant: 'primary', + }, +} + +export const Inactive: Story = { + args: { + icon: , + 'aria-label': 'Favorite', + inactive: true, + }, +} + +export const Disabled: Story = { + args: { + icon: , + 'aria-label': 'Favorite', + disabled: true, + }, +} + +export const AllVariants: Story = { + args: { + icon: , + 'aria-label': 'Favorite', + }, + render: () => ( +
+
+

Variants

+
+ } aria-label="Default" variant="default" /> + } aria-label="Primary" variant="primary" /> + } aria-label="Danger" variant="danger" /> + } aria-label="Invisible" variant="invisible" /> +
+
+
+

Sizes

+
+ } aria-label="Small" size="small" /> + } aria-label="Medium" size="medium" /> + } aria-label="Large" size="large" /> +
+
+
+

States

+
+ } aria-label="Default" /> + } aria-label="Inactive" inactive /> + } aria-label="Disabled" disabled /> + } aria-label="Loading" loading /> +
+
+
+ ), +} + +export const CommonUseCases: Story = { + args: { + icon: , + 'aria-label': 'Favorite', + }, + render: () => ( +
+
+

Navigation & Actions

+
+ } aria-label="Search" /> + } aria-label="Notifications" /> + } aria-label="Inbox" /> + } aria-label="Settings" /> +
+
+
+

Editor Actions

+
+ } aria-label="Edit" /> + } aria-label="View code" /> + } aria-label="Download" /> + } aria-label="Delete" variant="danger" /> +
+
+
+

Dialog Actions

+
+ } aria-label="Close dialog" variant="invisible" /> +
+
+
+ ), +} + +export const SizeComparison: Story = { + args: { + icon: , + 'aria-label': 'Favorite', + }, + render: () => ( +
+ {(['default', 'primary', 'danger', 'invisible'] as const).map(variant => ( +
+ {variant} +
+ } aria-label={`${variant} small`} variant={variant} size="small" /> + } aria-label={`${variant} medium`} variant={variant} size="medium" /> + } aria-label={`${variant} large`} variant={variant} size="large" /> +
+
+ ))} +
+ ), +} + +export const WithDescription: Story = { + args: { + icon: , + 'aria-label': 'Notifications', + description: 'You have unread notifications', + }, +} + +export const TooltipDirections: Story = { + args: { + icon: , + 'aria-label': 'Favorite', + }, + render: () => ( +
+
+
+ } aria-label="Top" description="Tooltip on top" side="top" /> +
+ } aria-label="Left" description="Tooltip on left" side="left" /> +
+ Hover buttons +
+ } aria-label="Right" description="Tooltip on right" side="right" /> +
+ } aria-label="Bottom" description="Tooltip on bottom" side="bottom" /> +
+
+
+ ), +} + +export const WithDescriptionVariants: Story = { + args: { + icon: , + 'aria-label': 'Favorite', + }, + render: () => ( +
+
+

Hover to see tooltips

+
+ } aria-label="Notifications" description="You have 5 unread notifications" /> + } aria-label="Inbox" description="Open your inbox" /> + } aria-label="Settings" description="Open settings" /> + } aria-label="Search" description="Search the repository" /> +
+
+
+

Different variants with descriptions

+
+ } aria-label="Add" description="Create new item" variant="primary" /> + } + aria-label="Delete" + description="Delete this item permanently" + variant="danger" + /> + } aria-label="Close" description="Close this panel" variant="invisible" /> +
+
+
+ ), +} diff --git a/packages/reforge/src/components/IconButton/IconButton.tsx b/packages/reforge/src/components/IconButton/IconButton.tsx new file mode 100644 index 00000000000..2a44b0039c1 --- /dev/null +++ b/packages/reforge/src/components/IconButton/IconButton.tsx @@ -0,0 +1,147 @@ +import React from 'react' +import {Button as BaseButton} from '@base-ui/react/button' +import type {Icon} from '@primer/octicons-react' +import {cva, type VariantProps} from 'cva' + +import {buttonBaseStyles, buttonInactiveStyles, buttonVariantStyles} from '../Button' +import {Tooltip} from '../Tooltip' + +const iconButtonVariants = cva({ + base: [buttonBaseStyles, 'flex items-center justify-center shrink-0'], + variants: { + variant: { + default: [...buttonVariantStyles.default, 'text-muted hover:text-muted'], + primary: buttonVariantStyles.primary, + danger: buttonVariantStyles.danger, + invisible: [...buttonVariantStyles.invisible, 'text-muted hover:text-muted'], + }, + size: { + small: ['size-7', '[&_svg]:size-4'], + medium: ['size-8', '[&_svg]:size-4'], + large: ['size-10', '[&_svg]:size-5'], + }, + inactive: buttonInactiveStyles, + loading: { + true: 'cursor-wait', + false: '', + }, + }, + defaultVariants: { + variant: 'default', + size: 'medium', + inactive: false, + loading: false, + }, +}) + +type IconButtonVariants = VariantProps + +type TooltipProps = Pick + +type IconButtonProps = Omit & + IconButtonVariants & + TooltipProps & { + icon: React.ReactElement + 'aria-label': string + description?: string + loading?: boolean + inactive?: boolean + type?: 'button' | 'submit' | 'reset' + } + +function IconButton({ + className, + variant, + size, + icon, + 'aria-label': ariaLabel, + description, + side, + align, + sideOffset, + alignOffset, + collisionPadding, + loading = false, + inactive = false, + disabled, + type = 'button', + ...rest +}: IconButtonProps) { + const isDisabled = disabled || loading + + const button = ( + + {loading ? ( + + ) : ( + + )} + + ) + + if (description) { + return ( + + + + {description} + + + ) + } + + return button +} + +const spinnerVariants = cva({ + base: ['inline-block animate-spin rounded-full border-current border-b-transparent'], + variants: { + size: { + small: 'size-4 border-2', + medium: 'size-4 border-2', + large: 'size-5 border-2', + }, + }, + defaultVariants: { + size: 'medium', + }, +}) + +function LoadingSpinner({size = 'medium'}: {size?: IconButtonVariants['size']}) { + return