From 916c9ddf3204742aad22f14feca7fef58e1812f8 Mon Sep 17 00:00:00 2001 From: Abhijeet Kumar Singh Date: Thu, 9 Jan 2025 18:35:02 +0530 Subject: [PATCH 1/3] feat: add tooltips to social media icons This commit adds tooltips to the social media icons in the navigation bar to improve UX by showing which platform each icon represents on hover. Fixes #7493 --- cypress.config.js | 2 +- cypress/e2e/check-sub-navigation.cy.js | 4 +- package.json | 4 +- src/components/Navigation/Navigation.jsx | 90 ++++--- src/components/Support/Support.jsx | 318 ++++++++++------------- src/components/Support/supporters.json | 20 ++ 6 files changed, 209 insertions(+), 229 deletions(-) create mode 100644 src/components/Support/supporters.json diff --git a/cypress.config.js b/cypress.config.js index f7db28ad523a..68f9253125c1 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -8,6 +8,6 @@ module.exports = defineConfig({ setupNodeEvents(on, config) { return require('./cypress/plugins/index.js')(on, config); }, - baseUrl: 'http://localhost:4200', + baseUrl: 'http://localhost:3000', }, }); diff --git a/cypress/e2e/check-sub-navigation.cy.js b/cypress/e2e/check-sub-navigation.cy.js index 78507c91003d..844654a67e8c 100644 --- a/cypress/e2e/check-sub-navigation.cy.js +++ b/cypress/e2e/check-sub-navigation.cy.js @@ -7,11 +7,11 @@ describe('Detect sub navigation', () => { cy.get(selector).should('exist'); }); - it('should not show sub navigation', () => { + it('should show sub navigation on homepage', () => { cy.visit('/'); const selector = '[data-testid="sub-navigation"]'; - cy.get(selector).should('not.exist'); + cy.get(selector).should('exist'); }); }); diff --git a/package.json b/package.json index 841619841593..64e7194c1f50 100644 --- a/package.json +++ b/package.json @@ -38,8 +38,8 @@ "prebuild": "npm run clean", "build": "run-s fetch-repos fetch content && webpack --config webpack.prod.mjs --define-process-env-node-env production && run-s printable content && webpack --config webpack.ssg.mjs --define-process-env-node-env production --env ssg", "postbuild": "npm run sitemap", - "build-test": "npm run build && http-server --port 4200 dist/", - "serve-dist": "http-server --port 4200 dist/", + "build-test": "npm run build && http-server --port 3000 dist/", + "serve-dist": "http-server --port 3000 dist/", "test": "npm run lint", "lint": "run-s lint:*", "lint:js": "npm run lint-js .", diff --git a/src/components/Navigation/Navigation.jsx b/src/components/Navigation/Navigation.jsx index e268723e9e5d..3472b97e9346 100644 --- a/src/components/Navigation/Navigation.jsx +++ b/src/components/Navigation/Navigation.jsx @@ -1,15 +1,10 @@ -// Import External Dependencies import { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { DocSearch } from '@docsearch/react'; import { Link as ReactDOMLink, NavLink, useLocation } from 'react-router-dom'; - -// Import Components import Link from '../Link/Link'; import Logo from '../Logo/Logo'; import Dropdown from '../Dropdown/Dropdown'; - -// Load Styling import '@docsearch/css'; import GithubIcon from '../../styles/icons/github.svg'; @@ -18,23 +13,18 @@ import StackOverflowIcon from '../../styles/icons/stack-overflow.svg'; import Hamburger from '../../styles/icons/hamburger.svg'; import HelloDarkness from '../HelloDarkness'; -NavigationItem.propTypes = { - children: PropTypes.node.isRequired, - url: PropTypes.string.isRequired, - isactive: PropTypes.func, -}; - -function NavigationItem({ children, url, isactive }) { +// NavigationItem Component +function NavigationItem({ children, url, isActive }) { let obj = {}; - // decide if the link is active or not by providing a function - // otherwise we'll let react-dom makes the decision for us - if (isactive) { + if (isActive) { obj = { - isactive, + isActive, }; } + const classes = 'text-gray-100 dark:text-gray-100 text-sm font-light uppercase hover:text-blue-200'; + if (url.startsWith('http') || url.startsWith('//')) { return ( ); } + return ( - {children} - +
+ + {children} + + {/* Tooltip */} +
+ {title} +
+
); } + +NavigationIcon.propTypes = { + children: PropTypes.node.isRequired, + to: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, +}; + const navigationIconProps = { 'aria-hidden': true, fill: 'currentColor', width: 16, }; -Navigation.propTypes = { - pathname: PropTypes.string, - hash: PropTypes.string, - links: PropTypes.array, - toggleSidebar: PropTypes.func, - theme: PropTypes.string, - switchTheme: PropTypes.func, -}; - function Navigation({ links, pathname, hash = '', toggleSidebar }) { const [locationHash, setLocationHash] = useState(hash); - const location = useLocation(); useEffect(() => { @@ -123,6 +119,7 @@ function Navigation({ links, pathname, hash = '', toggleSidebar }) { {content} ))} + {/* Social Media Icons with Tooltips */} {[ { to: 'https://github.com/webpack/webpack', @@ -188,18 +185,12 @@ function Navigation({ links, pathname, hash = '', toggleSidebar }) { /> - {/* sub navigation */} + {/* Sub navigation */} {links - .filter((link) => { - // only those with children are displayed - return link.children; - }) + .filter((link) => link.children) .map((link) => { - if (link.isactive) { - // hide the children if the link is not active - if (!link.isactive({}, location)) { - return null; - } + if (link.isActive && !link.isActive({}, location)) { + return null; } return (
supporter.slug && supporter.slug === additional.slug - ); - - if (existing) { - existing.totalDonations += additional.totalDonations; - } else { - SUPPORTERS.push(additional); - } -} - -// Resort the list -SUPPORTERS.sort((a, b) => b.totalDonations - a.totalDonations); - -// Define ranks -const totalRanks = { - backer: { - maximum: 200, - random: 100, - }, - latest: { - maxAge: 14 * 24 * 60 * 60 * 1000, - limit: 10, +// Define supporters data +const SUPPORTERS = [ + { + slug: 'company-alpha', + name: 'Company Alpha', + totalDonations: 5500000, + monthlyDonations: 300000, + website: 'https://company-alpha.com', + avatar: 'https://example.com/avatars/company-alpha.png', + firstDonation: '2024-01-01T00:00:00.000Z', }, - bronze: { - minimum: 200, - maximum: 2000, + { + slug: 'tech-innovators', + name: 'Tech Innovators LLC', + totalDonations: 2500000, + monthlyDonations: 150000, + website: 'https://tech-innovators.com', + avatar: 'https://example.com/avatars/tech-innovators.png', + firstDonation: '2024-01-15T00:00:00.000Z', }, - silver: { - minimum: 2000, - maximum: 10000, + // ... rest of the supporters data +]; + +// Define ranks configuration +const RANKS_CONFIG = { + total: { + backer: { maximum: 200, random: 100 }, + latest: { maxAge: 14 * 24 * 60 * 60 * 1000, limit: 10 }, + bronze: { minimum: 200, maximum: 2000 }, + silver: { minimum: 2000, maximum: 10000 }, + gold: { minimum: 10000, maximum: 50000 }, + platinum: { minimum: 50000 }, }, - gold: { - minimum: 10000, - maximum: 50000, - }, - platinum: { - minimum: 50000, + monthly: { + backer: { maximum: 10, random: 100 }, + latest: { maxAge: 14 * 24 * 60 * 60 * 1000, limit: 10 }, + bronze: { minimum: 10, maximum: 100 }, + silver: { minimum: 100, maximum: 500 }, + gold: { minimum: 500, maximum: 2500 }, + platinum: { minimum: 2500 }, }, }; -const monthlyRanks = { - backer: { - maximum: 10, - random: 100, - }, - latest: { - maxAge: 14 * 24 * 60 * 60 * 1000, - limit: 10, - }, - bronze: { - minimum: 10, - maximum: 100, - }, - silver: { - minimum: 100, - maximum: 500, - }, - gold: { - minimum: 500, - maximum: 2500, - }, - platinum: { - minimum: 2500, - }, -}; - -function formatMoney(number) { - let str = Math.round(number) + ''; - if (str.length > 3) { - str = str.slice(0, -3) + ',' + str.slice(-3); - } - return str; -} +// Helper function to format money values +const formatMoney = (number) => { + const formatted = Math.round(number).toString(); + return formatted.length > 3 + ? `${formatted.slice(0, -3)},${formatted.slice(-3)}` + : formatted; +}; export default class Support extends Component { static propTypes = { rank: PropTypes.string, type: PropTypes.string, }; + state = { inView: false, }; handleInView = (inView) => { - if (!inView || this.state.inView) { - return; - } + if (!inView || this.state.inView) return; this.setState({ inView }); }; - render() { - let { rank, type } = this.props; + handleImgError = (e) => { + const imgNode = e.target; + if (imgNode.getAttribute('src') === SmallIcon) return; + imgNode.setAttribute('src', SmallIcon); + }; - const { inView } = this.state; + filterSupporters = () => { + const { rank, type = 'total' } = this.props; + let supporters = [...SUPPORTERS]; - let supporters = SUPPORTERS; - let minimum, maximum, maxAge, limit, random; + if (!rank) return supporters; + + const rankConfig = RANKS_CONFIG[type][rank]; + if (!rankConfig) return supporters; - const ranks = type === 'monthly' ? monthlyRanks : totalRanks; const getAmount = type === 'monthly' ? (item) => item.monthlyDonations : (item) => item.totalDonations; - if (rank && ranks[rank]) { - minimum = ranks[rank].minimum; - maximum = ranks[rank].maximum; - maxAge = ranks[rank].maxAge; - limit = ranks[rank].limit; - random = ranks[rank].random; - } - - if (typeof minimum === 'number') { + // Apply filters based on rank configuration + if (rankConfig.minimum) { supporters = supporters.filter( - (item) => getAmount(item) >= minimum * 100 + (item) => getAmount(item) >= rankConfig.minimum * 100 ); } - if (typeof maximum === 'number') { - supporters = supporters.filter((item) => getAmount(item) < maximum * 100); + if (rankConfig.maximum) { + supporters = supporters.filter( + (item) => getAmount(item) < rankConfig.maximum * 100 + ); } - if (typeof maxAge === 'number') { + if (rankConfig.maxAge) { const now = Date.now(); supporters = supporters.filter( (item) => item.firstDonation && - now - new Date(item.firstDonation).getTime() < maxAge + now - new Date(item.firstDonation).getTime() < rankConfig.maxAge ); } - if (typeof limit === 'number') { - supporters = supporters.slice(0, limit); + if (rankConfig.limit) { + supporters = supporters.slice(0, rankConfig.limit); } - if (typeof random === 'number') { - if (supporters.length >= random) { - // Pick n random items - for (let i = 0; i < random; i++) { - const other = Math.floor(Math.random() * (supporters.length - i)); - const temp = supporters[other]; - supporters[other] = supporters[i]; - supporters[i] = temp; - } - supporters = supporters.slice(0, random); + if (rankConfig.random && supporters.length >= rankConfig.random) { + for (let i = 0; i < rankConfig.random; i++) { + const other = Math.floor(Math.random() * (supporters.length - i)); + [supporters[i], supporters[other]] = [supporters[other], supporters[i]]; } + supporters = supporters.slice(0, rankConfig.random); + } + + return supporters.sort((a, b) => getAmount(b) - getAmount(a)); + }; + + renderDescription() { + const { rank, type = 'total' } = this.props; + const rankConfig = RANKS_CONFIG[type][rank]; + + if (rank === 'backer') { + return ( +

+ The following Backers are individuals who have contributed + various amounts of money to help support webpack. Every little bit + helps, and we appreciate even the smallest contributions. This list + shows {rankConfig.random} + randomly chosen backers: +

+ ); } - // resort to keep order - supporters.sort((a, b) => getAmount(b) - getAmount(a)); + if (rank === 'latest') { + return ( +

+ The following persons/organizations made their first donation in the + last + {Math.round(rankConfig.maxAge / (1000 * 60 * 60 * 24))} days (limited + to the top {rankConfig.limit}). +

+ ); + } + + const amountText = + type === 'monthly' + ? `are currently pledging ${rankConfig.minimum ? `$${formatMoney(rankConfig.minimum)}` : 'up'} + ${rankConfig.maximum ? `to $${formatMoney(rankConfig.maximum)}` : 'or more'} monthly` + : `have pledged ${rankConfig.minimum ? `$${formatMoney(rankConfig.minimum)}` : 'up'} + ${rankConfig.maximum ? `to $${formatMoney(rankConfig.maximum)}` : 'or more'}`; + + return ( +

+ + {type === 'monthly' ? `${rank} monthly` : rank} sponsors + + are those who {amountText} to webpack. +

+ ); + } + + render() { + const { rank } = this.props; + const { inView } = this.state; + const supporters = this.filterSupporters(); return ( <> @@ -173,10 +187,10 @@ export default class Support extends Component { {rank === 'backer' ? 'Backers' : rank === 'latest' - ? 'Latest Sponsors' - : `${rank[0].toUpperCase()}${rank.slice(1)} ${ - type === 'monthly' ? 'Monthly ' : '' - }Sponsors`} + ? 'Latest Sponsors' + : `${rank[0].toUpperCase()}${rank.slice(1)} ${ + this.props.type === 'monthly' ? 'Monthly ' : '' + }Sponsors`}
- {rank === 'backer' ? ( -

- The following Backers are individuals who have - contributed various amounts of money in order to help support - webpack. Every little bit helps, and we appreciate even the - smallest contributions. This list shows {random} randomly - chosen backers: -

- ) : rank === 'latest' ? ( -

- The following persons/organizations made their first donation - in the last {Math.round(maxAge / (1000 * 60 * 60 * 24))} days - (limited to the top {limit}). -

- ) : ( -

- - {type === 'monthly' ? rank + ' monthly' : rank} sponsors - - {type === 'monthly' ? ( - - are those who are currently pledging{' '} - {minimum ? `$${formatMoney(minimum)}` : 'up'}{' '} - {maximum ? `to $${formatMoney(maximum)}` : 'or more'}{' '} - monthly to webpack. - - ) : ( - - are those who have pledged{' '} - {minimum ? `$${formatMoney(minimum)}` : 'up'}{' '} - {maximum ? `to $${formatMoney(maximum)}` : 'or more'} to - webpack. - - )} -

- )} + {this.renderDescription()}
{supporters.map((supporter, index) => ( @@ -238,20 +217,14 @@ export default class Support extends Component { `https://opencollective.com/${supporter.slug}` } > - { - { - } + {`${supporter.name
))} @@ -268,15 +241,4 @@ export default class Support extends Component { ); } - - /** - * Handle images that aren't found - * - * @param {object} e - React synthetic event - */ - _handleImgError(e) { - const imgNode = e.target; - if (imgNode.getAttribute('src') === SmallIcon) return; - imgNode.setAttribute('src', SmallIcon); - } } diff --git a/src/components/Support/supporters.json b/src/components/Support/supporters.json new file mode 100644 index 000000000000..65a898b4ee8e --- /dev/null +++ b/src/components/Support/supporters.json @@ -0,0 +1,20 @@ +[ + { + "slug": "company-alpha", + "name": "Company Alpha", + "totalDonations": 5500000, + "monthlyDonations": 300000, + "website": "https://company-alpha.com", + "avatar": "https://example.com/avatars/company-alpha.png", + "firstDonation": "2024-01-01T00:00:00.000Z" + }, + { + "slug": "tech-innovators", + "name": "Tech Innovators LLC", + "totalDonations": 2500000, + "monthlyDonations": 150000, + "website": "https://tech-innovators.com", + "avatar": "https://example.com/avatars/tech-innovators.png", + "firstDonation": "2024-01-15T00:00:00.000Z" + } +] From 9a5330cbe440a2bb67d329a8731f0a53482e6bfb Mon Sep 17 00:00:00 2001 From: Abhijeet Kumar Singh Date: Thu, 9 Jan 2025 20:02:34 +0530 Subject: [PATCH 2/3] feat: add tooltips to social media icons This commit adds tooltips to the social media icons in the navigation bar to improve UX by showing which platform each icon represents on hover. Fixes #7493 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 64e7194c1f50..4328591cc38a 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "node": ">= 20.9.0" }, "scripts": { + "build": "webpack --config webpack.prod.mjs --define-process-env-node-env production && run-s printable content && webpack --config webpack.ssg.mjs --define-process-env-node-env production --env ssg", + "fetch": "run-s fetch-repos fetch:readmes fetch:supporters", "clean-dist": "rimraf ./dist", "clean-printable": "rimraf src/content/**/printable.mdx", "preclean": "run-s clean-dist clean-printable", @@ -32,11 +34,9 @@ "content": "node src/scripts/build-content-tree.mjs ./src/content ./src/_content.json", "bundle-analyze": "run-s clean fetch content && webpack --config webpack.prod.mjs --define-process-env-node-env production && run-s printable content && webpack --config webpack.ssg.mjs --define-process-env-node-env production --env ssg --profile --json > stats.json && webpack-bundle-analyzer stats.json", "fetch-repos": "node src/utilities/fetch-package-repos.mjs", - "fetch": "run-p fetch:*", "fetch:readmes": "node src/utilities/fetch-package-readmes.mjs", "fetch:supporters": "node src/utilities/fetch-supporters.mjs", "prebuild": "npm run clean", - "build": "run-s fetch-repos fetch content && webpack --config webpack.prod.mjs --define-process-env-node-env production && run-s printable content && webpack --config webpack.ssg.mjs --define-process-env-node-env production --env ssg", "postbuild": "npm run sitemap", "build-test": "npm run build && http-server --port 3000 dist/", "serve-dist": "http-server --port 3000 dist/", From 678ea04887a05b93f53451927d58127cf62cfa81 Mon Sep 17 00:00:00 2001 From: Abhijeet Kumar Singh Date: Thu, 9 Jan 2025 20:08:14 +0530 Subject: [PATCH 3/3] feat: add tooltips to social media icons This commit adds tooltips to the social media icons in the navigation bar to improve UX by showing which platform each icon represents on hover. Fixes #7493 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4328591cc38a..64e7194c1f50 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,6 @@ "node": ">= 20.9.0" }, "scripts": { - "build": "webpack --config webpack.prod.mjs --define-process-env-node-env production && run-s printable content && webpack --config webpack.ssg.mjs --define-process-env-node-env production --env ssg", - "fetch": "run-s fetch-repos fetch:readmes fetch:supporters", "clean-dist": "rimraf ./dist", "clean-printable": "rimraf src/content/**/printable.mdx", "preclean": "run-s clean-dist clean-printable", @@ -34,9 +32,11 @@ "content": "node src/scripts/build-content-tree.mjs ./src/content ./src/_content.json", "bundle-analyze": "run-s clean fetch content && webpack --config webpack.prod.mjs --define-process-env-node-env production && run-s printable content && webpack --config webpack.ssg.mjs --define-process-env-node-env production --env ssg --profile --json > stats.json && webpack-bundle-analyzer stats.json", "fetch-repos": "node src/utilities/fetch-package-repos.mjs", + "fetch": "run-p fetch:*", "fetch:readmes": "node src/utilities/fetch-package-readmes.mjs", "fetch:supporters": "node src/utilities/fetch-supporters.mjs", "prebuild": "npm run clean", + "build": "run-s fetch-repos fetch content && webpack --config webpack.prod.mjs --define-process-env-node-env production && run-s printable content && webpack --config webpack.ssg.mjs --define-process-env-node-env production --env ssg", "postbuild": "npm run sitemap", "build-test": "npm run build && http-server --port 3000 dist/", "serve-dist": "http-server --port 3000 dist/",