+
+{% include 'metadata/labels.html' %}
+{% include 'metadata/routes.html' %}
-
+
-
@@ -140,10 +107,6 @@
{% endblock %}
-
{# Compile this down (or manually implement). Used only for nav bar so far #}
diff --git a/server/templates/custom_dc/custom/homepage.html b/server/templates/custom_dc/custom/homepage.html
index 3905829c8c..f7c39798d4 100644
--- a/server/templates/custom_dc/custom/homepage.html
+++ b/server/templates/custom_dc/custom/homepage.html
@@ -1,5 +1,5 @@
{#
- Copyright 2023 Google LLC
+ Copyright 2025 Google LLC
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,9 +18,18 @@
{% set main_id = 'homepage' %}
{% set page_id = 'page-homepage' %}
+{% set title = 'Home' %}
+{% macro external_icon() -%}
+
+
+{%- endmacro %}
+
+{% block head %}
+
+
+{% endblock %}
{% block content %}
-
-
Home Page
-
-{% endblock %}
\ No newline at end of file
+
+
+{% endblock %}
diff --git a/server/webdriver/cdc_tests/nl_test.py b/server/webdriver/cdc_tests/nl_test.py
index 0b7bb1a526..b910a0f7f6 100644
--- a/server/webdriver/cdc_tests/nl_test.py
+++ b/server/webdriver/cdc_tests/nl_test.py
@@ -35,4 +35,4 @@ def test_ensure_inline_search_bar_is_displayed(self):
self.assertTrue(
find_elem(self.driver,
by=By.CSS_SELECTOR,
- value='.explore-container .search-bar').is_displayed())
+ value='.explore-container .search-section').is_displayed())
diff --git a/server/webdriver/tests/homepage_test.py b/server/webdriver/tests/homepage_test.py
index af70bd0d6c..a14f6956eb 100644
--- a/server/webdriver/tests/homepage_test.py
+++ b/server/webdriver/tests/homepage_test.py
@@ -115,8 +115,10 @@ def test_homepage_autocomplete_places(self):
search_box_input.send_keys("California")
WebDriverWait(self.driver, self.TIMEOUT_SEC).until(
EC.presence_of_element_located(
- (By.CLASS_NAME, 'search-input-result-section')))
- results = find_elems(self.driver, value='search-input-result-section')
+ (By.CSS_SELECTOR, "[data-testid='search-input-result-section']")))
+ results = find_elems(self.driver,
+ by=By.CSS_SELECTOR,
+ value="[data-testid='search-input-result-section']")
self.assertGreater(len(results), 0)
search_box_input.clear()
@@ -124,10 +126,11 @@ def test_homepage_autocomplete_places(self):
search_box_input.send_keys("Population of Calif")
WebDriverWait(self.driver, self.TIMEOUT_SEC).until(
EC.presence_of_element_located(
- (By.CLASS_NAME, 'search-input-result-section')))
- first_result = find_elem(self.driver,
- by=By.CLASS_NAME,
- value='search-input-result-section')
+ (By.CSS_SELECTOR, "[data-testid='search-input-result-section']")))
+ first_result = find_elem(
+ self.driver,
+ by=By.CSS_SELECTOR,
+ value="[data-testid='search-input-result-section']")
self.assertIn("California", first_result.text)
search_box_input.clear()
@@ -147,9 +150,16 @@ def test_homepage_autocomplete_statvars(self):
# Test autocomplete for stat vars
search_box_input.send_keys("gdp")
- initial_results = find_elems(self.driver,
- value='search-input-result-section')
- self.assertEqual(len(initial_results), 6)
+ # Wait for the results to appear before counting
+ WebDriverWait(self.driver, self.TIMEOUT_SEC).until(
+ EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "[data-testid='search-input-result-section']")))
+ initial_results = find_elems(
+ self.driver,
+ by=By.CSS_SELECTOR,
+ value="[data-testid='search-input-result-section']")
+ # We are now only counting actual search results (and not the load more)
+ self.assertEqual(len(initial_results), 4)
search_box_input.clear()
# Sleeping this way is not ideal.
# However, without it, it does not always clear the search box,
@@ -159,9 +169,16 @@ def test_homepage_autocomplete_statvars(self):
# Test load more stat var results
search_box_input.send_keys("Household Income")
- initial_results = find_elems(self.driver,
- value='search-input-result-section')
- self.assertEqual(len(initial_results), 6)
+ # Wait for the results to appear before counting
+ WebDriverWait(self.driver, self.TIMEOUT_SEC).until(
+ EC.presence_of_element_located(
+ (By.CSS_SELECTOR, "[data-testid='search-input-result-section']")))
+ initial_results = find_elems(
+ self.driver,
+ by=By.CSS_SELECTOR,
+ value="[data-testid='search-input-result-section']")
+ # We are now only counting actual search results (and not the load more)
+ self.assertEqual(len(initial_results), 4)
# Click on the "Load More" button to fetch more results
load_more_button = find_elem(self.driver,
@@ -173,6 +190,12 @@ def test_homepage_autocomplete_statvars(self):
# Wait for new results to load, which means more result sections appear
WebDriverWait(self.driver, self.TIMEOUT_SEC).until(lambda d: len(
- find_elems(d, value='search-input-result-section')) > initial_count)
- final_results = find_elems(self.driver, value='search-input-result-section')
- self.assertGreater(len(final_results), initial_count)
+ find_elems(d,
+ by=By.CSS_SELECTOR,
+ value="[data-testid='search-input-result-section']")) >
+ initial_count)
+ final_results = find_elems(
+ self.driver,
+ by=By.CSS_SELECTOR,
+ value="[data-testid='search-input-result-section']")
+ self.assertGreater(len(final_results), initial_count)
\ No newline at end of file
diff --git a/static/custom_dc/custom/logo.png b/static/custom_dc/custom/logo.png
deleted file mode 100644
index a6d00ee881..0000000000
Binary files a/static/custom_dc/custom/logo.png and /dev/null differ
diff --git a/static/custom_dc/custom/logo.svg b/static/custom_dc/custom/logo.svg
new file mode 100644
index 0000000000..9c5c20b829
--- /dev/null
+++ b/static/custom_dc/custom/logo.svg
@@ -0,0 +1,23 @@
+
\ No newline at end of file
diff --git a/static/js/apps/explore/search_section.tsx b/static/js/apps/explore/search_section.tsx
index 9e1edbc3ad..a1ac58daa7 100644
--- a/static/js/apps/explore/search_section.tsx
+++ b/static/js/apps/explore/search_section.tsx
@@ -55,23 +55,25 @@ export function SearchSection(props: {
);
return (
- {
- triggerGAEvent(GA_EVENT_NL_SEARCH, {
- [GA_PARAM_QUERY]: q,
- [GA_PARAM_SOURCE]: GA_VALUE_PAGE_EXPLORE,
- });
- updateHash({
- [URL_HASH_PARAMS.QUERY]: q,
- [URL_HASH_PARAMS.PLACE]: "",
- [URL_HASH_PARAMS.TOPIC]: "",
- [URL_HASH_PARAMS.CLIENT]: CLIENT_TYPES.QUERY,
- });
- }}
- initialValue={props.query}
- shouldAutoFocus={false}
- feedbackLink={DEVELOPER_MODE ? feedbackLink : undefined}
- />
+
);
}
diff --git a/static/js/apps/homepage/app.tsx b/static/js/apps/homepage/app.tsx
index fbb7617dd8..0659b1a6a5 100644
--- a/static/js/apps/homepage/app.tsx
+++ b/static/js/apps/homepage/app.tsx
@@ -22,6 +22,7 @@ import { ThemeProvider } from "@emotion/react";
import React, { ReactElement } from "react";
import Partners from "../../components/content/partners";
+import { Tools } from "../../components/content/tools";
import { Section } from "../../components/elements/layout/section";
import { Separator } from "../../components/elements/layout/separator";
import { Link } from "../../components/elements/link_chip";
@@ -36,7 +37,6 @@ import theme from "../../theme/theme";
import { BuildYourOwn } from "./components/build_your_own";
import { HomeHero } from "./components/home_hero";
import { SampleQuestions } from "./components/sample_questions";
-import { Tools } from "./components/tools";
import { UnitedNations } from "./components/united_nations";
interface AppProps {
@@ -77,7 +77,16 @@ export function App({
-
+
+
Data Commons tools
+
+ Data Commons offers data exploration tools and cloud-based APIs to
+ access and integrate cleaned datasets.
+
+
diff --git a/static/js/apps/homepage/components/tools.tsx b/static/js/apps/homepage/components/tools.tsx
deleted file mode 100644
index 49d948b098..0000000000
--- a/static/js/apps/homepage/components/tools.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-/**
- * Copyright 2024 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-/**
- * A component that renders the tools section of the home page.
- */
-
-/** @jsxImportSource @emotion/react */
-
-import { css, useTheme } from "@emotion/react";
-import React, { ReactElement } from "react";
-
-import { Download } from "../../../components/elements/icons/download";
-import { IntegrationInstructions } from "../../../components/elements/icons/integration_instructions";
-import { Public } from "../../../components/elements/icons/public";
-import { ScatterPlot } from "../../../components/elements/icons/scatter_plot";
-import { Timeline } from "../../../components/elements/icons/timeline";
-import { LinkIconBox } from "../../../components/elements/link_icon_box";
-import { resolveHref } from "../../base/utilities/utilities";
-
-interface ToolsProps {
- //the routes dictionary - this is used to convert routes to resolved urls
- routes: Record;
-}
-
-export const Tools = ({ routes }: ToolsProps): ReactElement => {
- const theme = useTheme();
- return (
- <>
-
-
- Data Commons tools
-
-
- Data Commons offers data exploration tools and cloud-based APIs to
- access and integrate cleaned datasets.
-
- >
- );
-};
diff --git a/static/js/apps/homepage/custom_dc_app.tsx b/static/js/apps/homepage/custom_dc_app.tsx
new file mode 100644
index 0000000000..cd0717e33f
--- /dev/null
+++ b/static/js/apps/homepage/custom_dc_app.tsx
@@ -0,0 +1,66 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Main component for the homepage.
+ */
+
+/** @jsxImportSource @emotion/react */
+
+import { CSSObject, ThemeProvider } from "@emotion/react";
+import React, { ReactElement } from "react";
+
+import { Search } from "../../components/content/search";
+import { Tools } from "../../components/content/tools";
+import { Section } from "../../components/elements/layout/section";
+import { Routes } from "../../shared/types/base";
+import theme from "../../theme/theme";
+
+interface AppProps {
+ routes: Routes;
+}
+
+/**
+ * Default Custom DC application container
+ */
+export function App({ routes }: AppProps): ReactElement {
+ return (
+
+
+
+
Search Your Data Commons
+
+
+
+
+
+
Your Data Commons tools
+
({
+ color: theme.colors.text.tertiary.base,
+ })}
+ >
+ Your Data Commons comes with tools to browse the available datasets
+
+
+
+
+ );
+}
diff --git a/static/js/apps/homepage/main_custom_dc.ts b/static/js/apps/homepage/main_custom_dc.ts
index 9bc7630a3d..89f68501da 100644
--- a/static/js/apps/homepage/main_custom_dc.ts
+++ b/static/js/apps/homepage/main_custom_dc.ts
@@ -13,18 +13,33 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
/**
- * Entrypoint file for homepage.
+ * Entry point for the home page.
*/
+
import React from "react";
import ReactDOM from "react-dom";
-import { App } from "./app";
+import { loadLocaleData } from "../../i18n/i18n";
+import { extractRoutes } from "../base/utilities/utilities";
+import { App } from "./custom_dc_app";
window.addEventListener("load", (): void => {
- // Initialize search box.
- ReactDOM.render(
- React.createElement(App),
- document.getElementById("search-container")
+ loadLocaleData("en", [import("../../i18n/compiled-lang/en/units.json")]).then(
+ () => {
+ renderPage();
+ }
);
});
+
+function renderPage(): void {
+ const routes = extractRoutes();
+
+ ReactDOM.render(
+ React.createElement(App, {
+ routes,
+ }),
+ document.getElementById("app-container")
+ );
+}
diff --git a/static/js/components/content/search.tsx b/static/js/components/content/search.tsx
new file mode 100644
index 0000000000..d517627632
--- /dev/null
+++ b/static/js/components/content/search.tsx
@@ -0,0 +1,81 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Search component for arbitrary embedding on a page (such as the home page).
+ */
+
+/** @jsxImportSource @emotion/react */
+
+import { css } from "@emotion/react";
+import React, { Children, ReactElement, ReactNode } from "react";
+
+import { localizeLink } from "../../i18n/i18n";
+import theme from "../../theme/theme";
+import { NlSearchBar } from "../nl_search_bar";
+
+export interface SearchProps {
+ // Custom class name for the outer
.
+ className?: string;
+ // Optional Markdown/JSX shown as an introduction above the search bar.
+ children?: ReactNode;
+}
+
+export function Search({ className, children }: SearchProps): ReactElement {
+ const hasIntro = Children.count(children) > 0;
+
+ return (
+