diff --git a/apps/builder/app/builder/features/address-bar.stories.tsx b/apps/builder/app/builder/features/address-bar.stories.tsx
new file mode 100644
index 0000000000..a023c6a452
--- /dev/null
+++ b/apps/builder/app/builder/features/address-bar.stories.tsx
@@ -0,0 +1,98 @@
+import { computed } from "nanostores";
+import type { Meta, StoryFn } from "@storybook/react";
+import { Box, Text, theme } from "@webstudio-is/design-system";
+import { AddressBarPopover } from "./address-bar";
+import {
+ $dataSourceVariables,
+ $dataSources,
+ $pages,
+ $selectedPage,
+ $selectedPageId,
+} from "~/shared/nano-states";
+import { useStore } from "@nanostores/react";
+
+$dataSources.set(
+ new Map([
+ [
+ "systemId",
+ {
+ id: "systemId",
+ scopeInstanceId: "rootInstanceId",
+ name: "system",
+ type: "parameter",
+ },
+ ],
+ ])
+);
+
+$pages.set({
+ folders: [
+ {
+ id: "rootId",
+ name: "",
+ slug: "",
+ children: ["homeId", "dynamicId"],
+ },
+ ],
+ homePage: {
+ id: "homeId",
+ path: "",
+ name: "",
+ title: "",
+ meta: {},
+ rootInstanceId: "",
+ systemDataSourceId: "",
+ },
+ pages: [
+ {
+ id: "dynamicId",
+ path: "/blog/:date/post/:slug",
+ name: "",
+ title: "",
+ meta: {},
+ rootInstanceId: "rootInstanceId",
+ systemDataSourceId: "systemId",
+ },
+ ],
+});
+
+$selectedPageId.set("dynamicId");
+
+const $selectedPageSystem = computed(
+ [$selectedPage, $dataSourceVariables],
+ (selectedPage, dataSourceVariables) => {
+ if (selectedPage === undefined) {
+ return {};
+ }
+ return dataSourceVariables.get(selectedPage.systemDataSourceId);
+ }
+);
+
+const SystemInspect = () => {
+ const system = useStore($selectedPageSystem);
+ return (
+
+ {JSON.stringify(system, null, 2)}
+
+ );
+};
+
+export default {
+ title: "Builder/Address Bar",
+ component: AddressBarPopover,
+} satisfies Meta;
+
+export const AddressBar: StoryFn = () => (
+ <>
+
+
+
+
+ >
+);
diff --git a/apps/builder/app/builder/features/address-bar.tsx b/apps/builder/app/builder/features/address-bar.tsx
index ab38b85bed..dbf40ea4bb 100644
--- a/apps/builder/app/builder/features/address-bar.tsx
+++ b/apps/builder/app/builder/features/address-bar.tsx
@@ -1,6 +1,12 @@
import { computed } from "nanostores";
import { useStore } from "@nanostores/react";
-import { useEffect, useId, useState, type ComponentProps } from "react";
+import {
+ forwardRef,
+ useEffect,
+ useRef,
+ useState,
+ type ComponentProps,
+} from "react";
import {
Flex,
InputField,
@@ -61,24 +67,6 @@ const $selectedPagePathParams = computed(
}
);
-const updatePathParam = (name: string, value: string) => {
- const pathParams = $selectedPagePathParams.get();
- const path = $selectedPagePath.get();
- const tokens = tokenizePathnamePattern(path);
- // delete stale fields
- const newParams: Record = {};
- for (const token of tokens) {
- if (token.type === "param") {
- newParams[token.name] = pathParams?.[token.name] ?? "";
- }
- }
- newParams[name] = value;
- const page = $selectedPage.get();
- if (page) {
- updateSystem(page, { params: newParams });
- }
-};
-
const useCopyUrl = (pageUrl: string) => {
const [copyState, setCopyState] = useState<"copy" | "copied">("copy");
// reset copied state after 2 seconds
@@ -113,13 +101,14 @@ const useCopyUrl = (pageUrl: string) => {
};
};
-const AddressBar = () => {
- const id = useId();
+const AddressBar = forwardRef((_props, ref) => {
const publishedOrigin = useStore($publishedOrigin);
const path = useStore($selectedPagePath);
- const pathParams = useStore($selectedPagePathParams);
+ const [pathParams, setPathParams] = useState(
+ () => $selectedPagePathParams.get() ?? {}
+ );
const tokens = tokenizePathnamePattern(path);
- const compiledPath = compilePathnamePattern(tokens, pathParams ?? {});
+ const compiledPath = compilePathnamePattern(tokens, pathParams);
const { tooltipProps, buttonProps } = useCopyUrl(
`${publishedOrigin}${compiledPath}`
);
@@ -127,7 +116,7 @@ const AddressBar = () => {
const errors = new Map();
for (const token of tokens) {
if (token.type === "param") {
- const value = (pathParams?.[token.name] ?? "").trim();
+ const value = (pathParams[token.name] ?? "").trim();
if (value === "" && token.optional === false) {
errors.set(token.name, `"${token.name}" is required`);
}
@@ -141,46 +130,77 @@ const AddressBar = () => {
}
return (
-
-
-
- {tokens.map((token, index) => {
- if (token.type === "fragment") {
- return token.value;
- }
- if (token.type === "param") {
- return (
-
- updatePathParam(token.name, event.target.value)
- }
- />
- );
- }
- token satisfies never;
- })}
-
+
+
+
);
-};
+});
export const AddressBarPopover = () => {
const [isOpen, setIsOpen] = useState(false);
const path = useStore($selectedPagePath);
const publishedOrigin = useStore($publishedOrigin);
const { tooltipProps, buttonProps } = useCopyUrl(`${publishedOrigin}${path}`);
+ const formRef = useRef(null);
// show only copy button when path is static
if (isPathnamePattern(path) === false) {
@@ -196,7 +216,15 @@ export const AddressBarPopover = () => {
}
return (
-
+ {
+ setIsOpen(newIsOpen);
+ if (newIsOpen === false) {
+ formRef.current?.requestSubmit();
+ }
+ }}
+ >
@@ -209,7 +237,7 @@ export const AddressBarPopover = () => {
collisionPadding={4}
align="start"
>
-
+