Skip to content

Commit

Permalink
UX functional cues
Browse files Browse the repository at this point in the history
  • Loading branch information
jekrch committed Sep 15, 2024
1 parent 7028d0a commit 32aa800
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 61 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Eurovision Ranker :yellow_heart:

![Version](https://img.shields.io/badge/version-5.4-blue)
![Version](https://img.shields.io/badge/version-5.5-blue)
![Run Tests](https://github.com/jekrch/eurovision-ranker/actions/workflows/test_on_push.yml/badge.svg)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Expand Down
2 changes: 1 addition & 1 deletion src/components/modals/MainModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const MainModal: React.FC<MainModalProps> = (props: MainModalProps) => {
target="_blank"
rel="noopener noreferrer"
href="https://github.com/jekrch/eurovision-ranker/releases"
>v5.4</a>
>v5.5</a>
</span>
<span className="text-right">
{`Copyright (c) 2023${new Date().getFullYear()?.toString() !== '2023' ? '-' + new Date().getFullYear() : ''} `}
Expand Down
28 changes: 25 additions & 3 deletions src/components/modals/WelcomeOverlay.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import React, { useRef } from 'react';
import React, { useRef, useState } from 'react';
import { FaList, FaTv, FaGlobe, FaCog, FaHeart } from 'react-icons/fa';
import IconButton from '../IconButton';
import { faCheck, faGlasses } from '@fortawesome/free-solid-svg-icons';
import classNames from 'classnames';
import { AppDispatch, AppState } from '../../redux/store';
import { useAppDispatch, useAppSelector } from '../../hooks/stateHooks';
import { setWelcomeOverlayIsOpen } from '../../redux/rootSlice';

interface WelcomeOverlayProps {
exiting: boolean;
Expand All @@ -20,14 +23,33 @@ interface WelcomeOverlayProps {
*/
const WelcomeOverlay: React.FC<WelcomeOverlayProps> = ({ handleGetStarted, handleTakeTour, exiting }) => {

const dispatch: AppDispatch = useAppDispatch();
const welcomeOverlayIsOpen = useAppSelector((state: AppState) => state.welcomeOverlayIsOpen);
const overlayContentRef = useRef<HTMLDivElement>(null);
const [closed, setClosed] = useState(false);

if (!welcomeOverlayIsOpen && !closed) {
dispatch(
setWelcomeOverlayIsOpen(true)
)
console.log("set")
}

const handleClickOutside = (event: React.MouseEvent) => {
if (overlayContentRef.current && !overlayContentRef.current.contains(event.target as Node)) {
handleGetStarted();
getStarted();
}
};

function getStarted(){
dispatch(
setWelcomeOverlayIsOpen(false)
);
setClosed(true);
console.log('closed')
handleGetStarted();
}

return (
<div
className={`z-50 w-full h-full fixed top-0 left-0 flex items-center justify-center`} // bg-gray-900 transition-opacity duration-100 ${exiting ? 'bg-opacity-0' : 'bg-opacity-80'}`}
Expand Down Expand Up @@ -77,7 +99,7 @@ const WelcomeOverlay: React.FC<WelcomeOverlayProps> = ({ handleGetStarted, handl
className="w-[9em] py-2 rounded-lg"
iconClassName='mr-[3px]'
title='Get Started'
onClick={handleGetStarted}
onClick={getStarted}
/>
<IconButton
icon={faGlasses}
Expand Down
39 changes: 39 additions & 0 deletions src/components/ranking/PhantomArrow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React, { useState, useEffect, useRef } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faArrowRight } from '@fortawesome/free-solid-svg-icons';

interface PhantomArrowProps {
show: boolean;
}

const PhantomArrow: React.FC<PhantomArrowProps> = ({ show }) => {
const [isVisible, setIsVisible] = useState(false);
const arrowRef = useRef<HTMLDivElement>(null);

useEffect(() => {
if (show) {
const showTimer = setTimeout(() => setIsVisible(true), 2000);
return () => clearTimeout(showTimer);
} else {
setIsVisible(false);
}
}, [show]);

if (!isVisible) return null;

return (
<div
ref={arrowRef}
className="fixed z-50 animate-phantomArrow"
style={{
top: '50%',
right: '50%',
transform: 'translateY(-50%)',
}}
>
<FontAwesomeIcon icon={faArrowRight} size="4x" className="text-slate-400 font-extrabold" />
</div>
);
};

export default PhantomArrow;
2 changes: 1 addition & 1 deletion src/components/ranking/RankedCountriesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ const RankedCountriesList: React.FC<RankedCountriesListProps> = ({
<IconButton
className={classNames(
"tour-step-4 ml-auto py-1 pl-[0.7em] pr-[0.9em] mr-0 w-[6em]",
{ "tada-animation": showUnranked && rankedItems?.length }
{ "tada-animation-6s": showUnranked && rankedItems?.length }
)}
onClick={() => dispatch(
setShowUnranked(!showUnranked)
Expand Down
96 changes: 47 additions & 49 deletions src/components/ranking/UnrankedCountriesList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,57 @@ import { Card } from './Card';
import { AppState } from '../../redux/store';
import { useAppSelector } from '../../hooks/stateHooks';
import { Draggable } from '@hello-pangea/dnd';
import PhantomArrow from './PhantomArrow';

/**
* Displays all ranked countries in the left column list on the select view
* @param
* @returns
* displays all ranked countries in the left column list on the select view
*/
const UnrankedCountriesList: React.FC = ({
}) => {

const unrankedItems = useAppSelector((state: AppState) => state.unrankedItems);

return (
<div className="min-w-[10em] max-w-[40vw] overflow-y-auto overflow-x-hidden flex-grow mr-0">
<StrictModeDroppable droppableId="unrankedItems" key={`strict-md`}>
{(provided) => (
<ul
key={`ranked-list-${unrankedItems.length}`}
{...provided.droppableProps}
ref={provided.innerRef}
className={classNames("pt-[0.3em] min-w-[10em] tour-step-2", "")}
>
{unrankedItems.map((item, index) => (
<Draggable
key={item.id.toString()}
draggableId={item.id.toString()}
index={index}
>
{(provided, snapshot) => {
return (
<li
key={item.id.toString()}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className="no-select m-2"
>
<Card
key={item.id.toString()}
className="m-auto text-slate-400 bg-'blue' no-select"
countryContestant={item}
isDragging={snapshot.isDragging}
/>
</li>
);
}}
</Draggable>
))}
{provided.placeholder}
</ul>
const UnrankedCountriesList: React.FC = () => {
const unrankedItems = useAppSelector((state: AppState) => state.unrankedItems);
const rankedItems = useAppSelector((state: AppState) => state.rankedItems);
const welcomeOverlayIsOpen = useAppSelector((state: AppState) => state.welcomeOverlayIsOpen);

return (
<div className="min-w-[10em] max-w-[40vw] overflow-y-auto overflow-x-hidden flex-grow mr-0 relative">
<StrictModeDroppable droppableId="unrankedItems" key={`strict-md`}>
{(provided) => (
<ul
key={`ranked-list-${unrankedItems.length}`}
{...provided.droppableProps}
ref={provided.innerRef}
className={classNames("pt-[0.3em] min-w-[10em] tour-step-2", "")}
>
{unrankedItems.map((item, index) => (
<Draggable
key={item.id.toString()}
draggableId={item.id.toString()}
index={index}
>
{(provided, snapshot) => (
<li
key={item.id.toString()}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className="no-select m-2 relative"
>
<Card
key={item.id.toString()}
className="m-auto text-slate-400 bg-'blue' no-select"
countryContestant={item}
isDragging={snapshot.isDragging}
/>
{index === 0 && !welcomeOverlayIsOpen && <PhantomArrow show={rankedItems.length === 0 && !welcomeOverlayIsOpen} />}
</li>
)}
</StrictModeDroppable>
</div>
);
</Draggable>
))}
{provided.placeholder}
</ul>
)}
</StrictModeDroppable>
</div>
);
};

export default UnrankedCountriesList;
54 changes: 50 additions & 4 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ code {

/* Styling the scrollbar handle */
::-webkit-scrollbar-thumb {
background: #7d7aa5;
border-radius: 5px;
background: #7d7aa585;
border-radius: 5px;
}

/* Handle hover */
::-webkit-scrollbar-thumb:hover {
background: #52506c;
background: #484663;
}

.custom-scrollbar::-webkit-scrollbar {
Expand Down Expand Up @@ -149,6 +149,19 @@ code {
background: linear-gradient(135deg, #13172b, #334678);
}

@keyframes phantomArrow {
0% { transform: translateY(-50%) translateX(0); opacity: 0; }
/* 10% { transform: translateY(-50%) translateX(0); opacity: 0.5; } */
25% { transform: translateY(-50%) translateX(15px); opacity: 0.5; }
47.5% { transform: translateY(-50%) translateX(0); opacity: 0.5; }
70% { transform: translateY(-50%) translateX(20px); opacity: 0; }
100% { transform: translateY(-50%) translateX(20px); opacity: 0; }
}

.animate-phantomArrow {
animation: phantomArrow 3s ease-in-out forwards;
}

.houseUser {
margin-top: 0.15em;
height: 1em;
Expand Down Expand Up @@ -329,7 +342,40 @@ code {
}

.bounce-right {
animation: bounceRight 12s ease-out infinite;
animation: bounceRight 6s ease-out infinite;
}

@keyframes tada-6s {
0%,
71.43% {
/* 5s out of 7s as static */
transform: scale(1) rotate(0deg);
}

74.29%,
82.86% {
transform: scale(1) rotate(0deg);
}

87.14%,
91.43%,
95.71% {
transform: scale(1.1) rotate(3deg);
}

89.29%,
93.57% {
transform: scale(1.1) rotate(-3deg);
}

100% {
transform: scale(1) rotate(0deg);
}
}

.tada-animation-6s {
animation: tada-6s 6s ease-in-out infinite;
will-change: transform;
}

.max-w-50vw-6em {
Expand Down
10 changes: 8 additions & 2 deletions src/redux/rootSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ interface AppState {
activeCategory: number | undefined;
showTotalRank: boolean;
showComparison: boolean;
tableState: TableState
tableState: TableState,
welcomeOverlayIsOpen: boolean;
}

const initialState: AppState = {
Expand All @@ -42,6 +43,7 @@ const initialState: AppState = {
activeCategory: undefined,
showTotalRank: false,
showComparison: false,
welcomeOverlayIsOpen: false,
tableState: {
sortColumn: 'year',
sortDirection: 'desc',
Expand Down Expand Up @@ -81,6 +83,9 @@ const rootSlice = createSlice({
setHeaderMenuOpen: (state, action: PayloadAction<boolean>) => {
state.headerMenuOpen = action.payload;
},
setWelcomeOverlayIsOpen: (state, action: PayloadAction<boolean>) => {
state.welcomeOverlayIsOpen = action.payload;
},
setRankedItems: (state, action: PayloadAction<CountryContestant[]>) => {
state.rankedItems = action.payload;
},
Expand Down Expand Up @@ -200,7 +205,8 @@ export const {
setSelectedContestants,
setPaginatedContestants,
addAllPaginatedContestants,
setGlobalSearch
setGlobalSearch,
setWelcomeOverlayIsOpen
} = rootSlice.actions;

export default rootSlice.reducer;

0 comments on commit 32aa800

Please sign in to comment.