diff --git a/publisher/src/components/MetricConfiguration/Configuration.tsx b/publisher/src/components/MetricConfiguration/Configuration.tsx index a10498047..19f94dbac 100644 --- a/publisher/src/components/MetricConfiguration/Configuration.tsx +++ b/publisher/src/components/MetricConfiguration/Configuration.tsx @@ -15,10 +15,15 @@ // along with this program. If not, see . // ============================================================================= -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; -import { Metric, MetricDisaggregationDimensions } from "../../shared/types"; +import { + Metric, + MetricDisaggregationDimensions, + MetricDisaggregations as MetricDisaggregationsType, +} from "../../shared/types"; import { removeSnakeCase } from "../../utils"; +import { ReactComponent as RightArrowIcon } from "../assets/right-arrow.svg"; import blueCheck from "../assets/status-check-icon.png"; import { BinaryRadioButton } from "../Forms"; import { TabbedBar, TabbedItem, TabbedOptions } from "../Reports"; @@ -44,6 +49,11 @@ import { type MetricConfigurationProps = { activeMetricKey: string; filteredMetricSettings: { [key: string]: Metric }; + activeDimension: MetricDisaggregationDimensions | undefined; + activeDisaggregation: MetricDisaggregationsType | undefined; + setActiveDisaggregation: React.Dispatch< + React.SetStateAction + >; saveAndUpdateMetricSettings: ( typeOfUpdate: "METRIC" | "DISAGGREGATION" | "DIMENSION" | "CONTEXT", updatedSetting: MetricSettings, @@ -57,12 +67,12 @@ type MetricConfigurationProps = { export const Configuration: React.FC = ({ activeMetricKey, filteredMetricSettings, + activeDimension, + activeDisaggregation, + setActiveDisaggregation, saveAndUpdateMetricSettings, setActiveDimension, }): JSX.Element => { - const [activeDisaggregation, setActiveDisaggregation] = useState( - filteredMetricSettings[activeMetricKey]?.disaggregations?.[0] - ); const metricDisplayName = filteredMetricSettings[activeMetricKey]?.display_name; const metricEnabled = Boolean( @@ -201,6 +211,7 @@ export const Configuration: React.FC = ({ setActiveDimension(dimension)} > @@ -210,22 +221,20 @@ export const Configuration: React.FC = ({ activeDisaggregation.enabled && dimension.enabled } onChange={() => { - if (activeDisaggregation.enabled) { - saveAndUpdateMetricSettings("DIMENSION", { - key: activeMetricKey, - disaggregations: [ - { - key: activeDisaggregation.key, - dimensions: [ - { - key: dimension.key, - enabled: !dimension.enabled, - }, - ], - }, - ], - }); - } + saveAndUpdateMetricSettings("DIMENSION", { + key: activeMetricKey, + disaggregations: [ + { + key: activeDisaggregation.key, + dimensions: [ + { + key: dimension.key, + enabled: !dimension.enabled, + }, + ], + }, + ], + }); }} /> = ({ > {dimension.label} + + ); diff --git a/publisher/src/components/MetricConfiguration/MetricConfiguration.styles.tsx b/publisher/src/components/MetricConfiguration/MetricConfiguration.styles.tsx index 2185cb6d6..0bfeaf9b9 100644 --- a/publisher/src/components/MetricConfiguration/MetricConfiguration.styles.tsx +++ b/publisher/src/components/MetricConfiguration/MetricConfiguration.styles.tsx @@ -20,12 +20,18 @@ import styled from "styled-components/macro"; import { BinaryRadioGroupWrapper, Button } from "../Forms"; import { palette, typography } from "../GlobalStyles"; +const METRICS_VIEW_CONTAINER_BREAKPOINT = 1200; + export const MetricsViewContainer = styled.div` width: 100%; display: flex; flex-direction: column; align-items: flex-start; overflow: hidden; + + @media only screen and (max-width: ${METRICS_VIEW_CONTAINER_BREAKPOINT}px) { + overflow: unset; + } `; export const MetricsViewControlPanel = styled.div` @@ -35,6 +41,12 @@ export const MetricsViewControlPanel = styled.div` flex-wrap: wrap; justify-content: space-between; overflow-y: scroll; + + @media only screen and (max-width: ${METRICS_VIEW_CONTAINER_BREAKPOINT}px) { + flex-direction: column; + flex-wrap: nowrap; + justify-content: unset; + } `; export const MetricsViewControlPanelOverflowHidden = styled( @@ -63,6 +75,13 @@ export const PanelContainerRight = styled.div` overflow-y: scroll; `; +export const MetricBoxBottomPaddingContainer = styled.div` + display: flex; + flex-wrap: wrap; + padding-bottom: 100px; + overflow-y: scroll; +`; + type MetricBoxContainerProps = { enabled?: boolean; }; @@ -84,6 +103,12 @@ export const MetricBoxContainer = styled.div` cursor: pointer; border: 1px solid ${palette.solid.blue}; } + + @media only screen and (max-width: ${METRICS_VIEW_CONTAINER_BREAKPOINT}px) { + width: 100%; + max-width: unset; + flex: unset; + } `; export const MetricViewBoxContainer = styled(MetricBoxContainer)<{ @@ -118,25 +143,34 @@ export const MetricNameBadgeWrapper = styled.div` align-items: center; `; -export const Metric = styled.div` +export const Metric = styled.div<{ inView: boolean }>` width: 100%; display: flex; gap: 20px; align-items: center; + justify-content: flex-start; border-bottom: 1px solid ${palette.solid.darkgrey}; - padding-bottom: 8px; - padding-right: 50px; + padding: 12px; position: relative; + background: ${({ inView }) => + inView ? palette.highlight.lightblue1 : `none`}; &:hover { + background: ${palette.highlight.grey1}; cursor: pointer; } - &:hover:after { - content: "➝"; + svg { position: absolute; - ${typography.sizeCSS.title} - right: 0; + opacity: ${({ inView }) => (inView ? `1` : `0`)}; + right: ${({ inView }) => (inView ? `13px` : `-20px`)}; + transition: opacity 0.2s ease, right 0.3s ease; + } + + &:hover svg { + display: block; + right: 13px; + opacity: 1; } `; @@ -160,7 +194,12 @@ export const MetricDescription = styled.div` export const MetricDetailsDisplay = styled.div` width: 100%; overflow-y: scroll; - padding: 24px 12px 24px 0; + padding: 24px 12px 50px 0; + + @media only screen and (max-width: ${METRICS_VIEW_CONTAINER_BREAKPOINT}px) { + overflow-y: unset; + padding: 24px 12px 10px 0; + } `; export const MetricOnOffWrapper = styled.div` @@ -200,6 +239,8 @@ export const MetricDisaggregations = styled.div<{ enabled?: boolean }>` height: 100%; width: 100%; top: 0; + left: 0; + z-index: 2; opacity: 0.5; } `} @@ -236,24 +277,33 @@ export const DisaggregationTab = styled.div` } `; -export const Dimension = styled.div<{ enabled?: boolean }>` +export const Dimension = styled.div<{ enabled?: boolean; inView?: boolean }>` ${typography.sizeCSS.medium}; display: flex; align-items: center; gap: 12px; - padding: 15px 0; + padding: 17px 10px; border-bottom: 1px solid ${palette.highlight.grey4}; position: relative; + background: ${({ inView }) => + inView ? palette.highlight.lightblue1 : `none`}; &:hover { + background: ${palette.highlight.grey1}; cursor: pointer; } - &:hover::before { - content: "➝"; + svg { position: absolute; - right: 0; - ${typography.sizeCSS.title} + opacity: ${({ inView }) => (inView ? `1` : `0`)}; + right: ${({ inView }) => (inView ? `13px` : `-20px`)}; + transition: opacity 0.2s ease, right 0.3s ease; + } + + &:hover svg { + display: block; + right: 13px; + opacity: 1; } &:last-child { @@ -270,6 +320,7 @@ export const Dimension = styled.div<{ enabled?: boolean }>` height: 100%; width: 100%; top: 0; + left: 0; opacity: 0.5; } `} @@ -289,6 +340,7 @@ export const DimensionTitle = styled.div<{ enabled?: boolean }>` export const CheckboxWrapper = styled.div` display: flex; position: relative; + z-index: 1; `; export const Checkbox = styled.input` @@ -459,17 +511,26 @@ export const MetricConfigurationWrapper = styled.div` width: 100%; display: flex; justify-content: space-between; - gap: 126px; overflow-y: hidden; + + @media only screen and (max-width: ${METRICS_VIEW_CONTAINER_BREAKPOINT}px) { + flex-direction: column; + } `; export const DefinitionsDisplayContainer = styled.div` display: flex; flex-direction: column; flex: 1 1 55%; - padding-top: 48px; - padding-right: 12px; + padding: 48px 12px 50px 126px; overflow-y: scroll; + + @media only screen and (max-width: ${METRICS_VIEW_CONTAINER_BREAKPOINT}px) { + border-top: 1px solid ${palette.highlight.grey3}; + padding: 30px 0 50px 0; + overflow-y: unset; + margin-right: 12px; + } `; export const DefinitionsDisplay = styled.div` @@ -531,6 +592,7 @@ export const DefinitionSelection = styled.div` export const DefinitionMiniButton = styled(RevertToDefaultButton)<{ selected?: boolean; + showDefault?: boolean; }>` width: unset; padding: 9px 16px; @@ -556,6 +618,9 @@ export const DefinitionMiniButton = styled(RevertToDefaultButton)<{ `}; + + ${({ showDefault, selected }) => + showDefault && !selected && `color: ${palette.highlight.grey4};`}; `; export const NoDefinitionsSelected = styled.div` diff --git a/publisher/src/components/MetricConfiguration/MetricConfiguration.tsx b/publisher/src/components/MetricConfiguration/MetricConfiguration.tsx index 8b2e75de1..534c56388 100644 --- a/publisher/src/components/MetricConfiguration/MetricConfiguration.tsx +++ b/publisher/src/components/MetricConfiguration/MetricConfiguration.tsx @@ -23,11 +23,14 @@ import React, { useEffect, useRef, useState } from "react"; import { ListOfMetricsForNavigation } from "../../pages/Settings"; import { Metric as MetricType, + MetricConfigurationSettings, MetricDisaggregationDimensions, + MetricDisaggregations, ReportFrequency, } from "../../shared/types"; import { useStore } from "../../stores"; import { removeSnakeCase } from "../../utils"; +import { ReactComponent as RightArrowIcon } from "../assets/right-arrow.svg"; import { Badge } from "../Badge"; import { Loading } from "../Loading"; import { TabbedBar, TabbedItem, TabbedOptions } from "../Reports"; @@ -37,6 +40,7 @@ import { Configuration, Metric, MetricBox, + MetricBoxBottomPaddingContainer, MetricConfigurationDisplay, MetricConfigurationWrapper, MetricDefinitions, @@ -51,11 +55,14 @@ export type MetricSettingsUpdateOptions = | "METRIC" | "DISAGGREGATION" | "DIMENSION" - | "CONTEXT"; + | "CONTEXT" + | "METRIC_SETTING" + | "DIMENSION_SETTING"; export type MetricSettings = { key: string; enabled?: boolean; + settings?: MetricConfigurationSettings[]; contexts?: { key: string; value: string; @@ -65,7 +72,8 @@ export type MetricSettings = { enabled?: boolean; dimensions?: { key: string; - enabled: boolean; + enabled?: boolean; + settings?: MetricConfigurationSettings[]; }[]; }[]; }; @@ -83,14 +91,14 @@ export const MetricConfiguration: React.FC<{ }> = observer(({ activeMetricKey, setActiveMetricKey, setListOfMetrics }) => { const { reportStore, userStore } = useStore(); - const [activeMetricFilter, setActiveMetricFilter] = useState(); const [isLoading, setIsLoading] = useState(true); const [loadingError, setLoadingError] = useState(); - const [activeDimension, setActiveDimension] = - useState(); + const [activeMetricFilter, setActiveMetricFilter] = useState(); const [metricSettings, setMetricSettings] = useState<{ [key: string]: MetricType; }>({}); + const [activeDimension, setActiveDimension] = + useState(); const filteredMetricSettings: MetricSettingsObj = Object.values( metricSettings @@ -104,9 +112,12 @@ export const MetricConfiguration: React.FC<{ return res; }, {}); - /** Updates shared state `listOfMetrics` so the SettingsMenu component can render the metric navigation */ + const [activeDisaggregation, setActiveDisaggregation] = + useState(); + useEffect( () => { + /** Updates shared state `listOfMetrics` so the SettingsMenu component can render the metric navigation */ const listOfMetricsForMetricNavigation = Object.values( filteredMetricSettings ).map((metric) => { @@ -117,6 +128,18 @@ export const MetricConfiguration: React.FC<{ }); setListOfMetrics(listOfMetricsForMetricNavigation); + + /** Update activeDimension when settings are updated */ + if (activeDimension && activeMetricKey) { + return setActiveDimension((prev) => { + return filteredMetricSettings[activeMetricKey].disaggregations + .find( + (disaggregation) => + disaggregation.key === activeDisaggregation?.key + ) + ?.dimensions.find((dimension) => dimension.key === prev?.key); + }); + } }, // eslint-disable-next-line react-hooks/exhaustive-deps [filteredMetricSettings] @@ -139,6 +162,34 @@ export const MetricConfiguration: React.FC<{ }; } + if (typeOfUpdate === "METRIC_SETTING") { + let updatedSettingsArray; + + if ( + prev[metricKey].settings?.length === updatedSetting.settings?.length + ) { + updatedSettingsArray = updatedSetting.settings; + } else { + updatedSettingsArray = prev[metricKey].settings?.map((setting) => { + if (setting.key === updatedSetting.settings?.[0].key) { + return { + ...setting, + included: updatedSetting.settings[0].included, + }; + } + return setting; + }); + } + + return { + ...prev, + [updatedSetting.key]: { + ...prev[metricKey], + settings: updatedSettingsArray, + }, + }; + } + if (typeOfUpdate === "DISAGGREGATION") { const updatedDisaggregations = prev[metricKey].disaggregations.map( (disaggregation) => { @@ -229,6 +280,7 @@ export const MetricConfiguration: React.FC<{ return { ...disaggregation, + enabled: true, dimensions: disaggregation.dimensions.map((dimension) => { if ( dimension.key === @@ -259,6 +311,71 @@ export const MetricConfiguration: React.FC<{ }; } + if (typeOfUpdate === "DIMENSION_SETTING") { + const updatedDisaggregations = prev[metricKey].disaggregations.map( + (disaggregation) => { + if ( + disaggregation.key !== updatedSetting.disaggregations?.[0].key + ) { + return disaggregation; + } + + return { + ...disaggregation, + dimensions: disaggregation.dimensions.map((dimension) => { + if ( + dimension.key !== + updatedSetting.disaggregations?.[0].dimensions?.[0].key + ) { + return dimension; + } + + let updatedSettingsArray; + + if ( + dimension.settings?.length === + updatedSetting.disaggregations?.[0].dimensions?.[0].settings + ?.length + ) { + updatedSettingsArray = + updatedSetting.disaggregations?.[0].dimensions?.[0] + .settings; + } else { + updatedSettingsArray = dimension.settings?.map((setting) => { + if ( + setting.key !== + updatedSetting.disaggregations?.[0].dimensions?.[0] + .settings?.[0].key + ) { + return setting; + } + return { + ...setting, + included: + updatedSetting.disaggregations?.[0].dimensions?.[0] + .settings[0].included, + }; + }); + } + + return { + ...dimension, + settings: updatedSettingsArray, + }; + }), + }; + } + ); + + return { + ...prev, + [updatedSetting.key]: { + ...prev[metricKey], + disaggregations: updatedDisaggregations, + }, + }; + } + if (typeOfUpdate === "CONTEXT") { const updatedContext = prev[metricKey].contexts.map((context) => { if (context.key === updatedSetting.contexts?.[0].key) { @@ -379,44 +496,48 @@ export const MetricConfiguration: React.FC<{ return ( <> - {!activeMetricKey && ( - - - - {userStore.currentAgency?.systems.map((filterOption) => ( - - setActiveMetricFilter(removeSnakeCase(filterOption)) - } - capitalize - > - {removeSnakeCase(filterOption.toLowerCase())} - - ))} - - - - )} + {!activeMetricKey && + userStore.currentAgency?.systems && + userStore.currentAgency?.systems?.length > 1 && ( + + + + {userStore.currentAgency?.systems.map((filterOption) => ( + + setActiveMetricFilter(removeSnakeCase(filterOption)) + } + capitalize + > + {removeSnakeCase(filterOption.toLowerCase())} + + ))} + + + + )} {/* List Of Metrics */} - {filteredMetricSettings && - !activeMetricKey && - Object.values(filteredMetricSettings).map((metric) => ( - - ))} + {filteredMetricSettings && !activeMetricKey && ( + + {Object.values(filteredMetricSettings).map((metric) => ( + + ))} + + )} {/* Metric Configuration */} {activeMetricKey && ( @@ -431,19 +552,26 @@ export const MetricConfiguration: React.FC<{ ← Back to Metrics - setActiveDimension(undefined)}> + setActiveDimension(undefined)} + inView={!activeDimension} + > {metricSettings[activeMetricKey]?.display_name} - {metricSettings[activeMetricKey]?.frequency} + {metricSettings[activeMetricKey]?.frequency?.toLowerCase()} + @@ -453,8 +581,9 @@ export const MetricConfiguration: React.FC<{ {/* Metric/Dimension Definitions (Includes/Excludes) */} diff --git a/publisher/src/components/MetricConfiguration/MetricDefinitions.tsx b/publisher/src/components/MetricConfiguration/MetricDefinitions.tsx index 6d7a89584..9bbf4852f 100644 --- a/publisher/src/components/MetricConfiguration/MetricDefinitions.tsx +++ b/publisher/src/components/MetricConfiguration/MetricDefinitions.tsx @@ -15,13 +15,14 @@ // along with this program. If not, see . // ============================================================================= -import React, { Fragment, useEffect, useState } from "react"; +import React, { Fragment, useState } from "react"; import { Metric, MetricConfigurationSettingsOptions, MetricContext, MetricDisaggregationDimensions, + MetricDisaggregations, } from "../../shared/types"; import { ContextConfiguration, @@ -42,8 +43,9 @@ import { type MetricDefinitionsProps = { activeMetricKey: string; - filteredMetricSettings: { [key: string]: Metric }; + activeMetric: Metric; activeDimension?: MetricDisaggregationDimensions | undefined; + activeDisaggregation: MetricDisaggregations | undefined; contexts: MetricContext[]; saveAndUpdateMetricSettings: ( typeOfUpdate: MetricSettingsUpdateOptions, @@ -54,84 +56,72 @@ type MetricDefinitionsProps = { export const MetricDefinitions: React.FC = ({ activeMetricKey, - filteredMetricSettings, + activeMetric, activeDimension, + activeDisaggregation, contexts, saveAndUpdateMetricSettings, }) => { + const [showDefaultSettings, setShowDefaultSettings] = useState(false); + const selectionOptions: MetricConfigurationSettingsOptions[] = [ "N/A", "No", "Yes", ]; + const activeDimensionOrMetric: MetricDisaggregationDimensions | Metric = + activeDimension || activeMetric; - /** TODO(#82): Remove mocks when GET & POST are implemented */ - /** Mocks To Be Removed */ - const generateMockDefinitions = (): - | MetricDisaggregationDimensions - | Metric => { - const dimensionOrMetric: MetricDisaggregationDimensions | Metric = - activeDimension || filteredMetricSettings[activeMetricKey]; - - const mockSettings = dimensionOrMetric - ? Array.from({ length: 10 }, (_, i) => ({ - key: `SETTING ${i}`, - label: `Includes/Excludes Q#${i + 1} for ${ - "display_name" in dimensionOrMetric - ? dimensionOrMetric.display_name - : dimensionOrMetric.label - }?`, - included: selectionOptions[i % 3], - default: selectionOptions[i % 3], - })) - : []; - - return { ...dimensionOrMetric, settings: mockSettings }; + const isMetricSettings = ( + dimensionOrMetric: MetricDisaggregationDimensions | Metric + ): dimensionOrMetric is Metric => { + return (dimensionOrMetric as Metric).display_name !== undefined; }; - const initialDefinitionsToDisplay = generateMockDefinitions(); - - const [mockDefinitionsToDisplay, setMockDefinitionsToDisplay] = useState< - MetricDisaggregationDimensions | Metric - >(initialDefinitionsToDisplay); - - const mockUpdateSelection = ( - key: string, - selection: MetricConfigurationSettingsOptions - ) => { - setMockDefinitionsToDisplay((prev) => { - return { - ...prev, - settings: prev.settings?.map((setting) => { - if (setting.key === key) { - return { ...setting, included: selection }; - } - - return setting; - }), - }; - }); + const activeSettings = isMetricSettings(activeDimensionOrMetric) + ? activeMetric.settings + : activeDimension?.settings; + const defaultSettings = activeSettings?.map((setting) => ({ + ...setting, + included: setting.default, + })); + + const revertToDefaultValues = () => { + if (isMetricSettings(activeDimensionOrMetric)) { + return saveAndUpdateMetricSettings("METRIC_SETTING", { + key: activeMetricKey, + settings: defaultSettings, + }); + } + + if (activeDisaggregation && activeDimension) { + saveAndUpdateMetricSettings("DIMENSION_SETTING", { + key: activeMetricKey, + disaggregations: [ + { + key: activeDisaggregation.key, + dimensions: [ + { + key: activeDimension.key, + settings: defaultSettings, + }, + ], + }, + ], + }); + } }; - useEffect( - () => { - setMockDefinitionsToDisplay(initialDefinitionsToDisplay); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [activeMetricKey, activeDimension] - ); - /** End of Mocks To Be Removed */ - return ( - {"display_name" in mockDefinitionsToDisplay - ? mockDefinitionsToDisplay.display_name - : mockDefinitionsToDisplay.label} + {isMetricSettings(activeDimensionOrMetric) + ? activeDimensionOrMetric.display_name + : activeDimensionOrMetric.label} - {mockDefinitionsToDisplay?.settings?.length && ( + {Boolean(activeSettings?.length) && ( <> Definitions @@ -144,40 +134,89 @@ export const MetricDefinitions: React.FC = ({ - setMockDefinitionsToDisplay(initialDefinitionsToDisplay) + onClick={() => { + setShowDefaultSettings(false); + revertToDefaultValues(); + }} + onMouseEnter={() => + !showDefaultSettings && setShowDefaultSettings(true) } + onMouseLeave={() => setShowDefaultSettings(false)} > - Revert to Default Definition + Choose Default Definition - {mockDefinitionsToDisplay?.settings?.map((setting) => ( - - {setting.label} - - - {selectionOptions.map((option) => ( - - - mockUpdateSelection(setting.key, option) - } - > - {option} - - - ))} - - - ))} + {(showDefaultSettings ? defaultSettings : activeSettings)?.map( + (setting) => ( + + + {setting.label} + + + + {selectionOptions.map((option) => ( + + { + if (isMetricSettings(activeDimensionOrMetric)) { + return saveAndUpdateMetricSettings( + "METRIC_SETTING", + { + key: activeMetricKey, + settings: [ + { ...setting, included: option }, + ], + } + ); + } + + const activeDimensionKey = + activeDimensionOrMetric.key; + + return ( + activeDisaggregation && + saveAndUpdateMetricSettings( + "DIMENSION_SETTING", + { + key: activeMetricKey, + disaggregations: [ + { + key: activeDisaggregation.key, + dimensions: [ + { + key: activeDimensionKey, + settings: [ + { + ...setting, + included: option, + }, + ], + }, + ], + }, + ], + } + ) + ); + }} + > + {option} + + + ))} + + + ) + )} )} {/* Display when user is viewing a dimension & there are no settings available */} - {!mockDefinitionsToDisplay?.settings?.length && activeDimension && ( + {!activeSettings?.length && activeDimension && ( This breakdown has no customizations available yet. diff --git a/publisher/src/components/Settings/Settings.styles.tsx b/publisher/src/components/Settings/Settings.styles.tsx index 76da9c637..7678c3c5f 100644 --- a/publisher/src/components/Settings/Settings.styles.tsx +++ b/publisher/src/components/Settings/Settings.styles.tsx @@ -24,7 +24,7 @@ export const SettingsContainer = styled.div` width: 100%; display: flex; align-items: flex-start; - padding: 39px 24px 0 24px; + padding: 48px 24px 0 24px; position: fixed; overflow-y: scroll; `; @@ -45,7 +45,6 @@ export const SettingsMenuContainer = styled.div` flex: 0 0 auto; flex-direction: column; gap: 16px; - padding: 16px 24px; margin-right: 100px; `; diff --git a/publisher/src/components/assets/right-arrow.svg b/publisher/src/components/assets/right-arrow.svg new file mode 100644 index 000000000..5b1b065a2 --- /dev/null +++ b/publisher/src/components/assets/right-arrow.svg @@ -0,0 +1,3 @@ + + +