Skip to content

Commit 319fde6

Browse files
authored
Merge pull request #1552 from visualize-admin/style/metadata-button
style: Update metadata and filters positions + styles
2 parents 92532b7 + afc4872 commit 319fde6

7 files changed

Lines changed: 197 additions & 139 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ You can also check the [release page](https://github.com/visualize-admin/visuali
1212
- Features
1313
- Add the "Free canvas" layout, allowing users to freely resize and move charts for dashboards
1414
- Ability to start a chart from another dataset than the current one
15-
15+
- Style
16+
- Improved the styles of metadata panel and interactive filters toggle buttons
17+
1618
# [4.5.1] - 2024-05-21
1719

1820
- Fixes

app/charts/shared/chart-data-filters.tsx

Lines changed: 102 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import {
4040
import { useTimeFormatLocale } from "@/formatters";
4141
import { useDataCubesComponentsQuery } from "@/graphql/hooks";
4242
import {
43-
DataCubeObservationFilter,
4443
PossibleFiltersDocument,
4544
PossibleFiltersQuery,
4645
PossibleFiltersQueryVariables,
@@ -52,6 +51,7 @@ import {
5251
useChartInteractiveFilters,
5352
useInteractiveFiltersGetState,
5453
} from "@/stores/interactive-filters";
54+
import { assert } from "@/utils/assert";
5555
import { hierarchyToOptions } from "@/utils/hierarchy";
5656
import useEvent from "@/utils/use-event";
5757

@@ -62,64 +62,72 @@ type PreparedFilter = {
6262
mappedFilters: Filters;
6363
};
6464

65-
type ChartDataFiltersProps = {
65+
export const useChartDataFiltersState = ({
66+
dataSource,
67+
chartConfig,
68+
}: {
6669
dataSource: DataSource;
6770
chartConfig: ChartConfig;
68-
};
69-
70-
export const ChartDataFilters = (props: ChartDataFiltersProps) => {
71-
const { dataSource, chartConfig } = props;
71+
}) => {
72+
const componentIris =
73+
chartConfig.interactiveFiltersConfig?.dataFilters.componentIris;
74+
assert(componentIris, "Data filters are not enabled for this chart.");
75+
const [open, setOpen] = useState(false);
76+
useEffect(() => {
77+
if (componentIris.length === 0) {
78+
setOpen(false);
79+
}
80+
}, [componentIris.length]);
7281
const { loading } = useLoadingState();
73-
const dataFilters = useChartInteractiveFilters((d) => d.dataFilters);
74-
const componentIris = chartConfig.interactiveFiltersConfig?.dataFilters
75-
.componentIris as string[];
7682
const queryFilters = useQueryFilters({
7783
chartConfig,
7884
allowNoneValues: true,
7985
componentIris,
8086
});
81-
const [filtersVisible, setFiltersVisible] = useState(false);
82-
83-
useEffect(() => {
84-
if (componentIris.length === 0) {
85-
setFiltersVisible(false);
86-
}
87-
}, [componentIris.length]);
88-
89-
const preparedFilters: PreparedFilter[] | undefined = useMemo(() => {
87+
const preparedFilters = useMemo(() => {
9088
return chartConfig.cubes.map((cube) => {
91-
const cubeQueryFilters = queryFilters.find(
92-
(d) => d.iri === cube.iri
93-
) as DataCubeObservationFilter;
89+
const cubeQueryFilters = queryFilters.find((d) => d.iri === cube.iri);
90+
assert(cubeQueryFilters, "Cube query filters not found.");
9491
const filtersByMappingStatus = getFiltersByMappingStatus(chartConfig, {
9592
cubeIri: cube.iri,
9693
});
9794
const { unmappedFilters, mappedFilters } = filtersByMappingStatus;
9895
const unmappedKeys = Object.keys(unmappedFilters);
99-
const unmappedFiltersArray = Object.entries(
96+
const unmappedEntries = Object.entries(
10097
cubeQueryFilters.filters as Filters
10198
).filter(([k]) => unmappedKeys.includes(k));
102-
const interactiveFiltersArray = unmappedFiltersArray.filter(([k]) =>
99+
const interactiveFiltersList = unmappedEntries.filter(([k]) =>
103100
componentIris.includes(k)
104101
);
105-
106102
return {
107103
cubeIri: cube.iri,
108-
interactiveFilters: Object.fromEntries(interactiveFiltersArray),
109-
unmappedFilters: Object.fromEntries(
110-
unmappedFiltersArray
111-
) as SingleFilters,
104+
interactiveFilters: Object.fromEntries(interactiveFiltersList),
105+
unmappedFilters: Object.fromEntries(unmappedEntries) as SingleFilters,
112106
mappedFilters,
113107
};
114108
});
115109
}, [chartConfig, componentIris, queryFilters]);
116-
117110
const { error } = useEnsurePossibleInteractiveFilters({
118111
dataSource,
119112
chartConfig,
120113
preparedFilters,
121114
});
115+
return {
116+
open,
117+
setOpen,
118+
dataSource,
119+
chartConfig,
120+
loading,
121+
error,
122+
preparedFilters,
123+
componentIris,
124+
};
125+
};
122126

127+
export const ChartDataFiltersToggle = (
128+
props: ReturnType<typeof useChartDataFiltersState>
129+
) => {
130+
const { open, setOpen, loading, error, componentIris } = props;
123131
return error ? (
124132
<Typography variant="body2" color="error">
125133
<Trans id="controls.section.data.filters.possible-filters-error">
@@ -128,10 +136,9 @@ export const ChartDataFilters = (props: ChartDataFiltersProps) => {
128136
</Trans>
129137
</Typography>
130138
) : (
131-
<Flex sx={{ flexDirection: "column", mt: 4 }}>
139+
<Flex sx={{ flexDirection: "column", width: "100%" }}>
132140
<Flex
133141
sx={{
134-
justifyContent: "flex-end",
135142
alignItems: "flex-start",
136143
gap: 3,
137144
minHeight: 20,
@@ -140,91 +147,107 @@ export const ChartDataFilters = (props: ChartDataFiltersProps) => {
140147
{componentIris.length > 0 && (
141148
<Button
142149
variant="text"
150+
color="primary"
151+
size="small"
143152
endIcon={
144153
<Icon
145154
name="add"
146155
size={16}
147156
style={{
148-
transform: filtersVisible ? "rotate(45deg)" : "rotate(0deg)",
157+
transform: open ? "rotate(45deg)" : "rotate(0deg)",
149158
transition: "transform 0.2s ease-in-out",
150159
}}
151160
/>
152161
}
153162
sx={{
154163
display: "flex",
155-
fontSize: ["0.75rem", "0.75rem", "0.75rem"],
156164
alignItems: "center",
157165
minWidth: "fit-content",
158166
minHeight: 0,
167+
ml: -2,
159168
px: 2,
160169
py: 1,
161170
}}
162-
onClick={() => setFiltersVisible(!filtersVisible)}
171+
onClick={() => setOpen(!open)}
163172
>
164173
{loading && (
165174
<span style={{ marginTop: "0.1rem", marginRight: "0.5rem" }}>
166175
<LoadingIndicator />
167176
</span>
168177
)}
169-
{filtersVisible ? (
170-
<Trans id="interactive.data.filters.hide">Hide Filters</Trans>
171-
) : (
172-
<Trans id="interactive.data.filters.show">Show Filters</Trans>
173-
)}
178+
<Typography variant="body2">
179+
{open ? (
180+
<Trans id="interactive.data.filters.hide">Hide Filters</Trans>
181+
) : (
182+
<Trans id="interactive.data.filters.show">Show Filters</Trans>
183+
)}
184+
</Typography>
174185
</Button>
175186
)}
176187
</Flex>
177-
178-
{componentIris.length > 0 && (
179-
<Box
180-
data-testid="published-chart-interactive-filters"
181-
sx={{
182-
display: filtersVisible ? "grid" : "none",
183-
columnGap: 3,
184-
rowGap: 2,
185-
gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))",
186-
}}
187-
>
188-
{preparedFilters?.map(({ cubeIri, interactiveFilters }) =>
189-
Object.keys(interactiveFilters).map((dimensionIri) => (
190-
<DataFilter
191-
key={dimensionIri}
192-
cubeIri={cubeIri}
193-
dimensionIri={dimensionIri}
194-
dataSource={dataSource}
195-
chartConfig={chartConfig}
196-
dataFilters={dataFilters}
197-
interactiveFilters={interactiveFilters}
198-
disabled={loading}
199-
/>
200-
))
201-
)}
202-
</Box>
203-
)}
204188
</Flex>
205189
);
206190
};
207191

208-
type DataFilterProps = {
192+
export const ChartDataFiltersList = (
193+
props: ReturnType<typeof useChartDataFiltersState>
194+
) => {
195+
const {
196+
open,
197+
dataSource,
198+
chartConfig,
199+
loading,
200+
preparedFilters,
201+
componentIris,
202+
} = props;
203+
const dataFilters = useChartInteractiveFilters((d) => d.dataFilters);
204+
return componentIris.length > 0 ? (
205+
<Box
206+
data-testid="published-chart-interactive-filters"
207+
sx={{
208+
display: open ? "grid" : "none",
209+
columnGap: 3,
210+
rowGap: 2,
211+
gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))",
212+
}}
213+
>
214+
{preparedFilters.map(({ cubeIri, interactiveFilters }) => {
215+
return Object.keys(interactiveFilters).map((dimensionIri) => {
216+
return (
217+
<DataFilter
218+
key={dimensionIri}
219+
cubeIri={cubeIri}
220+
dimensionIri={dimensionIri}
221+
dataSource={dataSource}
222+
chartConfig={chartConfig}
223+
dataFilters={dataFilters}
224+
interactiveFilters={interactiveFilters}
225+
disabled={loading}
226+
/>
227+
);
228+
});
229+
})}
230+
</Box>
231+
) : null;
232+
};
233+
234+
const DataFilter = ({
235+
cubeIri,
236+
dimensionIri,
237+
dataSource,
238+
chartConfig,
239+
dataFilters,
240+
interactiveFilters,
241+
disabled,
242+
}: {
209243
cubeIri: string;
210244
dimensionIri: string;
211245
dataSource: DataSource;
212246
chartConfig: ChartConfig;
213247
dataFilters: DataFilters;
214248
interactiveFilters: Filters;
215249
disabled: boolean;
216-
};
217-
218-
const DataFilter = (props: DataFilterProps) => {
219-
const {
220-
cubeIri,
221-
dimensionIri,
222-
dataSource,
223-
chartConfig,
224-
dataFilters,
225-
interactiveFilters,
226-
disabled,
227-
} = props;
250+
}) => {
228251
const locale = useLocale();
229252
const filters = useChartConfigFilters(chartConfig);
230253
const chartLoadingState = useLoadingState();

0 commit comments

Comments
 (0)