diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index 986324175..b01e526d5 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -1100,6 +1100,31 @@ msgstr "" msgid "calendarBlockSidebarTitle" msgstr "" +#: overrideTranslations +# defaultMessage: Clear selection +msgid "calendarClearDates" +msgstr "" + +#: overrideTranslations +# defaultMessage: Invalid value +msgid "calendarInvalid" +msgstr "" + +#: overrideTranslations +# defaultMessage: Month +msgid "calendarMonth" +msgstr "" + +#: overrideTranslations +# defaultMessage: Open calendar +msgid "calendarOpenCalendar" +msgstr "" + +#: overrideTranslations +# defaultMessage: Year +msgid "calendarYear" +msgstr "" + #: components/ItaliaTheme/Blocks/Calendar/Body # defaultMessage: Prossimo msgid "calendar_next_arrow" diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 6a0c569ee..9630a928e 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -1085,6 +1085,31 @@ msgstr "Navigation path" msgid "calendarBlockSidebarTitle" msgstr "" +#: overrideTranslations +# defaultMessage: Clear selection +msgid "calendarClearDates" +msgstr "" + +#: overrideTranslations +# defaultMessage: Invalid value +msgid "calendarInvalid" +msgstr "" + +#: overrideTranslations +# defaultMessage: Month +msgid "calendarMonth" +msgstr "" + +#: overrideTranslations +# defaultMessage: Open calendar +msgid "calendarOpenCalendar" +msgstr "" + +#: overrideTranslations +# defaultMessage: Year +msgid "calendarYear" +msgstr "" + #: components/ItaliaTheme/Blocks/Calendar/Body # defaultMessage: Prossimo msgid "calendar_next_arrow" diff --git a/locales/es/LC_MESSAGES/volto.po b/locales/es/LC_MESSAGES/volto.po index af0188c2f..f92d0eb4a 100644 --- a/locales/es/LC_MESSAGES/volto.po +++ b/locales/es/LC_MESSAGES/volto.po @@ -1094,6 +1094,31 @@ msgstr "" msgid "calendarBlockSidebarTitle" msgstr "Calendario" +#: overrideTranslations +# defaultMessage: Clear selection +msgid "calendarClearDates" +msgstr "" + +#: overrideTranslations +# defaultMessage: Invalid value +msgid "calendarInvalid" +msgstr "" + +#: overrideTranslations +# defaultMessage: Month +msgid "calendarMonth" +msgstr "" + +#: overrideTranslations +# defaultMessage: Open calendar +msgid "calendarOpenCalendar" +msgstr "" + +#: overrideTranslations +# defaultMessage: Year +msgid "calendarYear" +msgstr "" + #: components/ItaliaTheme/Blocks/Calendar/Body # defaultMessage: Prossimo msgid "calendar_next_arrow" diff --git a/locales/fr/LC_MESSAGES/volto.po b/locales/fr/LC_MESSAGES/volto.po index 93196a701..9aaa5d5c7 100644 --- a/locales/fr/LC_MESSAGES/volto.po +++ b/locales/fr/LC_MESSAGES/volto.po @@ -1102,6 +1102,31 @@ msgstr "" msgid "calendarBlockSidebarTitle" msgstr "" +#: overrideTranslations +# defaultMessage: Clear selection +msgid "calendarClearDates" +msgstr "" + +#: overrideTranslations +# defaultMessage: Invalid value +msgid "calendarInvalid" +msgstr "" + +#: overrideTranslations +# defaultMessage: Month +msgid "calendarMonth" +msgstr "" + +#: overrideTranslations +# defaultMessage: Open calendar +msgid "calendarOpenCalendar" +msgstr "" + +#: overrideTranslations +# defaultMessage: Year +msgid "calendarYear" +msgstr "" + #: components/ItaliaTheme/Blocks/Calendar/Body # defaultMessage: Prossimo msgid "calendar_next_arrow" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index e1d3de2d7..55a9cbe96 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -1085,6 +1085,31 @@ msgstr "" msgid "calendarBlockSidebarTitle" msgstr "Calendario" +#: overrideTranslations +# defaultMessage: Clear selection +msgid "calendarClearDates" +msgstr "Cancella la selezione" + +#: overrideTranslations +# defaultMessage: Invalid value +msgid "calendarInvalid" +msgstr "Valore non valido" + +#: overrideTranslations +# defaultMessage: Month +msgid "calendarMonth" +msgstr "Mese" + +#: overrideTranslations +# defaultMessage: Open calendar +msgid "calendarOpenCalendar" +msgstr "Apri il calendario" + +#: overrideTranslations +# defaultMessage: Year +msgid "calendarYear" +msgstr "Anno" + #: components/ItaliaTheme/Blocks/Calendar/Body # defaultMessage: Prossimo msgid "calendar_next_arrow" diff --git a/locales/volto.pot b/locales/volto.pot index 845d7b47d..66fcaaf12 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2024-03-06T10:48:38.114Z\n" +"POT-Creation-Date: 2024-03-21T15:27:52.153Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -1087,6 +1087,31 @@ msgstr "" msgid "calendarBlockSidebarTitle" msgstr "" +#: overrideTranslations +# defaultMessage: Clear selection +msgid "calendarClearDates" +msgstr "" + +#: overrideTranslations +# defaultMessage: Invalid value +msgid "calendarInvalid" +msgstr "" + +#: overrideTranslations +# defaultMessage: Month +msgid "calendarMonth" +msgstr "" + +#: overrideTranslations +# defaultMessage: Open calendar +msgid "calendarOpenCalendar" +msgstr "" + +#: overrideTranslations +# defaultMessage: Year +msgid "calendarYear" +msgstr "" + #: components/ItaliaTheme/Blocks/Calendar/Body # defaultMessage: Prossimo msgid "calendar_next_arrow" diff --git a/package.json b/package.json index dd8c9ede5..498176c63 100644 --- a/package.json +++ b/package.json @@ -130,6 +130,7 @@ "design-react-kit": "5.0.0-1", "htmldiff-js": "1.0.5", "marked": "9.0.0", + "react-aria-components": "1.1.1", "react-dropzone": "11.0.1", "react-focus-lock": "2.9.4", "react-google-recaptcha-v3": "1.7.0", diff --git a/src/components/ItaliaTheme/Blocks/BandiSearch/Body.jsx b/src/components/ItaliaTheme/Blocks/BandiSearch/Body.jsx index b12cef130..e452b8540 100644 --- a/src/components/ItaliaTheme/Blocks/BandiSearch/Body.jsx +++ b/src/components/ItaliaTheme/Blocks/BandiSearch/Body.jsx @@ -9,6 +9,7 @@ import { getQueryStringResults } from '@plone/volto/actions'; import { flattenToAppURL } from '@plone/volto/helpers'; import BandiInEvidenceTemplate from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/Listing/BandiInEvidenceTemplate'; import { Pagination } from 'design-comuni-plone-theme/components/ItaliaTheme'; +import { generateFiltersProps } from 'design-comuni-plone-theme/helpers'; import FiltersConfig from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/BandiSearch/FiltersConfig'; @@ -32,6 +33,14 @@ const messages = defineMessages({ }, }); +const BANDI_SEARCH_FILTER_PROPS_OVERRIDES = { + date_filter: { + showInputLabels: false, + textColor: 'light', + controlsBackgroundColor: 'primary', + }, +}; + const Body = ({ data, inEditMode, path, onChangeBlock }) => { const intl = useIntl(); const b_size = 6; @@ -107,7 +116,6 @@ const Body = ({ data, inEditMode, path, onChangeBlock }) => { let newState = { ...state, }; - if (action.type === 'reset') { newState = { ...getInitialState(), @@ -141,6 +149,12 @@ const Body = ({ data, inEditMode, path, onChangeBlock }) => { setCurrentPage(current); doRequest(current); } + const onChangeHandler = (filter, value) => { + dispatchFilter({ + filter: filter, + value: value, + }); + }; return filterOne || filterTwo || filterThree ? ( @@ -160,36 +174,32 @@ const Body = ({ data, inEditMode, path, onChangeBlock }) => { {filterOne && ( <> {React.createElement(filterOne.widget.component, { - ...filterOne.widget?.props, - id: 'filterOne', - onChange: (filter, value) => { - dispatchFilter({ - filter: filter, - value: value, - }); - }, + ...generateFiltersProps( + filterOne, + 'filterOne', + onChangeHandler, + BANDI_SEARCH_FILTER_PROPS_OVERRIDES, + ), })} )} {filterTwo && React.createElement(filterTwo.widget?.component, { - ...filterTwo.widget?.props, - id: 'filterTwo', - onChange: (filter, value) => - dispatchFilter({ - filter: filter, - value: value, - }), + ...generateFiltersProps( + filterTwo, + 'filterTwo', + onChangeHandler, + BANDI_SEARCH_FILTER_PROPS_OVERRIDES, + ), })} {filterThree && React.createElement(filterThree.widget?.component, { - ...filterThree.widget?.props, - id: 'filterThree', - onChange: (filter, value) => - dispatchFilter({ - filter: filter, - value: value, - }), + ...generateFiltersProps( + filterThree, + 'filterThree', + onChangeHandler, + BANDI_SEARCH_FILTER_PROPS_OVERRIDES, + ), })} +
+ + +
+ + + + + ); +}; + +function MonthDropdown({ state }: CalendarDropdown) { + const intl = useIntl(); + const months: string[] = []; + const formatter = useDateFormatter({ + month: 'long', + timeZone: state.timeZone, + }); + const ref = useRef(null); + // Format the name of each month in the year according to the + // current locale and calendar system. + const numMonths = state.focusedDate.calendar.getMonthsInYear( + state.focusedDate, + ); + for (let i = 1; i <= numMonths; i++) { + const date = state.focusedDate.set({ month: i }); + months.push(formatter.format(date.toDate(state.timeZone))); + } + + const onChange = (key: Key) => { + const value = Number(key) + 1; + const date = state.focusedDate.set({ month: value }); + state.setFocusedDate(date); + }; + return ( + + ); +} + +function YearDropdown({ state }: CalendarDropdown) { + const intl = useIntl(); + const years: Years[] = []; + const formatter = useDateFormatter({ + year: 'numeric', + timeZone: state.timeZone, + }); + + // Format 20 years on each side of the current year according + // to the current locale and calendar system. + for (let i = -20; i <= 20; i++) { + const date = state.focusedDate.add({ years: i }); + years.push({ + value: date, + formatted: formatter.format(date.toDate(state.timeZone)), + }); + } + + const onChange = (key: Key) => { + const date = years.find((y) => y.formatted === key)?.value!; + state.setFocusedDate(date); + }; + return ( + + ); +} diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Button.css b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Button.css new file mode 100644 index 000000000..1e67dfac3 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Button.css @@ -0,0 +1,30 @@ +.react-aria-button { + /* color: var(--text-color); */ + /* background: var(--button-background); */ + /* border: 1px solid var(--border-color); */ + border-radius: 4px; + appearance: none; + vertical-align: middle; + font-size: 1rem; + text-align: center; + margin: 0; + outline: none; + padding: 6px 10px; + text-decoration: none; + + &[data-pressed] { + box-shadow: inset 0 1px 2px rgb(0 0 0 / 0.1); + /* background: var(--button-background-pressed); */ + /* border-color: var(--border-color-pressed); */ + } + + &[data-focus-visible] { + /* outline: 2px solid var(--focus-ring-color); */ + outline-offset: -1px; + } + + &[data-disabled] { + /* border-color: var(--border-color-disabled); */ + /* color: var(--text-color-disabled); */ + } +} diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Button.tsx b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Button.tsx new file mode 100644 index 000000000..3d6c46612 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Button.tsx @@ -0,0 +1,16 @@ +import { forwardRef, ForwardedRef } from 'react'; +import { Button as RACButton, ButtonProps } from 'react-aria-components'; +import './Button.css'; + +export const Button = forwardRef(function _Button( + props: ButtonProps, + ref: ForwardedRef, +) { + return ( + + ); +}); diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/DateField.tsx b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/DateField.tsx new file mode 100644 index 000000000..a2b89c8a7 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/DateField.tsx @@ -0,0 +1,30 @@ +import { DateValue, AriaDatePickerProps } from '@react-types/datepicker'; +import React from 'react'; +import { + DateField as RACDateField, + Label, + DateInput, + DateSegment as RACDateSegment, +} from 'react-aria-components'; + +interface DateFieldProps + extends Omit, 'onChange'> { + showLabel?: boolean; + label: string; + name: 'end' | 'start'; + onChange: (name: 'end' | 'start', value: DateValue) => void; +} + +export const DateField: React.FC = (props) => { + const { label, showLabel = true, onChange, ...rest } = props; + const customOnChangeHandler = (value: DateValue) => { + return onChange(props.name, value); + }; + + return ( + + {showLabel && } + {(segment) => } + + ); +}; diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Dialog.css b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Dialog.css new file mode 100644 index 000000000..7f53f7325 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Dialog.css @@ -0,0 +1,13 @@ +@import './Button.css'; +@import './TextField.css'; +@import './Modal.css'; + +.react-aria-dialog { + outline: none; + padding: 30px; + + .react-aria-Heading[slot='title'] { + line-height: 1em; + margin-top: 0; + } +} diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Dialog.tsx b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Dialog.tsx new file mode 100644 index 000000000..18951a173 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Dialog.tsx @@ -0,0 +1,30 @@ +import type { AriaDialogProps } from 'react-aria'; +import { useDialog } from 'react-aria'; +import React, { useRef } from 'react'; + +import './Dialog.css'; + +interface DialogProps extends AriaDialogProps { + title?: React.ReactNode; + children: React.ReactNode; +} + +export const Dialog: React.FC = ({ + title, + children, + ...props +}) => { + const ref = useRef(null); + const { dialogProps, titleProps } = useDialog(props, ref); + + return ( +
+ {title && ( +

+ {title} +

+ )} + {children} +
+ ); +}; diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Listbox.css b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Listbox.css new file mode 100644 index 000000000..0da6f7bb4 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Listbox.css @@ -0,0 +1,220 @@ +.react-aria-ListBox { + display: flex; + flex-direction: column; + max-height: inherit; + overflow: auto; + padding: 2px; + border: 1px solid gray; + border-radius: 6px; + forced-color-adjust: none; + outline: none; + width: fit-content; + max-height: 300px; + min-height: 100px; + box-sizing: border-box; + + &[data-focus-visible] { + outline: 2px solid var(--focus-outline-color); + outline-offset: -1px; + } +} + +.react-aria-ListBoxItem { + margin: 2px; + padding: 0.286rem 0.571rem; + border-radius: 6px; + outline: none; + cursor: default; + color: var(--text-color); + font-size: 1.072rem; + position: relative; + display: flex; + flex-direction: column; + + &[data-focus-visible] { + outline: 2px solid var(--focus-outline-color); + outline-offset: -2px; + } + + &[data-selected] { + background: var(--listbox-selected-background); + color: var(--listbox-selected-color); + + &[data-focus-visible] { + outline-color: var(--focus-outline-color); + outline-offset: -4px; + } + } +} + +.react-aria-ListBoxItem[href] { + text-decoration: none; + cursor: pointer; + -webkit-touch-callout: none; +} + +.react-aria-ListBox { + .react-aria-Section:not(:first-child) { + margin-top: 12px; + } + + .react-aria-Header { + font-size: 1.143rem; + font-weight: bold; + padding: 0 0.714rem; + } +} + +.react-aria-ListBoxItem { + [slot='label'] { + font-weight: bold; + } + + [slot='description'] { + font-size: small; + } +} + +.react-aria-ListBox[data-orientation='horizontal'], +.react-aria-ListBox[data-layout='grid'] { + flex-direction: row; + width: fit-content; + max-width: 100%; + padding: 4px; + + .react-aria-ListBoxItem { + position: relative; + margin: 0; + padding: 4px; + + & img { + object-fit: cover; + aspect-ratio: 1/1; + max-width: 150px; + margin-bottom: 4px; + border-radius: 4px; + transition: box-shadow 200ms; + } + + &[data-hovered] { + & img { + box-shadow: 0 0 8px rgb(from var(--listbox-selected-color) r g b / 0.5); + } + } + + &[data-selected] { + background: none; + color: inherit; + + & img { + box-shadow: 0 0 12px rgb(from var(--listbox-selected-color) r g b / 0.8); + } + + &:after { + content: '✓'; + content: '✓' / ''; + alt: ' '; + position: absolute; + top: 8px; + right: 8px; + background: var(--listbox-selected-background); + border: 2px solid var(--listbox-selected-color); + color: var(--listbox-selected-color); + width: 22px; + height: 22px; + border-radius: 22px; + box-sizing: border-box; + font-size: 14px; + line-height: 1em; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 0 8px rgb(0 0 0 / 0.5); + } + } + } +} + +.react-aria-ListBox[data-layout='grid'] { + display: grid; + grid-template-columns: 1fr 1fr; + scrollbar-gutter: stable; +} + +.react-aria-ListBox[data-layout='grid'][data-orientation='horizontal'] { + width: 100%; + max-width: none; + display: grid; + grid-auto-flow: column; + grid-template-rows: 58px 58px; + grid-template-columns: none; + grid-auto-columns: 250px; + max-height: 200px; + gap: 8px; + + .react-aria-ListBoxItem { + display: grid; + grid-template-areas: + 'image .' + 'image title' + 'image description' + 'image .'; + grid-template-columns: auto 1fr; + grid-template-rows: 1fr auto auto 1fr; + column-gap: 8px; + + & img { + width: 50px; + height: 50px; + grid-area: image; + margin-bottom: 0; + } + + [slot='label'] { + grid-area: title; + } + + [slot='description'] { + grid-area: description; + } + } +} + +.react-aria-ListBoxItem { + &[data-disabled] { + color: var(--text-color-disabled); + } +} + +.react-aria-ListBox { + &[data-empty] { + align-items: center; + justify-content: center; + font-style: italic; + } +} + +.react-aria-ListBoxItem { + &[data-dragging] { + opacity: 0.6; + } +} + +.react-aria-DropIndicator[data-drop-target] { + outline: 1px solid var(--highlight-background); +} + +.react-aria-ListBox[data-drop-target] { + outline: 2px solid var(--highlight-background); + outline-offset: -1px; + background: var(--highlight-overlay); +} + +.react-aria-ListBoxItem[data-drop-target] { + outline: 2px solid var(--highlight-background); + background: var(--highlight-overlay); +} + +.react-aria-DropIndicator[data-drop-target] { + outline: 1px solid var(--highlight-background); +} diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Modal.css b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Modal.css new file mode 100644 index 000000000..32ac3e55c --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Modal.css @@ -0,0 +1,83 @@ +@import './Button.css'; +@import './TextField.css'; + +.react-aria-modalOverlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + /* height: var(--visual-viewport-height); */ + background: rgba(0 0 0 / 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 100; + + &[data-entering] { + animation: modal-fade 200ms; + } + + &[data-exiting] { + animation: modal-fade 150ms reverse ease-in; + } +} + +.react-aria-modal { + box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); + border-radius: 6px; + /* background: var(--overlay-background); */ + /* color: var(--text-color); */ + /* border: 1px solid var(--gray-400); */ + outline: none; + max-width: 300px; + + &[data-entering] { + animation: modal-zoom 300ms cubic-bezier(0.175, 0.885, 0.32, 1.275); + } + + .react-aria-TextField { + margin-bottom: 8px; + } +} + +@keyframes modal-fade { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes modal-zoom { + from { + transform: scale(0.8); + } + + to { + transform: scale(1); + } +} + +@keyframes mymodal-blur { + from { + background: rgba(45 0 0 / 0); + backdrop-filter: blur(0); + } + + to { + background: rgba(45 0 0 / 0.3); + backdrop-filter: blur(10px); + } +} + +@keyframes mymodal-slide { + from { + transform: translateX(100%); + } + + to { + transform: translateX(0); + } +} diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Popover.css b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Popover.css new file mode 100644 index 000000000..dcf352430 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Popover.css @@ -0,0 +1,85 @@ +@import './Button.css'; +@import './Dialog.css'; +/* @import './Switch.css'; */ + +.react-aria-Popover { + /* border: 1px solid var(--border-color); */ + box-shadow: 0 8px 20px rgba(0 0 0 / 0.1); + border-radius: 6px; + background: var(--popover-background); + /* color: var(--text-color); */ + outline: none; + max-width: 250px; + box-sizing: border-box; + + .react-aria-OverlayArrow svg { + display: block; + fill: var(--popover-background); + /* stroke: var(--border-color); */ + stroke-width: 1px; + } + + &[data-placement='top'] { + --origin: translateY(8px); + + &:has(.react-aria-OverlayArrow) { + margin-bottom: 6px; + } + } + + &[data-placement='bottom'] { + --origin: translateY(-8px); + + &:has(.react-aria-OverlayArrow) { + margin-top: 6px; + } + + .react-aria-OverlayArrow svg { + transform: rotate(180deg); + } + } + + &[data-placement='right'] { + --origin: translateX(-8px); + + &:has(.react-aria-OverlayArrow) { + margin-left: 6px; + } + + .react-aria-OverlayArrow svg { + transform: rotate(90deg); + } + } + + &[data-placement='left'] { + --origin: translateX(8px); + + &:has(.react-aria-OverlayArrow) { + margin-right: 6px; + } + + .react-aria-OverlayArrow svg { + transform: rotate(-90deg); + } + } + + &[data-entering] { + animation: popover-slide 200ms; + } + + &[data-exiting] { + animation: popover-slide 200ms reverse ease-in; + } +} + +@keyframes popover-slide { + from { + transform: var(--origin); + opacity: 0; + } + + to { + transform: translateY(0); + opacity: 1; + } +} diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Popover.tsx b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Popover.tsx new file mode 100644 index 000000000..0058e143b --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/Popover.tsx @@ -0,0 +1,44 @@ +import React, { useRef } from 'react'; +import { DismissButton, Overlay, usePopover } from 'react-aria'; +import type { AriaPopoverProps } from 'react-aria'; +import type { OverlayTriggerState } from 'react-stately'; + +import './Popover.css'; + +interface PopoverProps extends Omit { + children: React.ReactNode; + state: OverlayTriggerState; +} + +export const Popover: React.FC = ({ + children, + state, + ...props +}) => { + const popoverRef = useRef(null); + const { popoverProps, underlayProps } = usePopover( + { + ...props, + popoverRef, + }, + state, + ); + + return ( + +
+
+ + {children} + +
+ + ); +}; diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/TextField.css b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/TextField.css new file mode 100644 index 000000000..50cd2f799 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/TextField.css @@ -0,0 +1,48 @@ +@import './Button.css'; + +.react-aria-textField { + display: flex; + flex-direction: column; + width: fit-content; + /* color: var(--text-color); */ + + .react-aria-Input, + .react-aria-TextArea { + padding: 0.286rem; + margin: 0; + /* border: 1px solid var(--border-color); */ + border-radius: 6px; + /* background: var(--field-background); */ + font-size: 1.143rem; + /* color: var(--field-text-color); */ + + &[data-focused] { + /* outline: 2px solid var(--focus-ring-color); */ + outline-offset: -1px; + } + } + + .react-aria-input, + .react-aria-textArea { + &[data-invalid] { + /* border-color: var(--invalid-color); */ + } + } + + .react-aria-fieldError { + font-size: 12px; + /* color: var(--invalid-color); */ + } + + [slot='description'] { + font-size: 12px; + } + + .react-aria-input, + .react-aria-textArea { + &[data-disabled] { + /* border-color: var(--border-color-disabled); */ + /* color: var(--text-color-disabled); */ + } + } +} diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/index.tsx b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/index.tsx new file mode 100644 index 000000000..5a65168a4 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/Commons/index.tsx @@ -0,0 +1,6 @@ +import { Button } from './Button'; +import { Dialog } from './Dialog'; +import { Popover } from './Popover'; +import { DateField } from './DateField'; + +export { Button, DateField, Dialog, Popover }; diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/DateRangeFilter.tsx b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/DateRangeFilter.tsx new file mode 100644 index 000000000..c476b2014 --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/DateRangeFilter.tsx @@ -0,0 +1,174 @@ +import { useDateRangePicker } from 'react-aria'; +import { + useDateRangePickerState, + DateRangePickerStateOptions, +} from 'react-stately'; +import { RangeValue } from '@react-types/shared'; +import { useRef } from 'react'; +import { DateField, Button, Dialog, Popover } from './Commons'; +import { RangeCalendar } from './Calendar/RangeCalendar'; +import { DateRange, DateValue } from 'react-aria-components'; +import { Icon } from 'design-react-kit'; +import cx from 'classnames'; +import { useIntl, defineMessages } from 'react-intl'; + +import './index.css'; + +type AvailableColors = + | 'white' + | 'primary' + | 'secondary' + | 'body' + | 'dark' + | 'danger' + | 'warning' + | 'info' + | 'success' + | 'light' + | 'muted'; + +export type CustomDateRange = { + start: DateValue | null; + end: DateValue | null; +}; + +interface DateRangeFilterProps + extends Omit { + value?: RangeValue; + id: string; + startLabel?: string; + endLabel?: string; + showInputLabels?: boolean; + onChange: (id: string, value: CustomDateRange | null) => void; + textColor?: AvailableColors; + controlsBackgroundColor?: AvailableColors; + validationEnabled?: boolean; +} + +// Vorrei capire perche' sono costretta ad copiare TUTTI i messaggi +// di questo e tutti i componenti che lo compongono per metterli in +// overrideTranslations per generare traduzioni con yarn i18n +export const MESSAGES = { + calendarClearDates: { + id: 'calendarClearDates', + defaultMessage: 'Clear selection', + }, + calendarInvalid: { + id: 'calendarInvalid', + defaultMessage: 'Invalid value', + }, + calendarOpenCalendar: { + id: 'calendarOpenCalendar', + defaultMessage: 'Open calendar', + }, +}; + +const messages = defineMessages(MESSAGES); + +function DateRangeFilter(props: DateRangeFilterProps) { + const { + startLabel = '', + endLabel = '', + showInputLabels, + textColor = 'body', + controlsBackgroundColor = 'primary', + validationEnabled = false, + onChange, + ...rest + } = props; + const intl = useIntl(); + + const customOnChange = (value: CustomDateRange | null) => { + return onChange(props.id, value); + }; + const state = useDateRangePickerState({ ...rest, onChange: customOnChange }); + const ref = useRef(null); + + const { + labelProps, + groupProps, + startFieldProps, + endFieldProps, + buttonProps, + dialogProps, + calendarProps, + } = useDateRangePicker(rest, state, ref); + const onClearButtonClick = () => { + state.setValue(null); + customOnChange({ start: null, end: null }); + }; + const dateFieldChangeHandler = (name: 'end' | 'start', value: DateValue) => { + const newValue = { ...state.value, [name]: value } as DateRange; + state.setValue(newValue); + customOnChange(newValue); + }; + + return ( +
+ {props.label} +
+
+ + + – + + + {validationEnabled && state.isInvalid && ( + {intl.formatMessage(messages.calendarInvalid)} + )} +
+ + +
+ {state.isOpen && ( + + + + + + )} +
+ ); +} + +export default DateRangeFilter; diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/index.css b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/index.css new file mode 100644 index 000000000..430209c9b --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/index.css @@ -0,0 +1,23 @@ +button#open-calendar { + border-bottom-right-radius: 4px !important; + border-bottom-left-radius: 0 !important; + border-bottom: 0 !important; +} +#daterange-filter, +.daterange-filter { + border-bottom-right-radius: 4px !important; + width: fit-content; +} +#search-block-daterange-facet { + border-bottom-color: var(--bs-gray-secondary) !important; + border-bottom-right-radius: 4px !important; + border-bottom-width: 2px; + width: fit-content; + color: var(--bs-gray-secondary) !important; + button#open-calendar { + border-color: var(--bs-gray-secondary) !important; + } + button#open-calendar svg { + fill: var(--bs-gray-secondary) !important; + } +} diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/index.tsx b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/index.tsx new file mode 100644 index 000000000..7f797dbdd --- /dev/null +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/DateRangeFilter/index.tsx @@ -0,0 +1,15 @@ +import { MESSAGES as CalendarMessages } from './Calendar/RangeCalendar'; +import { MESSAGES as DateFilterMessages } from './DateRangeFilter'; +import { lazy } from 'react'; + +// Remove webpackChunkName if bundling by components that depend on react-aria-component +// is deemed useless now or in the future, given @plone/components has RAC as dependency. +const DateRangeFilter = lazy( + () => import(/* webpackChunkName: "RACUIComponents" */ './DateRangeFilter'), +); +// Tried to export messages because i18n is unable to generate translations, but babel-plugin-react-intl@5.1.17 +// is old, deprecated and broken and you cannot merge translation objects in overrideTrandlations, sad. +// In copy pasting we will trust. +const DateRangeFilterMessages = { ...CalendarMessages, ...DateFilterMessages }; +export { DateRangeFilterMessages }; +export default DateRangeFilter; diff --git a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/index.js b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/index.js index d77397d58..1fcebc1c6 100644 --- a/src/components/ItaliaTheme/Blocks/Common/SearchFilters/index.js +++ b/src/components/ItaliaTheme/Blocks/Common/SearchFilters/index.js @@ -1,5 +1,6 @@ import TextFilter from './TextFilter'; import SelectFilter from './SelectFilter'; import DateFilter from './DateFilter'; +import DateRangeFilter from './DateRangeFilter'; -export { TextFilter, SelectFilter, DateFilter }; +export { TextFilter, SelectFilter, DateFilter, DateRangeFilter }; diff --git a/src/components/ItaliaTheme/Blocks/EventSearch/Body.jsx b/src/components/ItaliaTheme/Blocks/EventSearch/Body.jsx index 2e912e6e5..eca88825e 100644 --- a/src/components/ItaliaTheme/Blocks/EventSearch/Body.jsx +++ b/src/components/ItaliaTheme/Blocks/EventSearch/Body.jsx @@ -2,14 +2,13 @@ import React, { useState, useReducer, useEffect, createRef } from 'react'; import { useIntl, defineMessages } from 'react-intl'; import { useDispatch, useSelector } from 'react-redux'; import { Container, Button, Spinner } from 'design-react-kit'; -import moment from 'moment'; import cx from 'classnames'; import { getQueryStringResults } from '@plone/volto/actions'; import { flattenToAppURL } from '@plone/volto/helpers'; import CardWithImageTemplate from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/Listing/CardWithImageTemplate'; import { Pagination } from 'design-comuni-plone-theme/components/ItaliaTheme'; - +import { generateFiltersProps } from 'design-comuni-plone-theme/helpers'; import FiltersConfig from 'design-comuni-plone-theme/components/ItaliaTheme/Blocks/EventSearch/FiltersConfig'; const messages = defineMessages({ @@ -31,13 +30,18 @@ const messages = defineMessages({ defaultMessage: 'Nessun risultato trovato', }, }); +const EVENTS_SEARCH_FILTER_PROPS_OVERRIDES = { + date_filter: { + showInputLabels: false, + textColor: 'light', + controlsBackgroundColor: 'primary', + }, +}; const Body = ({ data, inEditMode, path, onChangeBlock }) => { const intl = useIntl(); const b_size = 6; - moment.locale(intl.locale); - const [currentPage, setCurrentPage] = useState(1); const subsite = useSelector((state) => state.subsite?.data); @@ -152,6 +156,13 @@ const Body = ({ data, inEditMode, path, onChangeBlock }) => { doRequest(current); } + const onChangeHandler = (filter, value) => { + dispatchFilter({ + filter: filter, + value: value, + }); + }; + return filterOne || filterTwo || filterThree ? (
{ {filterOne && ( <> {React.createElement(filterOne.widget.component, { - ...filterOne.widget?.props, - id: 'filterOne', - onChange: (filter, value) => { - dispatchFilter({ - filter: filter, - value: value, - }); - }, + ...generateFiltersProps( + filterOne, + 'filterOne', + onChangeHandler, + EVENTS_SEARCH_FILTER_PROPS_OVERRIDES, + ), })} )} {filterTwo && React.createElement(filterTwo.widget?.component, { - ...filterTwo.widget?.props, - id: 'filterTwo', - onChange: (filter, value) => - dispatchFilter({ - filter: filter, - value: value, - }), + ...generateFiltersProps( + filterTwo, + 'filterTwo', + onChangeHandler, + EVENTS_SEARCH_FILTER_PROPS_OVERRIDES, + ), })} {filterThree && React.createElement(filterThree.widget?.component, { - ...filterThree.widget?.props, - id: 'filterThree', - onChange: (filter, value) => - dispatchFilter({ - filter: filter, - value: value, - }), + ...generateFiltersProps( + filterThree, + 'filterThree', + onChangeHandler, + EVENTS_SEARCH_FILTER_PROPS_OVERRIDES, + ), })} -