diff --git a/docs/customization.md b/docs/customization.md index 4d20fc3d0..601761269 100644 --- a/docs/customization.md +++ b/docs/customization.md @@ -46,6 +46,21 @@ In json files, you can just override the primary keys you need. You have to over - `home.json` to define homepage settings. + - `activityBar`: This is a menu showing all search types (hiking practices, outdoor practices, tourist content categories and tourist events categories). + + - `shouldDisplay`: Boolean allowing this menu to be displayed or not. Its default value is `true`. + - `numberOfItemsBeforeTruncation` The number of items displayed on the screen. To see the others, click on the "Show more" button. Its default value is `8`. + - `links`: Allows you to customize the order and display of categories links. It's an array containing an object with 3 properties: + ```typescript + { + "type" : 'trek' | 'outdoorSite' | 'touristicContent' | 'touristicEvent' ; + "grouped" : boolean ; // If set to "true", all activities of the type are grouped under a single link. + "iconUrl" : string ; // Optional, url to replace default icon. Used only if "grouped" is set to "true", + } + ``` + + More explanations in this [comments](https://github.com/GeotrekCE/Geotrek-rando-v3/issues/560#issuecomment-1858166341) (in French). + - `suggestions`: You can define blocks to display suggestions groups with treks ID, outdoor sites ID, services ID or events ID to highlight on homepage (see https://github.com/GeotrekCE/Geotrek-rando-v3/blob/main/frontend/customization/config/home.json). Each group has the following properties : diff --git a/frontend/config/home.json b/frontend/config/home.json index 79288d93f..205d1e91d 100644 --- a/frontend/config/home.json +++ b/frontend/config/home.json @@ -8,7 +8,24 @@ "shouldDisplayText": true }, "activityBar": { - "shouldDisplay": true + "shouldDisplay": true, + "numberOfItemsBeforeTruncation": 8, + "links": [{ + "type": "trek", + "grouped": false + }, + { + "type": "outdoorSite", + "grouped": false + }, + { + "type": "touristicContent", + "grouped": false + }, + { + "type": "touristicEvent", + "grouped": false + }] }, "suggestions": { "default": [] diff --git a/frontend/customization/config/home.json b/frontend/customization/config/home.json index 60b471703..1cb315c49 100644 --- a/frontend/customization/config/home.json +++ b/frontend/customization/config/home.json @@ -8,7 +8,24 @@ "shouldDisplayText": true }, "activityBar": { - "shouldDisplay": true + "shouldDisplay": true, + "numberOfItemsBeforeTruncation": 8, + "links": [{ + "type": "trek", + "grouped": false + }, + { + "type": "outdoorSite", + "grouped": false + }, + { + "type": "touristicContent", + "grouped": false + }, + { + "type": "touristicEvent", + "grouped": false + }] }, "suggestions": { "default": [ diff --git a/frontend/src/components/ActivitySearchFilter/ActivityButton/ActivityButton.tsx b/frontend/src/components/ActivitySearchFilter/ActivityButton/ActivityButton.tsx index 9ff43e363..a088be3a4 100644 --- a/frontend/src/components/ActivitySearchFilter/ActivityButton/ActivityButton.tsx +++ b/frontend/src/components/ActivitySearchFilter/ActivityButton/ActivityButton.tsx @@ -1,23 +1,55 @@ -import React from 'react'; import SVG from 'react-inlinesvg'; +import getConfig from 'next/config'; +import styled from 'styled-components'; import { optimizeAndDefineColor } from 'stylesheet'; import { Link } from 'components/Link'; +import getActivityColor from 'components/pages/search/components/ResultCard/getActivityColor'; +import { ActivityFilter } from 'modules/activities/interface'; interface Props { iconUrl: string; href: string; label: string; + type: ActivityFilter['type']; } -export const ActivityButton: React.FC = ({ iconUrl, href, label }) => { +const { + publicRuntimeConfig: { colors }, +} = getConfig(); + +const getColor = (type: ActivityFilter['type']) => { + if (type === 'PRACTICE') { + return getActivityColor('practices'); + } + if (type === 'OUTDOOR_PRACTICE') { + return getActivityColor('outdoorPractice'); + } + if (type === 'CATEGORY') { + return getActivityColor('categories'); + } + if (type === 'TOURISTIC_EVENT_TYPE') { + return getActivityColor('event'); + } + return colors.primary3; +}; + +export const ActivityButton: React.FC = ({ iconUrl, href, label, type }) => { return ( - {label} - + ); }; + +const StyleLink = styled(Link)<{ $color?: string }>` + &:hover, + &:focus { + color: ${props => props.$color}; + } +`; diff --git a/frontend/src/components/ActivitySearchFilter/ActivitySearchFilter.tsx b/frontend/src/components/ActivitySearchFilter/ActivitySearchFilter.tsx index 21d7bf91d..4423650e7 100644 --- a/frontend/src/components/ActivitySearchFilter/ActivitySearchFilter.tsx +++ b/frontend/src/components/ActivitySearchFilter/ActivitySearchFilter.tsx @@ -1,33 +1,26 @@ import { ChevronDown } from 'components/Icons/ChevronDown'; import { MoreHorizontal } from 'components/Icons/MoreHorizontal'; -import React from 'react'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import { routes } from 'services/routes'; import { ActivityFilter } from 'modules/activities/interface'; import { CATEGORY_ID, EVENT_ID, OUTDOOR_ID, PRACTICE_ID } from 'modules/filters/constant'; +import { cn } from 'services/utils/cn'; import { ActivityButton } from './ActivityButton'; import { useActivitySearchFilter } from './useActivitySearchFilter'; import { ActivitySearchFilterMobile } from './ActivitySearchFilterMobile'; interface Props { className?: string; + itemsToDisplayBeforeTruncation: number; } -const MAX_VISIBLE_ACTIVITIES = 8; - -export const ActivitySearchFilter: React.FC = ({ className }) => { +export const ActivitySearchFilter: React.FC = ({ + className, + itemsToDisplayBeforeTruncation = 8, +}) => { const { activities, expandedState, toggleExpandedState } = useActivitySearchFilter(); - - const collapseIsNeeded: boolean = - activities !== undefined && activities.length > MAX_VISIBLE_ACTIVITIES; - - const visibleActivities: ActivityFilter[] | undefined = - activities !== undefined - ? collapseIsNeeded && expandedState === 'COLLAPSED' - ? activities.slice(0, MAX_VISIBLE_ACTIVITIES) - : activities - : undefined; + const intl = useIntl(); const getId = (type: string) => { if (type === 'PRACTICE') return PRACTICE_ID; @@ -37,41 +30,54 @@ export const ActivitySearchFilter: React.FC = ({ className }) => { return CATEGORY_ID; }; + if (!activities) { + return null; + } + + const collapseIsNeeded: boolean = activities.length > itemsToDisplayBeforeTruncation; + + const visibleActivities: ActivityFilter[] = + collapseIsNeeded && expandedState === 'COLLAPSED' + ? activities.slice(0, itemsToDisplayBeforeTruncation) + : activities; + return ( -
- {activities !== undefined && ( - <> - + + + )} +
+
+ +
+ ); }; diff --git a/frontend/src/components/ActivitySearchFilter/ActivitySearchFilterMobile/index.tsx b/frontend/src/components/ActivitySearchFilter/ActivitySearchFilterMobile/index.tsx index 3c6ddd55b..a38a2c656 100644 --- a/frontend/src/components/ActivitySearchFilter/ActivitySearchFilterMobile/index.tsx +++ b/frontend/src/components/ActivitySearchFilter/ActivitySearchFilterMobile/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import Select, { CSSObjectWithLabel } from 'react-select'; -import { FormattedMessage } from 'react-intl'; +import { FormattedMessage, useIntl } from 'react-intl'; import { colorPalette, getSpacing, shadow } from 'stylesheet'; import { routes } from 'services/routes'; @@ -17,6 +17,7 @@ export const ActivitySearchFilterMobile: React.FC<{ getId: (type: string) => string; }> = ({ className = '', activities, getId }) => { const { selectedActivityId, updateSelectedActivityId } = useActivitySearchFilterMobile(); + const intl = useIntl(); const selectedActivity = activities.find( ({ id, type }) => `${type}-${id}` === selectedActivityId, @@ -26,9 +27,9 @@ export const ActivitySearchFilterMobile: React.FC<{