@webflow/webflow-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/.eslintrc.js +5 -0
  2. package/.turbo/turbo-build.log +13 -0
  3. package/.turbo/turbo-lint.log +5 -0
  4. package/CHANGELOG.md +7 -0
  5. package/dist/devlink-engine/_Builtin/BackgroundVideo.d.ts +25 -0
  6. package/dist/devlink-engine/_Builtin/BackgroundVideo.js +38 -0
  7. package/dist/devlink-engine/_Builtin/Basic.d.ts +740 -0
  8. package/dist/devlink-engine/_Builtin/Basic.js +100 -0
  9. package/dist/devlink-engine/_Builtin/Dropdown.d.ts +38 -0
  10. package/dist/devlink-engine/_Builtin/Dropdown.js +47 -0
  11. package/dist/devlink-engine/_Builtin/Facebook.d.ts +11 -0
  12. package/dist/devlink-engine/_Builtin/Facebook.js +22 -0
  13. package/dist/devlink-engine/_Builtin/Form.d.ts +8401 -0
  14. package/dist/devlink-engine/_Builtin/Form.js +316 -0
  15. package/dist/devlink-engine/_Builtin/Map.d.ts +14 -0
  16. package/dist/devlink-engine/_Builtin/Map.js +71 -0
  17. package/dist/devlink-engine/_Builtin/Navbar.d.ts +53 -0
  18. package/dist/devlink-engine/_Builtin/Navbar.js +240 -0
  19. package/dist/devlink-engine/_Builtin/Slider.d.ts +876 -0
  20. package/dist/devlink-engine/_Builtin/Slider.js +304 -0
  21. package/dist/devlink-engine/_Builtin/Tabs.d.ts +34 -0
  22. package/dist/devlink-engine/_Builtin/Tabs.js +84 -0
  23. package/dist/devlink-engine/_Builtin/Twitter.d.ts +17 -0
  24. package/dist/devlink-engine/_Builtin/Twitter.js +41 -0
  25. package/dist/devlink-engine/_Builtin/Typography.d.ts +291 -0
  26. package/dist/devlink-engine/_Builtin/Typography.js +38 -0
  27. package/dist/devlink-engine/_Builtin/Video.d.ts +8 -0
  28. package/dist/devlink-engine/_Builtin/Video.js +8 -0
  29. package/dist/devlink-engine/_Builtin/YouTubeVideo.d.ts +15 -0
  30. package/dist/devlink-engine/_Builtin/YouTubeVideo.js +27 -0
  31. package/dist/devlink-engine/_Builtin/index.d.ts +13 -0
  32. package/dist/devlink-engine/_Builtin/index.js +13 -0
  33. package/dist/devlink-engine/interactions.d.ts +13 -0
  34. package/dist/devlink-engine/interactions.js +86 -0
  35. package/dist/devlink-engine/types.d.ts +32 -0
  36. package/dist/devlink-engine/types.js +1 -0
  37. package/dist/devlink-engine/utils.d.ts +66 -0
  38. package/dist/devlink-engine/utils.js +133 -0
  39. package/dist/index.js +197591 -0
  40. package/package.json +30 -0
  41. package/src/index.ts +33 -0
  42. package/tsconfig.json +5 -0
  43. package/tsup.config.ts +6 -0
@@ -0,0 +1,316 @@
1
+ import React from "react";
2
+ import { loadScript } from "../utils";
3
+ export function FormWrapper({ className = "", state: initialState = "normal", onSubmit, children, ...props }) {
4
+ const [state, setState] = React.useState(initialState);
5
+ return React.createElement("div", {
6
+ className: className + " w-form",
7
+ ...props,
8
+ }, React.Children.map(children, (child) => {
9
+ if (child.type === FormForm) {
10
+ return React.cloneElement(child, {
11
+ ...child.props,
12
+ // @ts-ignore
13
+ onSubmit: (e) => {
14
+ try {
15
+ e.preventDefault();
16
+ // If grecaptcha is defined, it means there is a ReCaptcha in the form
17
+ if (window.grecaptcha) {
18
+ // If the response is empty, it means the user didn't check the box
19
+ if (!window.grecaptcha?.getResponse()) {
20
+ alert(`Please confirm you’re not a robot.`);
21
+ return;
22
+ }
23
+ }
24
+ if (onSubmit) {
25
+ onSubmit(e);
26
+ setState("success");
27
+ }
28
+ }
29
+ catch (err) {
30
+ /**
31
+ * Capture the error to correctly set the state, but rethrow it
32
+ * in case another error handling method is used above up in
33
+ * the tree (e.g. Error Boundaries)
34
+ */
35
+ setState("error");
36
+ throw err;
37
+ }
38
+ },
39
+ style: {
40
+ display: state == "normal" || state == "error" ? "block" : "none",
41
+ },
42
+ });
43
+ }
44
+ if (child.type === FormSuccessMessage) {
45
+ return React.cloneElement(child, {
46
+ ...child.props,
47
+ // @ts-ignore
48
+ style: { display: state == "success" ? "block" : "none" },
49
+ });
50
+ }
51
+ if (child.type === FormErrorMessage) {
52
+ return React.cloneElement(child, {
53
+ ...child.props,
54
+ // @ts-ignore
55
+ style: { display: state == "error" ? "block" : "none" },
56
+ });
57
+ }
58
+ return child;
59
+ }));
60
+ }
61
+ export function FormForm(props) {
62
+ return React.createElement("form", props);
63
+ }
64
+ export function FormBlockLabel(props) {
65
+ return React.createElement("label", props);
66
+ }
67
+ export function FormTextInput({ className = "", ...props }) {
68
+ return React.createElement("input", {
69
+ type: "text",
70
+ className: className + " w-input",
71
+ ...props,
72
+ });
73
+ }
74
+ export function FormTextarea({ className = "", ...props }) {
75
+ return React.createElement("input", {
76
+ type: "textarea",
77
+ className: className + " w-input",
78
+ ...props,
79
+ });
80
+ }
81
+ export function FormInlineLabel({ className = "", ...props }) {
82
+ return React.createElement("span", {
83
+ className: className + " w-form-label",
84
+ ...props,
85
+ });
86
+ }
87
+ export function FormCheckboxWrapper({ className = "", ...props }) {
88
+ return React.createElement("label", {
89
+ className: className + " w-checkbox",
90
+ ...props,
91
+ });
92
+ }
93
+ export function FormRadioWrapper({ className = "", ...props }) {
94
+ return React.createElement("label", {
95
+ className: className + " w-radio",
96
+ ...props,
97
+ });
98
+ }
99
+ export function FormCheckboxInput({ className = "", checked = false, ...props }) {
100
+ const checkedProps = {};
101
+ if (typeof checked === "boolean") {
102
+ checkedProps["defaultChecked"] = checked;
103
+ }
104
+ return React.createElement("input", {
105
+ type: "checkbox",
106
+ className: className + " w-checkbox",
107
+ ...checkedProps,
108
+ ...props,
109
+ });
110
+ }
111
+ export function FormRadioInput({ className = "", inputType, ...props }) {
112
+ if (inputType === "custom") {
113
+ // TODO: support FormCustomRadioInput
114
+ }
115
+ return React.createElement("input", {
116
+ className: className + " w-radio-input",
117
+ ...props,
118
+ });
119
+ }
120
+ const MAX_FILE_SIZE_DEFAULT = 10485760;
121
+ const FileUploadContext = React.createContext({
122
+ files: null,
123
+ error: null,
124
+ maxSize: MAX_FILE_SIZE_DEFAULT,
125
+ setFiles: () => { },
126
+ setError: () => { },
127
+ });
128
+ export function FormFileUploadWrapper({ maxSize = MAX_FILE_SIZE_DEFAULT, ...props }) {
129
+ const [files, setFiles] = React.useState(null);
130
+ const [error, setError] = React.useState(null);
131
+ return React.createElement(FileUploadContext.Provider, {
132
+ value: { files, setFiles, error, setError, maxSize },
133
+ }, React.createElement(_FormFileUploadWrapper, { ...props }));
134
+ }
135
+ export function _FormFileUploadWrapper({ className = "", ...props }) {
136
+ return React.createElement("div", {
137
+ className: className + " w-file-upload",
138
+ ...props,
139
+ });
140
+ }
141
+ export function FormFileUploadDefault({ className = "", ...props }) {
142
+ const { files, error } = React.useContext(FileUploadContext);
143
+ return React.createElement("div", {
144
+ className: className + " w-file-upload-default",
145
+ ...props,
146
+ style: {
147
+ ...props.style,
148
+ display: !files || error ? "block" : "none",
149
+ },
150
+ });
151
+ }
152
+ export function FormFileUploadInput({ className = "", ...props }) {
153
+ const { setFiles, setError, maxSize } = React.useContext(FileUploadContext);
154
+ return React.createElement("input", {
155
+ type: "file",
156
+ className: className + " w-file-upload-input",
157
+ ...props,
158
+ onChange: (e) => {
159
+ if (e.target.files) {
160
+ if (e.target.files[0].size <= maxSize) {
161
+ setError(null);
162
+ setFiles(e.target.files);
163
+ }
164
+ else
165
+ setError("SIZE_ERROR");
166
+ }
167
+ },
168
+ });
169
+ }
170
+ export function FormFileUploadLabel({ className = "", ...props }) {
171
+ return React.createElement("label", {
172
+ className: className + " w-file-upload-label",
173
+ ...props,
174
+ });
175
+ }
176
+ export function FormFileUploadText({ className = "", ...props }) {
177
+ return React.createElement("div", {
178
+ className: className + " w-inline-block",
179
+ ...props,
180
+ });
181
+ }
182
+ export function FormFileUploadInfo({ className = "", ...props }) {
183
+ return React.createElement("div", {
184
+ className: className + " w-file-upload-info",
185
+ ...props,
186
+ });
187
+ }
188
+ export function FormFileUploadUploading({ className = "", ...props }) {
189
+ return React.createElement("div", {
190
+ className: className + " w-file-upload-uploading",
191
+ style: { ...props.style, display: "none" },
192
+ ...props,
193
+ });
194
+ }
195
+ export function FormFileUploadUploadingBtn({ className = "", ...props }) {
196
+ return React.createElement("div", {
197
+ className: className + " w-file-upload-uploading-btn",
198
+ ...props,
199
+ });
200
+ }
201
+ export function FormFileUploadUploadingIcon({ className = "", ...props }) {
202
+ return React.createElement("svg", {
203
+ className: className + " icon w-icon-file-upload-uploading",
204
+ ...props,
205
+ }, React.createElement(React.Fragment, null,
206
+ React.createElement("path", { fill: "currentColor", opacity: ".2", d: "M15 30a15 15 0 1 1 0-30 15 15 0 0 1 0 30zm0-3a12 12 0 1 0 0-24 12 12 0 0 0 0 24z" }),
207
+ React.createElement("path", { fill: "currentColor", opacity: ".75", d: "M0 15A15 15 0 0 1 15 0v3A12 12 0 0 0 3 15H0z" },
208
+ React.createElement("animateTransform", { attributeName: "transform", attributeType: "XML", dur: "0.6s", from: "0 15 15", repeatCount: "indefinite", to: "360 15 15", type: "rotate" }))));
209
+ }
210
+ export function FormFileUploadSuccess({ className = "", ...props }) {
211
+ const { files, error } = React.useContext(FileUploadContext);
212
+ return React.createElement("div", {
213
+ className: className + " w-file-upload-success",
214
+ ...props,
215
+ style: {
216
+ ...props.style,
217
+ display: !!files && !error ? "block" : "none",
218
+ },
219
+ });
220
+ }
221
+ export function FormFileUploadFile({ className = "", ...props }) {
222
+ return React.createElement("div", {
223
+ className: className + " w-file-upload-file",
224
+ ...props,
225
+ });
226
+ }
227
+ export function FormFileUploadFileName({ className = "", ...props }) {
228
+ const { files } = React.useContext(FileUploadContext);
229
+ return React.createElement("div", {
230
+ className: className + " w-file-upload-file-name",
231
+ ...props,
232
+ }, files && files[0].name);
233
+ }
234
+ export function FormFileUploadRemoveLink({ className = "", ...props }) {
235
+ const { setFiles } = React.useContext(FileUploadContext);
236
+ return React.createElement("div", {
237
+ className: className + " w-file-remove-link",
238
+ ...props,
239
+ onClick: () => {
240
+ setFiles(null);
241
+ },
242
+ });
243
+ }
244
+ export function FormFileUploadError({ className = "", ...props }) {
245
+ const { error } = React.useContext(FileUploadContext);
246
+ return React.createElement("div", {
247
+ className: className + " w-file-upload-error",
248
+ ...props,
249
+ style: {
250
+ ...props.style,
251
+ display: !!error ? "block" : "none",
252
+ },
253
+ });
254
+ }
255
+ export function FormFileUploadErrorMsg({ errors, className = "", ...props }) {
256
+ const { error } = React.useContext(FileUploadContext);
257
+ return React.createElement("div", {
258
+ className: className + " w-file-upload-error-msg",
259
+ ...props,
260
+ }, errors[error ?? "GENERIC_ERROR"]);
261
+ }
262
+ export function FormButton({ className = "", value, ...props }) {
263
+ return React.createElement("input", {
264
+ type: "submit",
265
+ value: value ?? "",
266
+ className: className + " w-button",
267
+ ...props,
268
+ });
269
+ }
270
+ export function SearchForm(props) {
271
+ return React.createElement("form", props);
272
+ }
273
+ export function SearchInput({ className = "", ...props }) {
274
+ return React.createElement("input", {
275
+ type: "text",
276
+ className: className + " w-input",
277
+ ...props,
278
+ });
279
+ }
280
+ export function SearchButton({ value = "", className = "", ...props }) {
281
+ return React.createElement("input", {
282
+ type: "submit",
283
+ value,
284
+ className: className + " w-button",
285
+ ...props,
286
+ });
287
+ }
288
+ export function FormSuccessMessage({ className = "", ...props }) {
289
+ return React.createElement("div", {
290
+ className: className + " w-form-done",
291
+ ...props,
292
+ });
293
+ }
294
+ export function FormErrorMessage({ className = "", ...props }) {
295
+ return React.createElement("div", {
296
+ className: className + " w-form-fail",
297
+ ...props,
298
+ });
299
+ }
300
+ function hasValue(str) {
301
+ if (typeof str !== "string")
302
+ return false;
303
+ //   is &nbsp
304
+ return str.replace(/^[s ]+|[s ]+$/g, "").length > 0;
305
+ }
306
+ export function FormSelect({ options, className = "", ...props }) {
307
+ return React.createElement("select", { className: className + " w-select", ...props }, options.map(({ v, t }, index) => React.createElement("option", { key: index, value: hasValue(v) ? v : "" }, hasValue(t) ? t : "")));
308
+ }
309
+ export function FormReCaptcha({ siteKey = "", theme = "light", size = "normal", }) {
310
+ React.useEffect(() => {
311
+ loadScript("https://www.google.com/recaptcha/api.js", {
312
+ cacheRegex: /(http|https):\/\/(www)?.+\/recaptcha/,
313
+ });
314
+ }, []);
315
+ return (React.createElement("div", { className: "g-recaptcha", "data-sitekey": siteKey, "data-theme": theme, "data-size": size }));
316
+ }
@@ -0,0 +1,14 @@
1
+ /// <reference types="react" />
2
+ type MapWidgetProps = {
3
+ className?: string;
4
+ apiKey: string;
5
+ zoom?: number;
6
+ latlng?: string;
7
+ mapStyle?: "roadmap" | "satellite" | "hybrid" | "terrain";
8
+ tooltip?: string;
9
+ title?: string;
10
+ enableScroll?: boolean;
11
+ enableTouch?: boolean;
12
+ };
13
+ export declare function MapWidget({ apiKey, mapStyle, zoom, latlng, tooltip, title, enableScroll, enableTouch, className, ...props }: MapWidgetProps): JSX.Element;
14
+ export default Map;
@@ -0,0 +1,71 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import { cj, loadScript } from "../utils";
3
+ function buildTitle(title, tooltip) {
4
+ let markerTitle = "Map pin";
5
+ if (title && tooltip) {
6
+ markerTitle = `Map pin on ${title} showing location of ${tooltip}`;
7
+ }
8
+ else if (title && !tooltip) {
9
+ markerTitle = `Map pin on ${title}`;
10
+ }
11
+ else if (!title && tooltip) {
12
+ markerTitle = `Map pin showing location of ${tooltip}`;
13
+ }
14
+ return markerTitle;
15
+ }
16
+ export function MapWidget({ apiKey = "", mapStyle = "roadmap", zoom = 12, latlng = "51.511214,-0.119824", tooltip = "", title = "", enableScroll = true, enableTouch = true, className = "", ...props }) {
17
+ const mapRef = useRef(null);
18
+ useEffect(() => {
19
+ const loadMap = () => {
20
+ if (!mapRef.current)
21
+ return;
22
+ if (!window?.google?.maps)
23
+ return;
24
+ const { Map, Marker, InfoWindow } = window.google.maps;
25
+ const coords = latlng.split(",");
26
+ const center = { lat: parseFloat(coords[0]), lng: parseFloat(coords[1]) };
27
+ const map = new Map(mapRef.current, {
28
+ zoom,
29
+ center,
30
+ mapTypeId: mapStyle,
31
+ mapTypeControl: false,
32
+ panControl: false,
33
+ streetViewControl: false,
34
+ draggable: enableTouch,
35
+ scrollwheel: enableScroll,
36
+ zoomControl: true,
37
+ });
38
+ const marker = new Marker({
39
+ draggable: false,
40
+ position: center,
41
+ title: buildTitle(title, tooltip),
42
+ map,
43
+ });
44
+ if (tooltip) {
45
+ new InfoWindow({
46
+ disableAutoPan: true,
47
+ content: tooltip,
48
+ position: center,
49
+ }).open({ anchor: marker, map });
50
+ }
51
+ google.maps.event.addListener(marker, "click", function () {
52
+ window.open(`https://maps.google.com/?z=${zoom}&daddr=${latlng}`);
53
+ });
54
+ };
55
+ loadScript(`https://maps.googleapis.com/maps/api/js?v=3.52.5&key=${apiKey}`, {
56
+ cacheRegex: /maps\.googleapis\.com\/maps\/api\/js\?v=3\.52\.5\&key=/gi,
57
+ }).then(loadMap);
58
+ }, [
59
+ apiKey,
60
+ mapStyle,
61
+ zoom,
62
+ latlng,
63
+ tooltip,
64
+ title,
65
+ enableScroll,
66
+ enableTouch,
67
+ mapRef,
68
+ ]);
69
+ return (React.createElement("div", { ...props, className: cj(className, "w-widget w-widget-map"), role: "region", ref: mapRef }));
70
+ }
71
+ export default Map;
@@ -0,0 +1,53 @@
1
+ import * as React from "react";
2
+ import { EASING_FUNCTIONS } from "../utils";
3
+ import { LinkProps, ContainerProps } from "./Basic";
4
+ type NavbarConfig = {
5
+ animation: string;
6
+ collapse: string;
7
+ docHeight: boolean;
8
+ duration: number;
9
+ easing: keyof typeof EASING_FUNCTIONS;
10
+ easing2: keyof typeof EASING_FUNCTIONS;
11
+ noScroll: boolean;
12
+ };
13
+ export declare const NavbarContext: React.Context<NavbarConfig & {
14
+ animDirect: -1 | 1;
15
+ animOver: boolean;
16
+ getBodyHeight: () => number;
17
+ getOverlayHeight: () => number;
18
+ isOpen: boolean;
19
+ menu?: React.MutableRefObject<HTMLElement | null> | undefined;
20
+ root?: React.MutableRefObject<HTMLElement | null> | undefined;
21
+ toggleOpen: () => void;
22
+ navbarMounted: boolean;
23
+ }>;
24
+ type NavbarChildrenType = NavbarContainerProps | NavbarBrandProps | NavbarMenuProps | NavbarButtonProps;
25
+ type NavbarProps = {
26
+ tag: React.ElementType;
27
+ config: NavbarConfig;
28
+ className?: string;
29
+ children?: React.ReactElement<NavbarChildrenType>[] | React.ReactElement<NavbarChildrenType>;
30
+ };
31
+ export declare function NavbarWrapper(props: NavbarProps): JSX.Element;
32
+ type NavbarContainerProps = ContainerProps & {
33
+ toggleOpen: () => void;
34
+ isOpen: boolean;
35
+ children: React.ReactNode;
36
+ };
37
+ export declare function NavbarContainer({ children, ...props }: NavbarContainerProps): JSX.Element;
38
+ type NavbarBrandProps = LinkProps;
39
+ export declare function NavbarBrand({ className, ...props }: NavbarBrandProps): JSX.Element;
40
+ type NavbarMenuProps = React.PropsWithChildren<{
41
+ tag?: React.ElementType;
42
+ className?: string;
43
+ isOpen?: boolean;
44
+ }>;
45
+ export declare function NavbarMenu({ tag, className, ...props }: NavbarMenuProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
46
+ type NavbarLinkProps = LinkProps;
47
+ export declare function NavbarLink({ className, ...props }: NavbarLinkProps): JSX.Element;
48
+ type NavbarButtonProps = React.PropsWithChildren<{
49
+ tag?: React.ElementType;
50
+ className?: string;
51
+ }>;
52
+ export declare function NavbarButton({ tag, className, ...props }: NavbarButtonProps): React.ReactElement<any, string | React.JSXElementConstructor<any>>;
53
+ export {};
@@ -0,0 +1,240 @@
1
+ import * as React from "react";
2
+ import { EASING_FUNCTIONS, cj, debounce, isServer, useLayoutEffect, useResizeObserver, } from "../utils";
3
+ import { Link, Container } from "./Basic";
4
+ export const NavbarContext = React.createContext({
5
+ animDirect: 1,
6
+ animOver: false,
7
+ animation: "animation",
8
+ collapse: "medium",
9
+ docHeight: false,
10
+ duration: 400,
11
+ easing2: "ease",
12
+ easing: "ease",
13
+ getBodyHeight: () => 0,
14
+ getOverlayHeight: () => 0,
15
+ isOpen: false,
16
+ noScroll: false,
17
+ toggleOpen: () => { },
18
+ navbarMounted: false,
19
+ });
20
+ function getAnimationKeyframes({ axis = "Y", start, end, }) {
21
+ const t = `translate${axis}`;
22
+ return [{ transform: `${t}(${start}px)` }, { transform: `${t}(${end}px)` }];
23
+ }
24
+ export function NavbarWrapper(props) {
25
+ const { animation, docHeight, easing, easing2, duration, noScroll } = props.config;
26
+ const root = React.useRef(null);
27
+ const menu = React.useRef(null);
28
+ const animOver = /^over/.test(animation);
29
+ const animDirect = /left$/.test(animation) ? -1 : 1;
30
+ const getBodyHeight = React.useCallback(() => {
31
+ if (isServer)
32
+ return 0;
33
+ return docHeight
34
+ ? document.documentElement.scrollHeight
35
+ : document.body.scrollHeight;
36
+ }, [docHeight]);
37
+ const getOverlayHeight = React.useCallback(() => {
38
+ if (isServer || !root.current)
39
+ return 0;
40
+ let h = getBodyHeight();
41
+ const style = getComputedStyle(root.current);
42
+ if (!animOver && style.position !== "fixed") {
43
+ h -= root.current.offsetHeight;
44
+ }
45
+ return h;
46
+ }, [animOver, getBodyHeight]);
47
+ const getOffsetHeight = React.useCallback(() => {
48
+ if (!root.current || !menu.current)
49
+ return 0;
50
+ return root.current.offsetHeight + menu.current.offsetHeight;
51
+ }, []);
52
+ const [isOpen, setIsOpen] = React.useState(false);
53
+ const toggleOpen = debounce(() => {
54
+ if (!menu.current)
55
+ return;
56
+ // menu is open and should be closed
57
+ if (isOpen) {
58
+ const keyframes = animOver
59
+ ? getAnimationKeyframes({
60
+ axis: "X",
61
+ start: 0,
62
+ end: animDirect * menu.current.offsetWidth,
63
+ })
64
+ : getAnimationKeyframes({ start: 0, end: -getOffsetHeight() });
65
+ const anim = menu.current.animate(keyframes, {
66
+ easing: EASING_FUNCTIONS[easing2] ?? "ease",
67
+ duration,
68
+ fill: "forwards",
69
+ });
70
+ anim.onfinish = () => {
71
+ setIsOpen(!isOpen);
72
+ };
73
+ return;
74
+ }
75
+ setIsOpen(!isOpen);
76
+ });
77
+ useLayoutEffect(() => {
78
+ if (!menu.current)
79
+ return;
80
+ // menu is closed and will open, but the animation only runs when isOpen is true
81
+ if (isOpen) {
82
+ const keyframes = animOver
83
+ ? getAnimationKeyframes({
84
+ axis: "X",
85
+ start: animDirect * menu.current.offsetWidth,
86
+ end: 0,
87
+ })
88
+ : getAnimationKeyframes({ start: -getOffsetHeight(), end: 0 });
89
+ menu.current.animate(keyframes, {
90
+ easing: EASING_FUNCTIONS[easing] ?? "ease",
91
+ duration,
92
+ fill: "forwards",
93
+ });
94
+ }
95
+ }, [
96
+ animDirect,
97
+ animOver,
98
+ duration,
99
+ easing,
100
+ getBodyHeight,
101
+ getOffsetHeight,
102
+ isOpen,
103
+ ]);
104
+ // if the menu is opened and noScroll === false prevent scrolling
105
+ useLayoutEffect(() => {
106
+ if (isOpen && noScroll) {
107
+ document.body.style.overflowY = "hidden";
108
+ }
109
+ else {
110
+ document.body.style.overflowY = "";
111
+ }
112
+ return () => {
113
+ document.body.style.overflowY = "";
114
+ };
115
+ }, [isOpen, noScroll]);
116
+ // Closes menu when the window is resized
117
+ const closeOnResize = React.useCallback(() => setIsOpen(false), [setIsOpen]);
118
+ useResizeObserver(root, closeOnResize);
119
+ return (React.createElement(NavbarContext.Provider, { value: {
120
+ ...props.config,
121
+ root,
122
+ menu,
123
+ animOver,
124
+ animDirect,
125
+ getBodyHeight,
126
+ getOverlayHeight,
127
+ isOpen,
128
+ toggleOpen,
129
+ navbarMounted: true,
130
+ } },
131
+ React.createElement(Navbar, { ...props })));
132
+ }
133
+ /**
134
+ * Navbar menu gets appended to the overlay when it's open.
135
+ * This function extracts the child menu when that's the case.
136
+ * */
137
+ const maybeExtractChildMenu = (children, isOpen) => {
138
+ if (!isOpen)
139
+ return { childMenu: null, rest: children };
140
+ const childrenArray = React.Children.toArray(children);
141
+ const { childMenu, rest } = childrenArray.reduce((acc, child) => {
142
+ if (child.type === NavbarMenu) {
143
+ acc.childMenu = child;
144
+ return acc;
145
+ }
146
+ if (child.type === NavbarContainer) {
147
+ const { children: containerChildren, ...containerProps } = child.props;
148
+ const { childMenu, rest } = maybeExtractChildMenu(containerChildren, isOpen);
149
+ acc.childMenu = childMenu;
150
+ acc.rest.push(React.createElement(NavbarContainer, { ...containerProps }, rest));
151
+ return acc;
152
+ }
153
+ acc.rest.push(child);
154
+ return acc;
155
+ }, { childMenu: null, rest: [] });
156
+ return {
157
+ childMenu,
158
+ rest: React.createElement(React.Fragment, null, rest.map((e, i) => React.cloneElement(e, { key: i }))),
159
+ };
160
+ };
161
+ function Navbar({ tag = "div", className = "", children, config, ...props }) {
162
+ const { root, isOpen } = React.useContext(NavbarContext);
163
+ const { childMenu, rest } = React.useMemo(() => maybeExtractChildMenu(children, isOpen), [children, isOpen]);
164
+ return React.createElement(tag, {
165
+ ...props,
166
+ className: cj(className, "w-nav"),
167
+ "data-collapse": config.collapse,
168
+ "data-animation": config.animation,
169
+ ref: root,
170
+ }, React.createElement(React.Fragment, null,
171
+ rest,
172
+ React.createElement(NavbarOverlay, null, childMenu)));
173
+ }
174
+ function NavbarOverlay({ children }) {
175
+ const { isOpen, getOverlayHeight, toggleOpen } = React.useContext(NavbarContext);
176
+ const overlayToggleOpen = React.useCallback((e) => {
177
+ // prevent link clicks to close the overlay
178
+ if (e.target === e.currentTarget) {
179
+ toggleOpen();
180
+ }
181
+ }, [toggleOpen]);
182
+ return (React.createElement("div", { className: "w-nav-overlay", id: "w-nav-overlay", style: {
183
+ display: isOpen ? "block" : "none",
184
+ height: getOverlayHeight(),
185
+ width: isOpen ? "100vw" : 0,
186
+ }, onClick: overlayToggleOpen }, children));
187
+ }
188
+ export function NavbarContainer({ children, ...props }) {
189
+ const ref = React.useRef(null);
190
+ const { isOpen } = React.useContext(NavbarContext);
191
+ const updateLinkStyles = React.useCallback((entry) => {
192
+ const { maxWidth: containerMaxWidth } = getComputedStyle(entry.target);
193
+ document
194
+ .querySelectorAll(".w-nav-menu>.w-dropdown,.w-nav-menu>.w-nav-link")
195
+ .forEach((node) => {
196
+ if (!(node instanceof HTMLElement))
197
+ return;
198
+ if (!isOpen) {
199
+ node.style.maxWidth = "";
200
+ return;
201
+ }
202
+ const { maxWidth } = getComputedStyle(node);
203
+ node.style.maxWidth =
204
+ !maxWidth || maxWidth === "none" ? containerMaxWidth : "";
205
+ });
206
+ }, [isOpen]);
207
+ useResizeObserver(ref, updateLinkStyles);
208
+ return (React.createElement(Container, { ...props, ref: ref }, children));
209
+ }
210
+ export function NavbarBrand({ className = "", ...props }) {
211
+ return React.createElement(Link, { ...props, className: cj(className, "w-nav-brand") });
212
+ }
213
+ export function NavbarMenu({ tag = "nav", className = "", ...props }) {
214
+ const { getBodyHeight, animOver, isOpen, menu } = React.useContext(NavbarContext);
215
+ return React.createElement(tag, {
216
+ ...props,
217
+ className: cj(className, "w-nav-menu"),
218
+ ...(isOpen ? { "data-nav-menu-open": "" } : {}),
219
+ style: animOver ? { height: getBodyHeight() } : {},
220
+ ref: menu,
221
+ });
222
+ }
223
+ export function NavbarLink({ className = "", ...props }) {
224
+ const { isOpen } = React.useContext(NavbarContext);
225
+ return (React.createElement(Link, { ...props, className: cj(className, "w-nav-link", isOpen && "w--nav-link-open") }));
226
+ }
227
+ export function NavbarButton({ tag = "div", className = "", ...props }) {
228
+ const { isOpen, toggleOpen } = React.useContext(NavbarContext);
229
+ return React.createElement(tag, {
230
+ ...props,
231
+ "aria-label": "menu",
232
+ "aria-expanded": isOpen ? "true" : "false",
233
+ "aria-haspopup": "menu",
234
+ "aria-controls": "w-nav-overlay",
235
+ role: "button",
236
+ tabIndex: 0,
237
+ className: cj(className, "w-nav-button", isOpen && "w--open"),
238
+ onClick: toggleOpen,
239
+ });
240
+ }