Skip to content
Snippets Groups Projects
Commit 1492273f authored by Sophia Kuhlmann's avatar Sophia Kuhlmann
Browse files

HOLI-11279 layout improvements to events screen

parent 7ce369fe
Branches holi-11279-events-layout
No related tags found
No related merge requests found
import { EventList } from '@holi-apps/events/screens/EventList'
import type { NextPage } from 'next'
import React from 'react'
import React, { type ReactElement } from 'react'
const EventsPage: NextPage = () => <EventList />
import { AppLayout } from '@holi/web/layout/NavigationLayoutVolunteeringApp'
import type { NextPageWithLayout } from '@holi/web/pages/_app'
const EventsPage: NextPageWithLayout = () => <EventList />
EventsPage.getLayout = (page: ReactElement) => <AppLayout>{page}</AppLayout>
export default EventsPage
import React from 'react'
import RecoSlider from '@holi/core/components/RecoSlider'
import { CheckboxFilled } from '@holi/icons/src/generated'
import { CalendarEventFilled } from '@holi/icons/src/generated'
import { useTranslation } from 'react-i18next'
import EventCard from '@holi-apps/events/components/EventCard'
import { Stack } from 'holi-bricks/components/stack'
......@@ -37,7 +37,7 @@ const EventCarousel = ({ cardWidth }: EventCarouselProps) => {
}}
data={events}
loading={user.isLoading}
icon={CheckboxFilled}
icon={CalendarEventFilled}
heading={t('act.events.title')}
subline={t('act.events.subline')}
labelSeeMore={t('act.events.showAll')}
......
......@@ -47,7 +47,15 @@ const EventsFilterForm = ({ onSubmit, filters }: EventFilterFormProps) => {
<View style={styles.modalContent}>
<Stack direction="row" alignItems="center" justifyContent="space-between" gap="3xs">
<Text size="3xl">{t('act.events.filter.heading')}</Text>
<Button label={t('global.reset')} inline onPress={resetForm} variant="secondary" size="sm" icon={Close} />
<Button
disabled={localOnly}
label={t('global.reset')}
inline
onPress={resetForm}
variant="secondary"
size="sm"
icon={Close}
/>
</Stack>
<Stack direction="row" alignItems="center" justifyContent="space-between">
......
......@@ -9,11 +9,8 @@ import HoliGridWrapper from '@holi/ui/components/atoms/HoliGridWrapper'
import { Image } from 'holi-bricks/components/image'
import HoliModal, { useModalState } from '@holi/ui/components/molecules/HoliModal'
import { createStyleSheet } from 'holi-bricks/utils'
import { useStyles } from 'holi-bricks/hooks'
import { Text } from 'holi-bricks/components/text'
import HeaderBackButton from '@holi/core/navigation/components/HeaderBackButton'
import HoliContainer from '@holi/ui/components/atoms/HoliContainer'
import HoliButton from '@holi/ui/components/molecules/HoliButton'
import EventsFilterForm, { type EventFilters } from '@holi-apps/events/components/EventsFilterForm'
import EventCard from '@holi-apps/events/components/EventCard'
......@@ -32,9 +29,9 @@ const PAGE_SIZE_EXTERNAL_DATA = 10
export const EventList = () => {
const { t } = useTranslation()
const { styles } = useStyles(stylesheet)
const [fetchLocalOnlyEvents, setFetchLocalOnlyEvents] = useState(false)
const [isFiltering, setIsFiltering] = useState(false)
const { events, canLoadMore, fetchMoreEvents } = useEventListData(PAGE_SIZE_EXTERNAL_DATA, fetchLocalOnlyEvents)
const partner = EVENTS_SOURCE_MAPPING
......@@ -47,6 +44,7 @@ export const EventList = () => {
const handleFilterChange = useCallback(
async (filters: EventFilters) => {
setFetchLocalOnlyEvents(filters.localOnly)
setIsFiltering(filters.localOnly)
closeFilterModal()
},
[closeFilterModal]
......@@ -59,6 +57,7 @@ export const EventList = () => {
onPress={showFilterModal}
testID="btn-to-volunteering-filter-modal"
trackingEvent={TrackingEvent.Events.filter}
showBell={isFiltering}
/>
)
......@@ -78,100 +77,75 @@ export const EventList = () => {
headerTitleAlign: 'center',
}}
>
<HoliContainer>
<Stack padding={{ top: 'md' }}>
{events && events.length > 0 && (
<Stack gap="xs">
<Text headingLevel="2" size="3xl">
{t('act.events.title')}
</Text>
<Stack padding={{ top: 'md' }}>
{events && events.length > 0 && (
<Stack gap="xs">
<Text headingLevel="2" size="3xl">
{t('act.events.title')}
</Text>
<HoliButton transparent onPress={showPartnerModal} label={t('act.events.partnerInfoRowText')}>
<Stack direction="row" alignItems="center" gap="3xs">
<Stack borderRadius="pill">
<View style={styles.imageContainer}>
<Image
source={require('@holi/ui/assets/img/events/gemeinschaftswerk-nachhaltigkeit-logo.png')}
width={33}
height={33}
alt="Logo Gemeinschaftswerk Nachhaltigkeit"
/>
</View>
</Stack>
<HoliButton transparent onPress={showPartnerModal} label={t('act.events.partnerInfoRowText')}>
<View style={{ flex: 3 }}>
<Text size="lg">{t('act.events.partnerInfoRowText')}</Text>
</View>
</HoliButton>
<Image
source={require('@holi/ui/assets/img/events/gemeinschaftswerk-nachhaltigkeit-logo.png')}
width={33}
height={33}
alt="Logo Gemeinschaftswerk Nachhaltigkeit"
/>
<View style={{ flex: 3 }}>
<Text size="lg">{t('act.events.partnerInfoRowText')}</Text>
</View>
</Stack>
</HoliButton>
<Text size="lg" color="support">
{events.length} {t('act.events.opportunities')}
</Text>
<Text size="lg" color="support">
{events.length} {t('act.events.opportunities')}
</Text>
{/* info modal */}
<InfoModal
headline={t('volunteering:listing.partnerModal.headline')}
items={partner}
shown={partnerModalVisible}
onDismiss={closePartnerModal}
i18nPrefix={'act.events.listing.partnerModal'}
/>
{/* info modal */}
<InfoModal
headline={t('volunteering:listing.partnerModal.headline')}
items={partner}
shown={partnerModalVisible}
onDismiss={closePartnerModal}
i18nPrefix={'act.events.listing.partnerModal'}
/>
{/* filter modal */}
<HoliModal label={'Filter Events'} shown={filterModalVisible} onDismiss={closeFilterModal} autoHeight>
<EventsFilterForm
filters={{
localOnly: fetchLocalOnlyEvents,
}}
onSubmit={handleFilterChange}
/>
</HoliModal>
</Stack>
)}
<HoliBox padding={[24, 0, 24, 0]}>
{/* <View testID={isFiltering ? 'volunteering-list-filtered' : 'volunteering-list-unfiltered'}> */}
<HoliGridWrapper>
{Array.isArray(events) &&
events.map((event) => {
return (
<HoliGridCell key={event.id}>
<EventCard event={event} />
</HoliGridCell>
)
})}
</HoliGridWrapper>
{/* </View> */}
</HoliBox>
{canLoadMore && (
<Stack padding={{ bottom: 'md' }}>
<ButtonTracked
label={t('global.seeMore')}
loadingLabel={t('global.loading')}
inline
onPress={fetchMoreEvents}
testID="btn-fetch-more"
trackingEvent={TrackingEvent.Events.loadMore}
{/* filter modal */}
<HoliModal label={'Filter Events'} shown={filterModalVisible} onDismiss={closeFilterModal} autoHeight>
<EventsFilterForm
filters={{
localOnly: fetchLocalOnlyEvents,
}}
onSubmit={handleFilterChange}
/>
</Stack>
)}
</Stack>
</HoliContainer>
</HoliModal>
</Stack>
)}
<HoliBox padding={[24, 0, 24, 0]}>
<HoliGridWrapper gap={8}>
{Array.isArray(events) &&
events.map((event) => {
return (
<HoliGridCell key={event.id}>
<EventCard event={event} />
</HoliGridCell>
)
})}
</HoliGridWrapper>
</HoliBox>
{canLoadMore && (
<Stack padding={{ bottom: 'md' }}>
<ButtonTracked
label={t('global.seeMore')}
loadingLabel={t('global.loading')}
inline
onPress={fetchMoreEvents}
testID="btn-fetch-more"
trackingEvent={TrackingEvent.Events.loadMore}
/>
</Stack>
)}
</Stack>
</Screen>
)
}
const stylesheet = createStyleSheet((theme) => ({
defaultLayout: {
backgroundColor: theme.colors.bg.page,
paddingBottom: 0,
},
imageContainer: {
borderColor: theme.colors.border.default,
overflow: 'hidden',
display: 'flex',
justifyContent: 'center',
justifySelf: 'center',
alignContent: 'center',
alignSelf: 'center',
textAlign: 'center',
},
}))
......@@ -55,7 +55,6 @@ export const CompactCard = ({
require('@holi/ui/assets/img/events/thumbnail-yellow-full.svg'),
]
const randomPlaceholderImage = useRef(placeholderImages[Math.floor(Math.random() * placeholderImages.length)]).current
return (
<View style={styles.cardContainer(maxWidth)}>
<Stack borderWidth={1} borderColor="support" borderStyle="solid" borderRadius="md">
......@@ -70,6 +69,7 @@ export const CompactCard = ({
width={90}
alt={t('act.events.thumbnail.label')}
onError={handleImageError}
whiteOverlay
/>
</View>
......
import type React from 'react'
import { useState } from 'react'
import { Image as RNImage } from 'react-native'
import { Image as RNImage, View } from 'react-native'
import { Image as ExpoImage } from 'expo-image'
//import { WithLocalSvg } from 'react-native-svg/css'
import { accessibilityProps } from 'holi-bricks/accessibility'
import type { ImageProps } from 'holi-bricks/components/image/type'
import { setScalingParams } from 'holi-bricks/components/image/helpers'
import { createStyleSheet } from 'holi-bricks/utils'
import { useStyles } from 'holi-bricks/hooks'
const isLocalSvg = (source: number) => {
try {
......@@ -32,12 +34,14 @@ export const Image: React.FC<ImageProps> = ({
fallbackSource,
blurhash,
alt,
whiteOverlay: overlay,
...otherProps
}) => {
const [hasError, setHasError] = useState(false)
const transformedSource = setScalingParams(source, width, height)
const isRemote = isRemoteSource(source)
const localSvg = typeof source === 'number' && isLocalSvg(source)
const { styles } = useStyles(stylesheet)
if (!source) return null
......@@ -59,25 +63,39 @@ export const Image: React.FC<ImageProps> = ({
}
return (
<ExpoImage
source={hasError && fallbackSource ? fallbackSource : transformedSource}
alt={alt}
role="img"
accessibilityLabel={alt}
accessibilityRole={otherProps?.['aria-hidden'] === true ? 'none' : 'image'}
accessible={otherProps?.['aria-hidden'] === true}
placeholder={isRemote ? blurhash : undefined}
style={[{ width, height }]}
contentFit={resizeMode}
onError={() => {
setHasError(true)
// eslint-disable-next-line no-console
console.warn(`[Image] Failed to load image: ${source}`)
if (onError) onError()
}}
aria-label={alt}
onLoadEnd={onLoadEnd}
{...accessibilityProps(otherProps)}
/>
<>
<ExpoImage
source={hasError && fallbackSource ? fallbackSource : transformedSource}
alt={alt}
role="img"
accessibilityLabel={alt}
accessibilityRole={otherProps?.['aria-hidden'] === true ? 'none' : 'image'}
accessible={otherProps?.['aria-hidden'] === true}
placeholder={isRemote ? blurhash : undefined}
style={[{ width, height }]}
contentFit={resizeMode}
onError={() => {
setHasError(true)
// eslint-disable-next-line no-console
console.warn(`[Image] Failed to load image: ${source}`)
if (onError) onError()
}}
aria-label={alt}
onLoadEnd={onLoadEnd}
{...accessibilityProps(otherProps)}
/>
{overlay && <View style={styles.overlay} />}
</>
)
}
const stylesheet = createStyleSheet({
overlay: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'rgba(255, 255, 255, 0.2)',
},
})
......@@ -4,6 +4,9 @@ import { useState } from 'react'
import NextImage from 'next/image'
import { accessibilityProps } from 'holi-bricks/accessibility'
import { setScalingParams } from 'holi-bricks/components/image/helpers'
import { createStyleSheet } from 'holi-bricks/utils'
import { useStyles } from 'holi-bricks/hooks'
import { View } from 'react-native'
export const isRemoteSource = (source: string): source is string => {
return /^(https?:)?\/\//i.test(source)
......@@ -19,30 +22,46 @@ export const Image: React.FC<ImageProps> = ({
onLoadEnd,
fallbackSource,
blurhash,
whiteOverlay: overlay,
...otherProps
}) => {
const [hasError, setHasError] = useState(false)
const isRemote = isRemoteSource(source as string)
const transformedSource = (isRemote ? setScalingParams(source as string, width, height) : source) as string
const { styles } = useStyles(stylesheet)
return (
<NextImage
src={hasError && fallbackSource ? (fallbackSource as string) : transformedSource}
alt={alt}
role="img"
aria-label={alt}
blurDataURL={isRemote ? blurhash : undefined}
width={width}
height={height}
style={{ objectFit: resizeMode }}
onError={() => {
setHasError(true)
// eslint-disable-next-line no-console
console.warn(`[Image] Failed to load image: ${transformedSource}`)
if (onError) onError()
}}
onLoad={onLoadEnd}
{...accessibilityProps(otherProps)}
/>
<>
<NextImage
src={hasError && fallbackSource ? (fallbackSource as string) : transformedSource}
alt={alt}
role="img"
aria-label={alt}
blurDataURL={isRemote ? blurhash : undefined}
width={width}
height={height}
style={{ objectFit: resizeMode }}
onError={() => {
setHasError(true)
// eslint-disable-next-line no-console
console.warn(`[Image] Failed to load image: ${transformedSource}`)
if (onError) onError()
}}
onLoad={onLoadEnd}
{...accessibilityProps(otherProps)}
/>
{overlay && <View style={styles.overlay} />}
</>
)
}
const stylesheet = createStyleSheet({
overlay: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'rgba(255, 255, 255, 0.2)',
},
})
......@@ -13,4 +13,5 @@ export interface ImageProps extends AccessibilityProps {
fallbackSource?: string | number
blurhash?: string
alt: string
whiteOverlay?: boolean
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment