diff --git a/package-lock.json b/package-lock.json index 0e0908a5..ac37c5ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4285,6 +4285,12 @@ "node": ">= 12" } }, + "node_modules/compute-scroll-into-view": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", + "integrity": "sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -4746,6 +4752,28 @@ "resolved": "plugins/doodles", "link": true }, + "node_modules/downshift": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-9.0.8.tgz", + "integrity": "sha512-59BWD7+hSUQIM1DeNPLirNNnZIO9qMdIK5GQ/Uo8q34gT4B78RBlb9dhzgnh0HfQTJj4T/JKYD8KoLAlMWnTsA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.5", + "compute-scroll-into-view": "^3.1.0", + "prop-types": "^15.8.1", + "react-is": "18.2.0", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, + "node_modules/downshift/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "license": "MIT" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -10001,6 +10029,7 @@ "@ataverascrespo/react18-ts-textfit": "^1.0.0", "@triozer/framer-toolbox": "^0.1.14", "aveta": "^1.4.1", + "downshift": "^9.0.8", "framer-plugin": "^1.0.0", "react": "^18", "react-dom": "^18", diff --git a/plugins/google-search-console/package.json b/plugins/google-search-console/package.json index a56de4a3..e363e9fb 100644 --- a/plugins/google-search-console/package.json +++ b/plugins/google-search-console/package.json @@ -13,6 +13,7 @@ "@ataverascrespo/react18-ts-textfit": "^1.0.0", "@triozer/framer-toolbox": "^0.1.14", "aveta": "^1.4.1", + "downshift": "^9.0.8", "framer-plugin": "^1.0.0", "react": "^18", "react-dom": "^18", diff --git a/plugins/google-search-console/src/App.css b/plugins/google-search-console/src/App.css index 54d9cf02..93f79694 100644 --- a/plugins/google-search-console/src/App.css +++ b/plugins/google-search-console/src/App.css @@ -582,3 +582,81 @@ body[data-framer-theme='dark'] .chart-tooltip-wrapper { .recharts-cartesian-axis-ticks { font-variant-numeric: tabular-nums !important; } + +.date-range-header { + text-align: right; + margin-bottom: 1em; +} + +.select-container { + position: relative; + padding-top: 1px; +} + +.select-button { + display: inline-block; + padding: 0.5em 1em; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +body[data-framer-theme='dark'] .select-button { + background: var(--framer-color-bg-secondary); +} + +.select-button:focus { + outline: none; +} + +.select-button:focus-visible { + outline: 2px solid var(--framer-color-tint); + outline-offset: -1px; +} + +.select-button:hover, +.select-button:focus-visible, +.select-container--open .select-button { + border-color: rgba(0, 0, 0, 0.2); +} + +.select-button--arrow { + display: inline-block; + margin-left: 0.25em; +} + +.select-options { + position: absolute; + background-color: var(--framer-color-bg, #fff); + right: 0; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); + border: 1px solid var(--framer-color-bg-tertiary); + padding: 0.5em; + text-align: left; + border-radius: 4px; + margin-top: 4px; + display: none; + z-index: 100; +} + +.select-container--open .select-options { + display: block; +} + +.select-option { + display: block; + padding: 0.5em 2em 0.5em 1em; + border-radius: 4px; +} + +.select-option--highlighted { + background-color: rgba(0, 0, 0, 0.025); +} + +body[data-framer-theme='dark'] .select-option--highlighted { + background: var(--framer-color-bg-secondary); +} + +.select-option--selected { + font-weight: 600; +} diff --git a/plugins/google-search-console/src/components/Select.tsx b/plugins/google-search-console/src/components/Select.tsx new file mode 100644 index 00000000..4510403a --- /dev/null +++ b/plugins/google-search-console/src/components/Select.tsx @@ -0,0 +1,79 @@ +import { useSelect, UseSelectSelectedItemChange } from 'downshift'; + +export interface SelectOption { + id: number | string; + title: string; +} + +interface SelectProps { + selected: SelectOption; + options: SelectOption[]; + onChange: (changes: UseSelectSelectedItemChange) => void; +} + +function itemToString(item: SelectOption | null) { + return item ? item.title : ''; +} + +export default function Select({ + selected: selectedItem, + options, + onChange, +}: SelectProps) { + const { + isOpen, + getToggleButtonProps, + getMenuProps, + highlightedIndex, + getItemProps, + } = useSelect({ + items: options, + itemToString, + selectedItem, + onSelectedItemChange: onChange, + }); + + return ( +
+
+
+ {selectedItem ? selectedItem.title : 'Select a range'} + + {isOpen ? <>↑ : <>↓} + +
+
+ +
+ ); +} diff --git a/plugins/google-search-console/src/hooks.ts b/plugins/google-search-console/src/hooks.ts index 19777d25..1dfb0f8c 100644 --- a/plugins/google-search-console/src/hooks.ts +++ b/plugins/google-search-console/src/hooks.ts @@ -194,20 +194,23 @@ function randomIntFromInterval(min: number, max: number) { return Math.floor(min + Math.random() * (max - min + 1)); } -export function useMockPerformanceResults(): { +export function useMockPerformanceResults( + siteUrl: string, + dates: string[], +): { dailyPerformance: GoogleQueryResult; queryPerformance: GoogleQueryResult; } { const getRandomData = (): number[][] => { const savedData = window.localStorage.getItem( - 'searchConsoleRandomChartData', + `searchConsoleRandomChartData_${dates.length}`, ) as string; if (savedData) { return JSON.parse(savedData); } - const randomDataGen = [...new Array(14)].map(() => { + const randomDataGen = [...new Array(dates.length)].map(() => { const clicks = randomIntFromInterval(1000, 3000); const impressions = clicks + randomIntFromInterval(1000, 3000); diff --git a/plugins/google-search-console/src/screens/Performance.tsx b/plugins/google-search-console/src/screens/Performance.tsx index 7f78c3c9..f82b0678 100644 --- a/plugins/google-search-console/src/screens/Performance.tsx +++ b/plugins/google-search-console/src/screens/Performance.tsx @@ -6,6 +6,9 @@ import { CSSProperties, useMemo, useState } from 'react'; import Loading from '../components/Loading'; import aveta from 'aveta'; import FitText from '../components/FitText'; +import Select, { SelectOption } from '../components/Select'; +import { rangeOptions } from './SiteHasIndexedSitemap'; +import { UseSelectSelectedItemChange } from 'downshift'; function dateFormatter(value: number) { const date = new Date(value * 1000); @@ -19,6 +22,8 @@ interface PerformanceProps { dailyPerformance: GoogleQueryResult; queryPerformance: GoogleQueryResult; } | null; + selectedRange: SelectOption; + onRangeChange: (changes: UseSelectSelectedItemChange) => void; } interface QueriesTableProps { @@ -111,7 +116,11 @@ const CustomTooltip = ({ const lineType = 'monotone'; -export default function Performance({ performance }: PerformanceProps) { +export default function Performance({ + performance, + selectedRange, + onRangeChange, +}: PerformanceProps) { const chartData = useMemo( () => performance?.dailyPerformance @@ -150,136 +159,155 @@ export default function Performance({ performance }: PerformanceProps) { return ( <> - {emptyPerformance ? ( -
-

Your site doesn’t have enough performance data to show yet.

-
- ) : performance?.dailyPerformance ? ( -
-
-
- setMetricFocus((currFocus) => - currFocus === 'clicks' ? null : 'clicks', - ) - } - > -
- {aveta(totalClicks)} +
+
+