diff --git a/plugins/config-schema/src/components/SchemaView/ArrayView.tsx b/plugins/config-schema/src/components/SchemaView/ArrayView.tsx
new file mode 100644
index 0000000000..15f4dd8800
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/ArrayView.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+import { Box, Typography } from '@material-ui/core';
+import { Schema } from 'jsonschema';
+import React from 'react';
+import { ChildView } from './ChildView';
+import { MetadataView } from './MetadataView';
+import { SchemaViewProps } from './types';
+
+export function ArrayView({ path, depth, schema }: SchemaViewProps) {
+ const itemDepth = depth + 1;
+ const itemPath = path ? `${path}[]` : '[]';
+ const itemSchema = schema.items;
+
+ return (
+ <>
+
+ {schema.description && (
+
+ {schema.description}
+
+ )}
+
+
+ Items
+
+ {schema.additionalItems && schema.additionalItems !== true && (
+ <>
+ Additional Items
+
+ >
+ )}
+ >
+ );
+}
diff --git a/plugins/config-schema/src/components/SchemaView/ChildView.tsx b/plugins/config-schema/src/components/SchemaView/ChildView.tsx
new file mode 100644
index 0000000000..bc4f3bb63e
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/ChildView.tsx
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+import { JsonValue } from '@backstage/config';
+import { Box, Chip, Divider, makeStyles, Typography } from '@material-ui/core';
+import { Schema } from 'jsonschema';
+import React, { useEffect, useRef } from 'react';
+import { useScrollTargets } from '../SchemaViewer/ScrollContext';
+import { SchemaView } from './SchemaView';
+
+export interface MetadataViewRowProps {
+ label: string;
+ text?: string;
+ data?: JsonValue;
+}
+
+function titleVariant(depth: number) {
+ if (depth <= 1) {
+ return 'h2';
+ } else if (depth === 2) {
+ return 'h3';
+ } else if (depth === 3) {
+ return 'h4';
+ } else if (depth === 4) {
+ return 'h5';
+ }
+ return 'h6';
+}
+
+const useChildViewStyles = makeStyles(theme => ({
+ title: {
+ marginBottom: 0,
+ },
+ chip: {
+ marginLeft: theme.spacing(1),
+ marginRight: 0,
+ marginBottom: 0,
+ },
+}));
+
+export function ChildView({
+ path,
+ depth,
+ schema,
+ required,
+ lastChild,
+}: {
+ path: string;
+ depth: number;
+ schema?: Schema;
+ required?: boolean;
+ lastChild?: boolean;
+}) {
+ const classes = useChildViewStyles();
+ const titleRef = useRef(null);
+ const scroll = useScrollTargets();
+
+ useEffect(() => {
+ return scroll?.setScrollListener(path, () => {
+ titleRef.current?.scrollIntoView({ behavior: 'smooth' });
+ });
+ }, [scroll, path]);
+
+ const chips = new Array();
+ const chipProps = { size: 'small' as const, classes: { root: classes.chip } };
+
+ if (required) {
+ chips.push(
+ ,
+ );
+ }
+
+ const visibility = (schema as { visibility?: string })?.visibility;
+ if (visibility === 'frontend') {
+ chips.push(
+ ,
+ );
+ } else if (visibility === 'secret') {
+ chips.push(
+ ,
+ );
+ }
+
+ return (
+
+
+
+
+
+ {path}
+
+ {chips.length > 0 && }
+ {chips}
+
+ {schema && (
+
+ )}
+
+
+ );
+}
diff --git a/plugins/config-schema/src/components/SchemaView/MatchView.tsx b/plugins/config-schema/src/components/SchemaView/MatchView.tsx
new file mode 100644
index 0000000000..df0dbba8ef
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/MatchView.tsx
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+import { Typography } from '@material-ui/core';
+import { Schema } from 'jsonschema';
+import React from 'react';
+import { ChildView } from './ChildView';
+
+export function MatchView({
+ path,
+ depth,
+ schema,
+ label,
+}: {
+ path: string;
+ depth: number;
+ schema: Schema[];
+ label: string;
+}) {
+ return (
+ <>
+ {label}
+ {schema.map((optionSchema, index) => (
+
+ ))}
+ >
+ );
+}
diff --git a/plugins/config-schema/src/components/SchemaView/MetadataView.tsx b/plugins/config-schema/src/components/SchemaView/MetadataView.tsx
new file mode 100644
index 0000000000..41d48149ad
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/MetadataView.tsx
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+import { JsonValue } from '@backstage/config';
+import {
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableRow,
+ Typography,
+} from '@material-ui/core';
+import { Schema } from 'jsonschema';
+import React from 'react';
+
+export interface MetadataViewRowProps {
+ label: string;
+ text?: string;
+ data?: JsonValue;
+}
+
+export function MetadataViewRow({ label, text, data }: MetadataViewRowProps) {
+ if (text === undefined && data === undefined) {
+ return null;
+ }
+ return (
+
+
+
+ {label}
+
+
+
+
+ {data ? JSON.stringify(data) : text}
+
+
+
+ );
+}
+
+export function MetadataView({ schema }: { schema: Schema }) {
+ return (
+
+
+
+
+
+ {schema.additionalProperties === true && (
+
+ )}
+ {schema.additionalItems === true && (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/plugins/config-schema/src/components/SchemaView/ObjectView.tsx b/plugins/config-schema/src/components/SchemaView/ObjectView.tsx
new file mode 100644
index 0000000000..e3d0effd0d
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/ObjectView.tsx
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+import { Box, Typography } from '@material-ui/core';
+import React from 'react';
+import { ChildView } from './ChildView';
+import { MetadataView } from './MetadataView';
+import { SchemaViewProps } from './types';
+
+function isRequired(name: string, required?: boolean | string[]) {
+ if (required === true) {
+ return true;
+ }
+ if (Array.isArray(required)) {
+ return required.includes(name);
+ }
+ return false;
+}
+
+export function ObjectView({ path, depth, schema }: SchemaViewProps) {
+ const properties = Object.entries(schema.properties ?? {});
+ const patternProperties = Object.entries(schema.patternProperties ?? {});
+
+ return (
+ <>
+ {depth > 0 && (
+
+ {schema.description && (
+
+ {schema.description}
+
+ )}
+
+
+ )}
+ {properties.length > 0 && (
+ <>
+ {depth > 0 && Properties}
+ {properties.map(([name, propSchema], index) => (
+
+ ))}
+ >
+ )}
+ {patternProperties.length > 0 && (
+ <>
+ {depth > 0 && (
+ Pattern Properties
+ )}
+ {patternProperties.map(([name, propSchema], index) => (
+ ` : name}
+ depth={depth + 1}
+ schema={propSchema}
+ lastChild={index === patternProperties.length - 1}
+ required={isRequired(name, schema.required)}
+ />
+ ))}
+ >
+ )}
+ {schema.additionalProperties && schema.additionalProperties !== true && (
+ <>
+ Additional Properties
+
+ >
+ )}
+ >
+ );
+}
diff --git a/plugins/config-schema/src/components/SchemaView/ScalarView.tsx b/plugins/config-schema/src/components/SchemaView/ScalarView.tsx
new file mode 100644
index 0000000000..1349358abd
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/ScalarView.tsx
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+import { Box, Typography } from '@material-ui/core';
+import React from 'react';
+import { MetadataView } from './MetadataView';
+import { SchemaViewProps } from './types';
+
+export function ScalarView({ schema }: SchemaViewProps) {
+ return (
+ <>
+ {schema.description && (
+
+ {schema.description}
+
+ )}
+
+ >
+ );
+}
diff --git a/plugins/config-schema/src/components/SchemaView/SchemaView.tsx b/plugins/config-schema/src/components/SchemaView/SchemaView.tsx
new file mode 100644
index 0000000000..bf5d7fd18c
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/SchemaView.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+import React from 'react';
+import { ArrayView } from './ArrayView';
+import { MatchView } from './MatchView';
+import { ObjectView } from './ObjectView';
+import { ScalarView } from './ScalarView';
+import { SchemaViewProps } from './types';
+
+export function SchemaView(props: SchemaViewProps) {
+ const { schema } = props;
+ if (schema.anyOf) {
+ return (
+
+ );
+ }
+ if (schema.oneOf) {
+ return (
+
+ );
+ }
+ if (schema.allOf) {
+ return (
+
+ );
+ }
+ switch (schema.type) {
+ case 'array':
+ return ;
+ case 'object':
+ case undefined:
+ return ;
+ default:
+ return ;
+ }
+}
diff --git a/plugins/config-schema/src/components/SchemaView/index.ts b/plugins/config-schema/src/components/SchemaView/index.ts
new file mode 100644
index 0000000000..8840696be6
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+export { SchemaView } from './SchemaView';
diff --git a/plugins/config-schema/src/components/SchemaView/types.ts b/plugins/config-schema/src/components/SchemaView/types.ts
new file mode 100644
index 0000000000..94b676ec3e
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaView/types.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+import { Schema } from 'jsonschema';
+
+export interface SchemaViewProps {
+ path: string;
+ depth: number;
+ schema: Schema;
+}
diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx
index e34755a39e..3ad1f55451 100644
--- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx
+++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx
@@ -13,445 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, {
- createContext,
- ReactNode,
- useContext,
- useEffect,
- useMemo,
- useRef,
-} from 'react';
-import { Schema } from 'jsonschema';
-import {
- Box,
- Chip,
- createStyles,
- Divider,
- fade,
- makeStyles,
- Paper,
- Table,
- TableBody,
- TableCell,
- TableRow,
- Typography,
- withStyles,
-} from '@material-ui/core';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+
+import { Box, createStyles, fade, Paper, withStyles } from '@material-ui/core';
import ChevronRightIcon from '@material-ui/icons/ChevronRight';
-import { JsonValue } from '@backstage/config';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { TreeItem, TreeItemProps, TreeView } from '@material-ui/lab';
-
-class ScrollForwarder {
- private readonly listeners = new Map void>();
-
- setScrollListener(id: string, listener: () => void): () => void {
- this.listeners.set(id, listener);
-
- return () => {
- if (this.listeners.get(id) === listener) {
- this.listeners.delete(id);
- }
- };
- }
-
- scrollTo(id: string) {
- this.listeners.get(id)?.();
- }
-}
-
-const ScrollContext = createContext(undefined);
-
-interface SchemaViewProps {
- path: string;
- depth: number;
- schema: Schema;
-}
-
-export interface MetadataViewRowProps {
- label: string;
- text?: string;
- data?: JsonValue;
-}
-
-export function MetadataViewRow({ label, text, data }: MetadataViewRowProps) {
- if (text === undefined && data === undefined) {
- return null;
- }
- return (
-
-
-
- {label}
-
-
-
-
- {data ? JSON.stringify(data) : text}
-
-
-
- );
-}
-
-export function MetadataView({ schema }: { schema: Schema }) {
- return (
-
-
-
-
-
- {schema.additionalProperties === true && (
-
- )}
- {schema.additionalItems === true && (
-
- )}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
-
-export function ScalarView({ schema }: SchemaViewProps) {
- return (
- <>
- {schema.description && (
-
- {schema.description}
-
- )}
-
- >
- );
-}
-
-function isRequired(name: string, required?: boolean | string[]) {
- if (required === true) {
- return true;
- }
- if (Array.isArray(required)) {
- return required.includes(name);
- }
- return false;
-}
-
-function titleVariant(depth: number) {
- if (depth <= 1) {
- return 'h2';
- } else if (depth === 2) {
- return 'h3';
- } else if (depth === 3) {
- return 'h4';
- } else if (depth === 4) {
- return 'h5';
- }
- return 'h6';
-}
-
-export function VisibilityView({ schema }: { schema: Schema }) {
- const { visibility } = schema as { visibility?: string };
- if (visibility === 'frontend') {
- return (
- }
- />
- );
- } else if (visibility === 'secret') {
- return (
- }
- />
- );
- }
- return null;
-}
-
-const useChildViewStyles = makeStyles(theme => ({
- title: {
- marginBottom: 0,
- },
- chip: {
- marginLeft: theme.spacing(1),
- marginRight: 0,
- marginBottom: 0,
- },
-}));
-
-export function ChildView({
- path,
- depth,
- schema,
- required,
- lastChild,
-}: {
- path: string;
- depth: number;
- schema?: Schema;
- required?: boolean;
- lastChild?: boolean;
-}) {
- const classes = useChildViewStyles();
- const titleRef = useRef(null);
- const scroll = useContext(ScrollContext);
-
- useEffect(() => {
- return scroll?.setScrollListener(path, () => {
- titleRef.current?.scrollIntoView({ behavior: 'smooth' });
- });
- }, [scroll, path]);
-
- const chips = new Array();
- const chipProps = { size: 'small' as const, classes: { root: classes.chip } };
-
- if (required) {
- chips.push(
- ,
- );
- }
-
- const visibility = (schema as { visibility?: string })?.visibility;
- if (visibility === 'frontend') {
- chips.push(
- ,
- );
- } else if (visibility === 'secret') {
- chips.push(
- ,
- );
- }
-
- return (
-
-
-
-
-
- {path}
-
- {chips.length > 0 && }
- {chips}
-
- {schema && (
-
- )}
-
-
- );
-}
-
-export function ArrayView({ path, depth, schema }: SchemaViewProps) {
- const itemDepth = depth + 1;
- const itemPath = path ? `${path}[]` : '[]';
- const itemSchema = schema.items;
-
- return (
- <>
-
- {schema.description && (
-
- {schema.description}
-
- )}
-
-
- Items
-
- {schema.additionalItems && schema.additionalItems !== true && (
- <>
- Additional Items
-
- >
- )}
- >
- );
-}
-
-export function ObjectView({ path, depth, schema }: SchemaViewProps) {
- const properties = Object.entries(schema.properties ?? {});
- const patternProperties = Object.entries(schema.patternProperties ?? {});
-
- return (
- <>
- {depth > 0 && (
-
- {schema.description && (
-
- {schema.description}
-
- )}
-
-
- )}
- {properties.length > 0 && (
- <>
- {depth > 0 && Properties}
- {properties.map(([name, propSchema], index) => (
-
- ))}
- >
- )}
- {patternProperties.length > 0 && (
- <>
- {depth > 0 && (
- Pattern Properties
- )}
- {patternProperties.map(([name, propSchema], index) => (
- ` : name}
- depth={depth + 1}
- schema={propSchema}
- lastChild={index === patternProperties.length - 1}
- required={isRequired(name, schema.required)}
- />
- ))}
- >
- )}
- {schema.additionalProperties && schema.additionalProperties !== true && (
- <>
- Additional Properties
-
- >
- )}
- >
- );
-}
-
-export function MatchView({
- path,
- depth,
- schema,
- label,
-}: {
- path: string;
- depth: number;
- schema: Schema[];
- label: string;
-}) {
- return (
- <>
- {label}
- {schema.map((optionSchema, index) => (
-
- ))}
- >
- );
-}
-
-export function SchemaView(props: SchemaViewProps) {
- const { schema } = props;
- if (schema.anyOf) {
- return (
-
- );
- }
- if (schema.oneOf) {
- return (
-
- );
- }
- if (schema.allOf) {
- return (
-
- );
- }
- switch (schema.type) {
- case 'array':
- return ;
- case 'object':
- case undefined:
- return ;
- default:
- return ;
- }
-}
+import { Schema } from 'jsonschema';
+import React, { ReactNode, useMemo, useRef } from 'react';
+import { SchemaView } from '../SchemaView';
+import { ScrollTargetsProvider, useScrollTargets } from './ScrollContext';
export interface SchemaViewerProps {
schema: Schema;
@@ -593,7 +163,7 @@ export function createSchemaBrowserItems(
}
export function SchemaBrowser({ schema }: { schema: Schema }) {
- const scroll = useContext(ScrollContext);
+ const scroll = useScrollTargets();
const expandedRef = useRef([]);
const data = useMemo(() => {
const expanded = new Array();
@@ -642,7 +212,7 @@ export const SchemaViewer = ({ schema }: SchemaViewerProps) => {
maxHeight="100%"
>
-
+
@@ -650,7 +220,7 @@ export const SchemaViewer = ({ schema }: SchemaViewerProps) => {
-
+
diff --git a/plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx b/plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx
new file mode 100644
index 0000000000..085b237754
--- /dev/null
+++ b/plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2021 Spotify AB
+ *
+ * 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.
+ */
+
+import React, { createContext, ReactNode, useContext } from 'react';
+
+class ScrollTargetsForwarder {
+ private readonly listeners = new Map void>();
+
+ setScrollListener(id: string, listener: () => void): () => void {
+ this.listeners.set(id, listener);
+
+ return () => {
+ if (this.listeners.get(id) === listener) {
+ this.listeners.delete(id);
+ }
+ };
+ }
+
+ scrollTo(id: string) {
+ this.listeners.get(id)?.();
+ }
+}
+
+const ScrollTargetsContext = createContext(
+ undefined,
+);
+
+export function ScrollTargetsProvider({ children }: { children: ReactNode }) {
+ return (
+
+ );
+}
+
+export function useScrollTargets() {
+ return useContext(ScrollTargetsContext);
+}