From 58508eeeb81c7eebe1e3c4d6b0e4663c544749fa Mon Sep 17 00:00:00 2001 From: sophiamersmann Date: Tue, 16 Apr 2024 12:37:18 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20(entity=20selector)=20render=20draw?= =?UTF-8?q?er=20within=20the=20Grapher=20frame?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../grapher/src/controls/SettingsMenu.tsx | 1 - .../grapher/src/core/Grapher.tsx | 24 +- .../grapher/src/core/GrapherConstants.ts | 1 - .../grapher/src/core/grapher.scss | 11 +- .../src/entitySelector/EntitySelector.scss | 454 +++++++++--------- .../grapher/src/fullScreen/FullScreen.tsx | 4 - packages/@ourworldindata/grapher/src/index.ts | 1 - .../src/slideInDrawer/SlideInDrawer.scss | 57 +-- .../src/slideInDrawer/SlideInDrawer.tsx | 68 ++- site/DataPageV2.tsx | 52 +- site/GrapherPage.tsx | 2 - 11 files changed, 317 insertions(+), 358 deletions(-) diff --git a/packages/@ourworldindata/grapher/src/controls/SettingsMenu.tsx b/packages/@ourworldindata/grapher/src/controls/SettingsMenu.tsx index bc96df1c7a0..f092dae1e30 100644 --- a/packages/@ourworldindata/grapher/src/controls/SettingsMenu.tsx +++ b/packages/@ourworldindata/grapher/src/controls/SettingsMenu.tsx @@ -9,7 +9,6 @@ import { SelectionArray } from "../selection/SelectionArray" import { ChartDimension } from "../chart/ChartDimension" import { makeSelectionArray } from "../chart/ChartUtils.js" import { AxisConfig } from "../axis/AxisConfig" -import { CloseButton } from "../closeButton/CloseButton.js" import { AxisScaleToggle } from "./settings/AxisScaleToggle" import { AbsRelToggle, AbsRelToggleManager } from "./settings/AbsRelToggle" diff --git a/packages/@ourworldindata/grapher/src/core/Grapher.tsx b/packages/@ourworldindata/grapher/src/core/Grapher.tsx index d98871ad791..42d7d32da94 100644 --- a/packages/@ourworldindata/grapher/src/core/Grapher.tsx +++ b/packages/@ourworldindata/grapher/src/core/Grapher.tsx @@ -124,7 +124,6 @@ import { STATIC_EXPORT_DETAIL_SPACING, GRAPHER_LIGHT_TEXT, GRAPHER_LOADED_EVENT_NAME, - GRAPHER_DRAWER_ID, isContinentsVariableId, isPopulationVariableId, } from "../core/GrapherConstants" @@ -3163,21 +3162,14 @@ export class Grapher } @computed get showEntitySelectorAs(): GrapherWindowType { - const isLarge = this.frameBounds.width > 940 - - // show the panel in full screen mode if the grapher is large enough - if (this.isInFullScreenMode && isLarge) return GrapherWindowType.panel - - // don't use the panel or drawer if the grapher is embedded - if (this.isInIFrame || this.isEmbeddedInAnOwidPage) - return GrapherWindowType.modal - - if (isLarge) return GrapherWindowType.panel - - // if there is no empty drawer element on the page, use the modal - const hasDrawer = - document.querySelector(`nav#${GRAPHER_DRAWER_ID}`) !== null - if (!hasDrawer) return GrapherWindowType.modal + if ( + this.frameBounds.width > 940 && + // don't use the panel if the grapher is embedded + ((!this.isInIFrame && !this.isEmbeddedInAnOwidPage) || + // unless we're in full-screen mode + this.isInFullScreenMode) + ) + return GrapherWindowType.panel return this.isSemiNarrow ? GrapherWindowType.modal diff --git a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts index c882b8ab32b..4a30ab55acc 100644 --- a/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts +++ b/packages/@ourworldindata/grapher/src/core/GrapherConstants.ts @@ -4,7 +4,6 @@ export const GRAPHER_EMBEDDED_FIGURE_ATTR = "data-grapher-src" export const GRAPHER_EMBEDDED_FIGURE_CONFIG_ATTR = "data-grapher-config" export const GRAPHER_PAGE_BODY_CLASS = "StandaloneGrapherOrExplorerPage" -export const GRAPHER_DRAWER_ID = "grapher-drawer" export const GRAPHER_IS_IN_IFRAME_CLASS = "IsInIframe" export const GRAPHER_TIMELINE_CLASS = "timeline-component" export const GRAPHER_SIDE_PANEL_CLASS = "side-panel" diff --git a/packages/@ourworldindata/grapher/src/core/grapher.scss b/packages/@ourworldindata/grapher/src/core/grapher.scss index 24134673c2c..26670d1a92c 100644 --- a/packages/@ourworldindata/grapher/src/core/grapher.scss +++ b/packages/@ourworldindata/grapher/src/core/grapher.scss @@ -78,6 +78,10 @@ $zindex-controls-drawer: 150; @import "../tabs/ExpandableTabs.scss"; @import "../slideInDrawer/SlideInDrawer.scss"; @import "../sidePanel/SidePanel.scss"; + @import "../closeButton/CloseButton.scss"; + @import "../controls/RadioButton.scss"; + @import "../controls/Dropdown.scss"; + @import "../core/OverlayHeader.scss"; } // These rules are currently used elsewhere in the site. e.g. Explorers @@ -87,10 +91,6 @@ $zindex-controls-drawer: 150; @import "../sparkBars/SparkBars.scss"; @import "../fullScreen/FullScreen.scss"; @import "../../../components/src/Checkbox.scss"; -@import "../closeButton/CloseButton.scss"; -@import "../controls/RadioButton.scss"; -@import "../controls/Dropdown.scss"; -@import "../core/OverlayHeader.scss"; .grapher_dark { color: $dark-text; @@ -124,6 +124,9 @@ $zindex-controls-drawer: 150; border: 1px solid $frame-color; z-index: $zindex-chart; + // important for the slide-in drawer + overflow-x: clip; + * { box-sizing: border-box; } diff --git a/packages/@ourworldindata/grapher/src/entitySelector/EntitySelector.scss b/packages/@ourworldindata/grapher/src/entitySelector/EntitySelector.scss index 4731f3f7c83..8686cdcbcc8 100644 --- a/packages/@ourworldindata/grapher/src/entitySelector/EntitySelector.scss +++ b/packages/@ourworldindata/grapher/src/entitySelector/EntitySelector.scss @@ -1,291 +1,289 @@ -@at-root { - .entity-selector { - --padding: var(--modal-padding, 16px); +.entity-selector { + --padding: var(--modal-padding, 16px); - color: $dark-text; + color: $dark-text; - // necessary for scrolling - display: flex; - flex-direction: column; - height: 100%; - > * { - flex-shrink: 0; - } + // necessary for scrolling + display: flex; + flex-direction: column; + height: 100%; + > * { + flex-shrink: 0; + } - .scrollable { - flex: 1 1 auto; - overflow-y: auto; - width: 100%; - } + .scrollable { + flex: 1 1 auto; + overflow-y: auto; + width: 100%; + } + + .entity-selector__search-bar { + padding: 0 var(--padding) 8px var(--padding); + + .search-input { + // search icon + $svg-margin: 8px; + $svg-size: 12px; - .entity-selector__search-bar { - padding: 0 var(--padding) 8px var(--padding); + $placeholder: #a1a1a1; + $focus: 1px solid #a4b6ca; - .search-input { - // search icon - $svg-margin: 8px; - $svg-size: 12px; + position: relative; - $placeholder: #a1a1a1; - $focus: 1px solid #a4b6ca; + .search-icon { + position: absolute; + top: 50%; + left: $svg-margin; + color: $light-text; + transform: translateY(-50%); + font-size: $svg-size; + } - position: relative; + .clear { + margin: 0; + padding: 0; + background: none; + border: none; + position: absolute; + top: 50%; + right: $svg-margin; + transform: translateY(-50%); + font-size: $svg-size; + color: $dark-text; + cursor: pointer; + } - .search-icon { - position: absolute; - top: 50%; - left: $svg-margin; - color: $light-text; - transform: translateY(-50%); - font-size: $svg-size; + input[type="search"] { + @include grapher_label-2-regular; + width: 100%; + height: 32px; + border: 1px solid #e7e7e7; + padding-left: $svg-margin + $svg-size + 4px; + padding-right: $svg-margin + $svg-size + 4px; + border-radius: 4px; + background: #fff; + + // style placeholder text in search input + &::placeholder { + color: $placeholder; + opacity: 1; /* Firefox */ + } + &:-ms-input-placeholder { + color: $placeholder; + } + &::-ms-input-placeholder { + color: $placeholder; } - .clear { - margin: 0; - padding: 0; - background: none; + // style focus state + &:focus { + outline: none; + border: $focus; + } + &:focus:not(:focus-visible) { border: none; - position: absolute; - top: 50%; - right: $svg-margin; - transform: translateY(-50%); - font-size: $svg-size; - color: $dark-text; - cursor: pointer; + } + &:focus-visible { + border: $focus; } - input[type="search"] { - @include grapher_label-2-regular; - width: 100%; - height: 32px; - border: 1px solid #e7e7e7; - padding-left: $svg-margin + $svg-size + 4px; - padding-right: $svg-margin + $svg-size + 4px; - border-radius: 4px; - background: #fff; - - // style placeholder text in search input - &::placeholder { - color: $placeholder; - opacity: 1; /* Firefox */ - } - &:-ms-input-placeholder { - color: $placeholder; - } - &::-ms-input-placeholder { - color: $placeholder; - } - - // style focus state - &:focus { - outline: none; - border: $focus; - } - &:focus:not(:focus-visible) { - border: none; - } - &:focus-visible { - border: $focus; - } - - &::-webkit-search-cancel-button { - -webkit-appearance: none; - } + &::-webkit-search-cancel-button { + -webkit-appearance: none; } + } - &.search-input--empty { - input[type="search"] { - padding-right: 8px; - } + &.search-input--empty { + input[type="search"] { + padding-right: 8px; } } } + } - .entity-selector__sort-bar { - padding: 0 var(--padding); - display: flex; - align-items: center; - margin-top: 8px; - margin-bottom: 16px; + .entity-selector__sort-bar { + padding: 0 var(--padding); + display: flex; + align-items: center; + margin-top: 8px; + margin-bottom: 16px; - .grapher-dropdown { - flex-grow: 1; - } + .grapher-dropdown { + flex-grow: 1; + } - .label { - flex-shrink: 0; - margin-right: 8px; - color: $dark-text; - } + .label { + flex-shrink: 0; + margin-right: 8px; + color: $dark-text; + } - button.sort { - flex-shrink: 0; - margin-left: 16px; + button.sort { + flex-shrink: 0; + margin-left: 16px; - $size: 32px; + $size: 32px; - display: flex; - align-items: center; - justify-content: center; + display: flex; + align-items: center; + justify-content: center; - position: relative; - height: $size; - width: $size; - padding: 7px; + position: relative; + height: $size; + width: $size; + padding: 7px; - color: $dark-text; - background: #f2f2f2; - border: none; - border-radius: 4px; + color: $dark-text; + background: #f2f2f2; + border: none; + border-radius: 4px; - svg { - height: 14px; - width: 14px; - } + svg { + height: 14px; + width: 14px; + } - &:hover:not(:disabled) { - background: #e7e7e7; - cursor: pointer; - } + &:hover:not(:disabled) { + background: #e7e7e7; + cursor: pointer; + } - &:active:not(:disabled) { - color: #1d3d63; - background: #dbe5f0; - border: 1px solid #dbe5f0; - } + &:active:not(:disabled) { + color: #1d3d63; + background: #dbe5f0; + border: 1px solid #dbe5f0; + } - &:disabled { - background: #f2f2f2; - color: #a1a1a1; - } + &:disabled { + background: #f2f2f2; + color: #a1a1a1; } } + } - .entity-selector__content { - $row-border: 1px solid #f2f2f2; + .entity-selector__content { + $row-border: 1px solid #f2f2f2; - margin: 0 var(--padding) 8px var(--padding); + margin: 0 var(--padding) 8px var(--padding); - .entity-section + .entity-section { - margin-top: 16px; - } + .entity-section + .entity-section { + margin-top: 16px; + } - .entity-section__title { - letter-spacing: 0.01em; - margin-bottom: 8px; + .entity-section__title { + letter-spacing: 0.01em; + margin-bottom: 8px; + } + + .entity-search-results { + margin-top: 8px; + } + + .selectable-entity { + padding: 9px 8px 9px 16px; + display: flex; + justify-content: space-between; + position: relative; + cursor: pointer; + + .value { + color: #a1a1a1; + white-space: nowrap; + margin-left: 8px; } - .entity-search-results { - margin-top: 8px; + .bar { + position: absolute; + top: 0; + left: 0; + height: 100%; + background: #ebeef2; + z-index: -1; } - .selectable-entity { - padding: 9px 8px 9px 16px; + .label-with-location-icon { display: flex; - justify-content: space-between; - position: relative; - cursor: pointer; + align-items: center; - .value { - color: #a1a1a1; - white-space: nowrap; + svg { margin-left: 8px; - } - - .bar { - position: absolute; - top: 0; - left: 0; - height: 100%; - background: #ebeef2; - z-index: -1; - } - - .label-with-location-icon { - display: flex; - align-items: center; - - svg { - margin-left: 8px; - font-size: 0.9em; - color: #a1a1a1; + font-size: 0.9em; + color: #a1a1a1; - // hide focus outline when clicked - &:focus:not(:focus-visible) { - outline: none; - } + // hide focus outline when clicked + &:focus:not(:focus-visible) { + outline: none; } } } + } - .animated-entity { - position: relative; - z-index: 0; - background: #fff; - - &.most-recently-selected { - z-index: 1; - } - } + .animated-entity { + position: relative; + z-index: 0; + background: #fff; - ul { - margin: 0; - padding: 0; - list-style-type: none; + &.most-recently-selected { + z-index: 1; } + } - li + li .selectable-entity { - border-top: $row-border; - } + ul { + margin: 0; + padding: 0; + list-style-type: none; + } - li:first-of-type .selectable-entity { - border-top: $row-border; - } + li + li .selectable-entity { + border-top: $row-border; + } - li:last-of-type .selectable-entity { - border-bottom: $row-border; - } + li:first-of-type .selectable-entity { + border-top: $row-border; } - .entity-selector__footer { - background-color: #fff; - width: 100%; - border-radius: 4px; - z-index: 2; + li:last-of-type .selectable-entity { + border-bottom: $row-border; + } + } - display: flex; - align-items: center; - justify-content: space-between; - padding: 16px var(--padding); - box-shadow: 0px -4px 8px 0px rgba(0, 0, 0, 0.04); + .entity-selector__footer { + background-color: #fff; + width: 100%; + border-radius: 4px; + z-index: 2; - .footer__selected { - font-size: 0.6875rem; - font-weight: 500; - letter-spacing: 0.06em; - text-transform: uppercase; + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px var(--padding); + box-shadow: 0px -4px 8px 0px rgba(0, 0, 0, 0.04); - display: flex; - flex-wrap: wrap; - column-gap: 4px; - } + .footer__selected { + font-size: 0.6875rem; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; - button { - background: none; - border: none; - color: $dark-text; - font-size: 0.8125rem; - font-weight: 500; - letter-spacing: 0.01em; - text-decoration-line: underline; - text-underline-offset: 3px; - cursor: pointer; + display: flex; + flex-wrap: wrap; + column-gap: 4px; + } - &:disabled { - color: #c6c6c6; - text-decoration: none; - cursor: default; - } + button { + background: none; + border: none; + color: $dark-text; + font-size: 0.8125rem; + font-weight: 500; + letter-spacing: 0.01em; + text-decoration-line: underline; + text-underline-offset: 3px; + cursor: pointer; + + &:disabled { + color: #c6c6c6; + text-decoration: none; + cursor: default; } } } diff --git a/packages/@ourworldindata/grapher/src/fullScreen/FullScreen.tsx b/packages/@ourworldindata/grapher/src/fullScreen/FullScreen.tsx index 185aff3255f..f6ab10ba05d 100644 --- a/packages/@ourworldindata/grapher/src/fullScreen/FullScreen.tsx +++ b/packages/@ourworldindata/grapher/src/fullScreen/FullScreen.tsx @@ -2,7 +2,6 @@ import React from "react" import { action } from "mobx" import { observer } from "mobx-react" import { BodyDiv } from "../bodyDiv/BodyDiv" -import { GRAPHER_DRAWER_ID } from "../core/GrapherConstants" @observer export class FullScreen extends React.Component<{ @@ -13,13 +12,10 @@ export class FullScreen extends React.Component<{ content: React.RefObject = React.createRef() @action.bound onDocumentClick(e: React.MouseEvent): void { - const drawer = document.getElementById(GRAPHER_DRAWER_ID) if ( this.content?.current && // check if the click was outside of the modal !this.content.current.contains(e.target as Node) && - // check if the click was outside of the drawer - (!drawer || !drawer.contains(e.target as Node)) && // check that the target is still mounted to the document; we also get click events on nodes that have since been removed by React document.contains(e.target as Node) ) diff --git a/packages/@ourworldindata/grapher/src/index.ts b/packages/@ourworldindata/grapher/src/index.ts index edd0dc3256e..98bf29e8441 100644 --- a/packages/@ourworldindata/grapher/src/index.ts +++ b/packages/@ourworldindata/grapher/src/index.ts @@ -12,7 +12,6 @@ export { GRAPHER_EMBEDDED_FIGURE_CONFIG_ATTR, GRAPHER_PAGE_BODY_CLASS, GRAPHER_IS_IN_IFRAME_CLASS, - GRAPHER_DRAWER_ID, DEFAULT_GRAPHER_WIDTH, DEFAULT_GRAPHER_HEIGHT, STATIC_EXPORT_DETAIL_SPACING, diff --git a/packages/@ourworldindata/grapher/src/slideInDrawer/SlideInDrawer.scss b/packages/@ourworldindata/grapher/src/slideInDrawer/SlideInDrawer.scss index 84b40363286..7de5ab99a67 100644 --- a/packages/@ourworldindata/grapher/src/slideInDrawer/SlideInDrawer.scss +++ b/packages/@ourworldindata/grapher/src/slideInDrawer/SlideInDrawer.scss @@ -1,37 +1,26 @@ -@at-root { - nav#grapher-drawer { - $light-stroke: #e7e7e7; - - $active-fill: #dbe5f0; - $hover-fill: #f2f2f2; - - $medium: 400; - $bold: 700; - $lato: $sans-serif-font-stack; - - .grapher-drawer-contents { - position: fixed; - right: 0; - top: 0; - width: 300px; - height: 100vh; - z-index: $zindex-controls-drawer; - background: white; - } - - .grapher-drawer-backdrop { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.2); - z-index: $zindex-controls-backdrop; - } +.drawer { + .drawer-contents { + position: fixed; + right: 0; + top: 0; + width: 300px; + height: 100%; + z-index: $zindex-controls-drawer; + background: white; + } + + .drawer-backdrop { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.2); + z-index: $zindex-controls-backdrop; } } -@keyframes grapher-drawer-backdrop-enter { +@keyframes drawer-backdrop-enter { 0% { opacity: 0; } @@ -40,7 +29,7 @@ } } -@keyframes grapher-drawer-backdrop-exit { +@keyframes drawer-backdrop-exit { 0% { opacity: 1; } @@ -49,7 +38,7 @@ } } -@keyframes grapher-drawer-contents-enter { +@keyframes drawer-contents-enter { 0% { transform: translate(301px, 0); } @@ -58,7 +47,7 @@ } } -@keyframes grapher-drawer-contents-exit { +@keyframes drawer-contents-exit { 0% { transform: translate(0, 0); } diff --git a/packages/@ourworldindata/grapher/src/slideInDrawer/SlideInDrawer.tsx b/packages/@ourworldindata/grapher/src/slideInDrawer/SlideInDrawer.tsx index 2a17cdee73b..1bd878fa2a7 100644 --- a/packages/@ourworldindata/grapher/src/slideInDrawer/SlideInDrawer.tsx +++ b/packages/@ourworldindata/grapher/src/slideInDrawer/SlideInDrawer.tsx @@ -1,8 +1,6 @@ import React from "react" -import { createPortal } from "react-dom" import { computed, action, observable } from "mobx" import { observer } from "mobx-react" -import { GRAPHER_DRAWER_ID } from "../core/GrapherConstants" export const DrawerContext = React.createContext<{ toggleDrawerVisibility?: () => void @@ -15,7 +13,7 @@ export class SlideInDrawer extends React.Component<{ children: React.ReactNode }> { @observable.ref visible: boolean = this.props.active // true while the drawer is active and during enter/exit transitions - contentRef: React.RefObject = React.createRef() + drawerRef: React.RefObject = React.createRef() componentDidMount(): void { document.addEventListener("keydown", this.onDocumentKeyDown) @@ -39,8 +37,8 @@ export class SlideInDrawer extends React.Component<{ @action.bound onDocumentClick(e: MouseEvent): void { if ( this.active && - this.contentRef?.current && - !this.contentRef.current.contains(e.target as Node) && + this.drawerRef?.current && + !this.drawerRef.current.contains(e.target as Node) && document.contains(e.target as Node) ) this.toggleVisibility() @@ -49,7 +47,7 @@ export class SlideInDrawer extends React.Component<{ @action.bound toggleVisibility(e?: React.MouseEvent): void { this.props.toggle() if (this.active) this.visible = true - this.drawer?.classList.toggle("active", this.active) + this.drawerRef?.current?.classList.toggle("active", this.active) e?.stopPropagation() } @@ -61,47 +59,39 @@ export class SlideInDrawer extends React.Component<{ return this.props.active } - @computed private get drawer(): Element | null { - return document.querySelector(`nav#${GRAPHER_DRAWER_ID}`) - } - private animationFor(selector: string): { animation: string } { const phase = this.active ? "enter" : "exit" return { animation: `${selector}-${phase} 333ms` } } - @computed get drawerContents(): JSX.Element { - return ( -
-
-
- +
+
- {this.props.children} - + + {this.props.children} + +
-
- ) - } - - render(): JSX.Element | null { - const { visible, drawer, active } = this - - if (drawer && (active || visible)) { - return createPortal(this.drawerContents, drawer) + ) } return null diff --git a/site/DataPageV2.tsx b/site/DataPageV2.tsx index 3ab628582f8..b74b503459d 100644 --- a/site/DataPageV2.tsx +++ b/site/DataPageV2.tsx @@ -2,7 +2,6 @@ import { getVariableDataRoute, getVariableMetadataRoute, GrapherProgrammaticInterface, - GRAPHER_DRAWER_ID, } from "@ourworldindata/grapher" import { uniq, @@ -139,33 +138,30 @@ export const DataPageV2 = (props: {
- <> - -