Skip to content

Commit

Permalink
Extend linkedevents app
Browse files Browse the repository at this point in the history
* Add new config variables
* Add card ghost
* Add loading animation
* Add bordered card variation
  • Loading branch information
jeremysteerio committed Dec 10, 2024
1 parent 063e0c5 commit 8239618
Show file tree
Hide file tree
Showing 18 changed files with 98 additions and 42 deletions.
2 changes: 1 addition & 1 deletion dist/css/styles.min.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/district-and-project-search.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/health-station-search.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/job-search.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/linkedevents.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/maternity-and-child-health-clinic-search.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/news-archive.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/js/school-search.min.js

Large diffs are not rendered by default.

13 changes: 5 additions & 8 deletions src/js/react/apps/linkedevents/components/ResultCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,10 @@ const getCardTags = ({ keywords, currentLanguage }: KeywordsForLanguage ) => key

function ResultCard({ end_time, id, location, name, keywords=[], start_time, images, offers }: Event) {
const { currentLanguage } = drupalSettings.path;
const { baseUrl, imagePlaceholder } = drupalSettings.helfi_events;
const { baseUrl, imagePlaceholder, useBorderedCards } = drupalSettings.helfi_events;
const url = `${baseUrl}/${currentLanguage}/events/${id}`;

// Bail if no current language
if (!name[currentLanguage]) {
return null;
}
const resolvedName = name?.[currentLanguage] || name?.fi || Object.values(name)[0] || '';

const getDate = () => {
let startDate;
Expand All @@ -43,7 +40,7 @@ function ResultCard({ end_time, id, location, name, keywords=[], start_time, ima
endDate = new Date(end_time);
isMultiDate = end_time ? overDayApart(startDate, endDate) : false;
} catch (e) {
throw new Error('DATE ERROR');
throw new Error(`DATE ERROR ${e}`);
}

if (isMultiDate) {
Expand Down Expand Up @@ -102,19 +99,19 @@ function ResultCard({ end_time, id, location, name, keywords=[], start_time, ima

const image = images?.find(img => img.url);
const isRemote = location && location.id === INTERNET_EXCEPTION;
const title = name[currentLanguage] || '';
const cardTags = getCardTags({keywords, currentLanguage});

return (
<CardItem
cardUrl={url}
cardTitle={title}
cardTitle={resolvedName}
cardImage={image ? imageToElement(image) : parse(imagePlaceholder) }
cardTags={cardTags}
cardUrlExternal
location={isRemote ? 'Internet' : getLocation()}
time={getDate()}
registrationRequired={getOffers()}
useBorderedCards={useBorderedCards}
/>
);
}
Expand Down
3 changes: 2 additions & 1 deletion src/js/react/apps/linkedevents/components/SeeAllButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import ExternalLink from '../../../common/ExternalLink';

function SeeAllButton() {
const eventsUrl = useAtomValue(eventsPublicUrl) || '';
const { seeAllButtonOverride } = drupalSettings?.helfi_events || null;

return (
<div className="event-list__see-all-button">
<ExternalLink
data-hds-component="button"
data-hds-variant="secondary"
href={eventsUrl}
title={Drupal.t('Refine search in tapahtumat.hel.fi', {}, { context: 'Events search' })} />
title={seeAllButtonOverride || Drupal.t('Refine search in tapahtumat.hel.fi', {}, { context: 'Events search' })} />
</div>
);
}
Expand Down
35 changes: 18 additions & 17 deletions src/js/react/apps/linkedevents/containers/ResultsContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { createRef } from 'react';
import { useAtomValue } from 'jotai';

import ResultsError from '@/react/common/ResultsError';
import LoadingOverlay from '@/react/common/LoadingOverlay';
import useScrollToResults from '@/react/common/hooks/useScrollToResults';
import Pagination from '../components/Pagination';
import ResultCard from '../components/ResultCard';
import CardGhost from '@/react/common/CardGhost';
import SeeAllButton from '../components/SeeAllButton';
import { settingsAtom, urlAtom } from '../store';
import type Event from '../types/Event';
Expand All @@ -27,14 +27,6 @@ function ResultsContainer({ countNumber, events, loading, error }: ResultsContai
const choices = Boolean(url);
useScrollToResults(scrollTarget, choices);

if (loading) {
return (
<div className='hdbt__loading-wrapper'>
<LoadingOverlay />
</div>
);
}

if (error) {
return (
<ResultsError
Expand All @@ -49,24 +41,33 @@ function ResultsContainer({ countNumber, events, loading, error }: ResultsContai
const addLastPage = countNumber > size && countNumber % size;
const count = countNumber.toString();

return (
<div className='react-search__list-container'>
{events?.length > 0 ?
const getContent = () => {
if (loading && !events.length) {
return Array.from({ length: size }, (_, i) => <CardGhost key={i} />);
}
if (events.length > 0) {
return (
<>
<ResultsHeader
resultText={
<>
{ Drupal.formatPlural(count, '1 event', '@count events',{},{context: 'Events search: result count'}) }
{Drupal.formatPlural(count, '1 event', '@count events', {}, {context: 'Events search: result count'})}
</>
}
ref={scrollTarget}
/>
{events.map(event => <ResultCard key={event.id} {...event} />)}
{events.map(event => loading ? <CardGhost key={event.id} /> : <ResultCard key={event.id} {...event} />)}
<Pagination pages={5} totalPages={addLastPage ? pages + 1 : pages} />
</>
:
<ResultsEmpty wrapperClass='event-list__no-results' ref={scrollTarget} />
}
);
}

return <ResultsEmpty wrapperClass='event-list__no-results' ref={scrollTarget} />;
};

return (
<div className={`react-search__list-container${loading ? ' loading' : ''}`}>
{getContent()}
<SeeAllButton />
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions src/js/react/apps/linkedevents/containers/SearchContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const SWR_REFRESH_OPTIONS = {
revalidateOnFocus: false,
revalidateOnReconnect: false,
refreshInterval: 6000000, // 10 minutes,in millis
keepPreviousData: true,
};

const SearchContainer = () => {
Expand Down Expand Up @@ -59,13 +60,12 @@ const SearchContainer = () => {

throw new Error('Failed to get data from the API');
};
const { data, error } = useSWR(url, getEvents, SWR_REFRESH_OPTIONS);
const loading = !error && !data;
const { data, error, isLoading } = useSWR(url, getEvents, SWR_REFRESH_OPTIONS);

return (
<>
<FormContainer />
<ResultsContainer error={error} countNumber={data?.meta.count || 0} loading={loading} events={data?.data || []} />
<ResultsContainer error={error} countNumber={data?.meta.count || 0} loading={isLoading} events={data?.data || []} />
</>
);
};
Expand Down
5 changes: 2 additions & 3 deletions src/js/react/apps/linkedevents/store.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import OptionType from './types/OptionType';
import FormErrors from './types/FormErrors';
import ApiKeys from './enum/ApiKeys';
import Topic from './types/Topic';
import type Event from './types/Event';

interface Options {
[key: string]: string
Expand Down Expand Up @@ -57,11 +56,11 @@ const createBaseAtom = () => {
showTimeFilter: settings?.field_event_time,
showFreeFilter: settings?.field_free_events,
showRemoteFilter: settings?.field_remote_events,
showTopicsFilter: settings?.field_filter_keywords.length > 0,
showTopicsFilter: settings?.field_filter_keywords?.length > 0,
eventCount: Number(settings?.field_event_count)
};
const locations = transformLocations(settings?.places);
const topics: Topic[] = settings?.field_filter_keywords.map(topic => ({
const topics: Topic[] = settings?.field_filter_keywords?.map(topic => ({
value: topic.id,
label: topic.name.charAt(0).toUpperCase() + topic.name.slice(1),
}));
Expand Down
9 changes: 8 additions & 1 deletion src/js/react/common/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export type CardItemProps = {
weightedEducation?: string;
languageEducation?: string;
registrationRequired?: boolean;
useBorderedCards?: boolean;
};

function CardItem({
Expand Down Expand Up @@ -74,8 +75,14 @@ function CardItem({
weightedEducation,
languageEducation,
registrationRequired,
useBorderedCards,
}: CardItemProps): JSX.Element {
const cardClass = `card${cardModifierClass ? ` ${cardModifierClass}` : ''}${cardUrlExternal ? ' card--external' : ''}`;
const cardClass = `
card
${cardModifierClass ? ` ${cardModifierClass}` : ''}
${cardUrlExternal ? ' card--external' : ''}
${useBorderedCards ? ' card--bordered' : ''}
`;
const HeadingTag = cardTitleLevel ? `h${cardTitleLevel}` as keyof JSX.IntrinsicElements : 'h3';

return (
Expand Down
9 changes: 9 additions & 0 deletions src/js/react/common/CardGhost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function CardGhost() {
return (
<div className='card card--ghost'>
<div className='card__image'>
<div className="image-placeholder"></div>
</div>
</div>
);
}
4 changes: 3 additions & 1 deletion src/js/types/drupalSettings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ declare namespace drupalSettings {
},
use_fixtures: boolean
}
}
},
seeAllButtonOverride: string,
useBorderedCards: boolean,
};
const helfi_react_search: {
// @todo UHF-10862 Remove cookie_privacy_url once the HDBT cookie banner module is in use.
Expand Down
10 changes: 10 additions & 0 deletions src/scss/05_objects/_card.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ $item-gap: $spacing-half;

.card {
background: $color-white;
border: 2px solid transparent;
overflow-wrap: anywhere;
padding: $spacing;
position: relative;
Expand All @@ -26,6 +27,15 @@ $item-gap: $spacing-half;
}
}

.card--ghost {
background: $color-black-20;
border: none;
}

.card--bordered {
border: 2px solid $color-black-20;
}

.card__image {
flex-basis: 30%;
flex-shrink: 0;
Expand Down
30 changes: 30 additions & 0 deletions src/scss/06_components/paragraphs/_react-search.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,36 @@
margin-bottom: $spacing-double;
}

.react-search__list-container.loading .card {
overflow: hidden;
position: relative;
}

@keyframes loading {
0% {
transform: skewX(-10deg) translateX(-150%);
}

100% {
transform: skewX(-10deg) translateX(250%);
}
}

.react-search__list-container.loading .card::after {
animation: loading 1.2s infinite;
background: linear-gradient(
90deg,
transparent,
$color-white,
transparent,
);
bottom: 0;
content: "";
position: absolute;
top: 0;
width: 50%;
};

.react-search__results-stats {
margin-bottom: $spacing;
}
Expand Down

0 comments on commit 8239618

Please sign in to comment.