Skip to content

Commit

Permalink
Replace ellipsified result column headers with icons (#343)
Browse files Browse the repository at this point in the history
* Add results header icons

* Make results cells padding consistent

* Add new words icon

* Use GeoAlt instead of GeoAltFill

* Fix columns alignment

* Move ResultColumn type

* Remove "C" from Squares icon

* Make header icons smaller

* Optimize useTranslate

* Refactor useColumns

* Extract Header component

* Refactor useColumns to a map

* Format code

* Fix loading state border radius

* Fix loading state styling in the corners

* Fix alignment in Words icon

* Format code

* ESLint
  • Loading branch information
kamilmielnik authored Jul 13, 2024
1 parent 68d7f64 commit 39aeb23
Show file tree
Hide file tree
Showing 31 changed files with 415 additions and 241 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
@import 'styles/mixins';

.dictionary {
@include scrollbars;

position: relative;
max-height: var(--dictionary--height);
height: var(--dictionary--height);
overflow-y: auto;
word-break: break-word;
transition: var(--transition);
word-break: break-word;
overflow: hidden;

&.isAllowed {
background-color: var(--color--green--light);
Expand All @@ -19,6 +15,13 @@
}
}

.content {
@include scrollbars;

height: 100%;
overflow-y: auto;
}

.result {
transition: var(--transition);

Expand All @@ -43,7 +46,7 @@
}
}

.content {
.resultContent {
padding: var(--spacing--l);
}

Expand Down
74 changes: 38 additions & 36 deletions packages/scrabble-solver/src/components/Dictionary/Dictionary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,49 +25,51 @@ const Dictionary: FunctionComponent<Props> = ({ className }) => {
[styles.isNotAllowed]: isLastAllowed === false,
})}
>
{typeof error !== 'undefined' && !isLoading && <EmptyState variant="error">{error.message}</EmptyState>}
<div className={styles.content}>
{typeof error !== 'undefined' && !isLoading && <EmptyState variant="error">{error.message}</EmptyState>}

{typeof error === 'undefined' && !isLoading && results.length === 0 && (
<EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
)}
{typeof error === 'undefined' && !isLoading && results.length === 0 && (
<EmptyState variant="info">{translate('dictionary.empty-state.uninitialized')}</EmptyState>
)}

{results.map(({ definitions, exists, isAllowed, word }) => (
<div
className={classNames(styles.result, {
[styles.isAllowed]: isAllowed === true,
[styles.isNotAllowed]: isAllowed === false,
})}
key={word}
>
<div className={styles.content}>
{word && <h2 className={styles.word}>{word}</h2>}
{results.map(({ definitions, exists, isAllowed, word }) => (
<div
className={classNames(styles.result, {
[styles.isAllowed]: isAllowed === true,
[styles.isNotAllowed]: isAllowed === false,
})}
key={word}
>
<div className={styles.resultContent}>
{word && <h2 className={styles.word}>{word}</h2>}

{isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}
{isAllowed === false && <div>{translate('dictionary.empty-state.not-allowed')}</div>}

{isAllowed === true && definitions.length === 0 && (
<>
<div>
{exists
? translate('dictionary.empty-state.no-definitions')
: translate('dictionary.empty-state.no-results')}
</div>
</>
)}
{isAllowed === true && definitions.length === 0 && (
<>
<div>
{exists
? translate('dictionary.empty-state.no-definitions')
: translate('dictionary.empty-state.no-results')}
</div>
</>
)}

{isAllowed === true && definitions.length > 0 && (
<ul className={styles.definitions}>
{definitions.map((result, index) => (
<li key={index} className={styles.definition}>
{result}
</li>
))}
</ul>
)}
{isAllowed === true && definitions.length > 0 && (
<ul className={styles.definitions}>
{definitions.map((result, index) => (
<li key={index} className={styles.definition}>
{result}
</li>
))}
</ul>
)}

{!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
{!isLoading && isAllowed === null && <div>{translate('dictionary.empty-state.no-results')}</div>}
</div>
</div>
</div>
))}
))}
</div>

{isLoading && <Loading />}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
.dim {
position: absolute;
inset: 0;
border-radius: inherit;
}

.loading {
Expand Down
9 changes: 6 additions & 3 deletions packages/scrabble-solver/src/components/Results/Cell.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import classNames from 'classnames';
import { FunctionComponent, ReactNode } from 'react';
import { CSSProperties, FunctionComponent, ReactNode } from 'react';

import { selectLocale, useTranslate, useTypedSelector } from 'state';
import { TranslationKey } from 'types';
Expand All @@ -11,19 +11,22 @@ import styles from './Results.module.scss';
interface Props {
children?: ReactNode;
className?: string;
style?: CSSProperties;
translationKey: TranslationKey;
tooltip?: string | number;
value: string | number;
}

const Cell: FunctionComponent<Props> = ({ children, className, translationKey, tooltip, value }) => {
const Cell: FunctionComponent<Props> = ({ children, className, style, translationKey, tooltip, value }) => {
const translate = useTranslate();
const locale = useTypedSelector(selectLocale);
const formattedValue = value.toLocaleString(locale);

return (
<Tooltip tooltip={`${translate(translationKey)}: ${tooltip || formattedValue}`}>
<div className={classNames(styles.cell, className)}>{children || formattedValue}</div>
<div className={classNames(styles.cell, className)} style={style}>
{children || formattedValue}
</div>
</Tooltip>
);
};
Expand Down
99 changes: 99 additions & 0 deletions packages/scrabble-solver/src/components/Results/Header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { FunctionComponent } from 'react';

import { useAppLayout, useColumns } from 'hooks';
import { GeoAlt, OneTwoThree, Square, SquareA, SquareB, Squares, Words } from 'icons';
import { RESULTS_COLUMN_WIDTH } from 'parameters';
import { ResultColumnId } from 'types';

import HeaderButton from './HeaderButton';
import styles from './Results.module.scss';

const Results: FunctionComponent = () => {
const { resultWordWidth } = useAppLayout();
const columns = useColumns();

return (
<div className={styles.header}>
{columns[ResultColumnId.Coordinates] && (
<HeaderButton
className={styles.coordinates}
Icon={GeoAlt}
id={ResultColumnId.Coordinates}
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.Coordinates] }}
translationKey="settings.showCoordinates"
/>
)}

{columns[ResultColumnId.Word] && (
<HeaderButton
className={styles.word}
id={ResultColumnId.Word}
style={{ flexBasis: resultWordWidth }}
translationKey="common.word"
/>
)}

{columns[ResultColumnId.TilesCount] && (
<HeaderButton
className={styles.stat}
Icon={Squares}
id={ResultColumnId.TilesCount}
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.TilesCount] }}
translationKey="common.tiles"
/>
)}

{columns[ResultColumnId.VowelsCount] && (
<HeaderButton
className={styles.stat}
Icon={SquareA}
id={ResultColumnId.VowelsCount}
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.VowelsCount] }}
translationKey="common.vowels"
/>
)}

{columns[ResultColumnId.ConsonantsCount] && (
<HeaderButton
className={styles.stat}
Icon={SquareB}
id={ResultColumnId.ConsonantsCount}
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.ConsonantsCount] }}
translationKey="common.consonants"
/>
)}

{columns[ResultColumnId.BlanksCount] && (
<HeaderButton
className={styles.stat}
Icon={Square}
id={ResultColumnId.BlanksCount}
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.BlanksCount] }}
translationKey="common.blanks"
/>
)}

{columns[ResultColumnId.WordsCount] && (
<HeaderButton
className={styles.stat}
Icon={Words}
id={ResultColumnId.WordsCount}
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.WordsCount] }}
translationKey="common.words"
/>
)}

{columns[ResultColumnId.Points] && (
<HeaderButton
className={styles.points}
Icon={OneTwoThree}
id={ResultColumnId.Points}
style={{ flexBasis: RESULTS_COLUMN_WIDTH[ResultColumnId.Points] }}
translationKey="common.points"
/>
)}
</div>
);
};

export default Results;
31 changes: 18 additions & 13 deletions packages/scrabble-solver/src/components/Results/HeaderButton.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,47 @@
import classNames from 'classnames';
import { ReactElement, useCallback } from 'react';
import { CSSProperties, FunctionComponent, ReactElement, SVGAttributes, useCallback } from 'react';
import { useDispatch } from 'react-redux';

import { SortDown, SortUp } from 'icons';
import { resultsSlice, selectResultsSort, useTranslate, useTypedSelector } from 'state';
import { SortDirection } from 'types';
import { ResultColumnId, SortDirection, TranslationKey } from 'types';

import { Tooltip } from '../Tooltip';

import styles from './Results.module.scss';
import { Column } from './types';

interface Props {
column: Column;
className: string;
Icon?: FunctionComponent<SVGAttributes<SVGElement>>;
id: ResultColumnId;
translationKey: TranslationKey;
style?: CSSProperties;
}

const HeaderButton = ({ column }: Props): ReactElement => {
const HeaderButton = ({ className, Icon, id, translationKey, style }: Props): ReactElement => {
const dispatch = useDispatch();
const translate = useTranslate();
const sort = useTypedSelector(selectResultsSort);

const handleClick = useCallback(() => {
dispatch(resultsSlice.actions.sort(column.id));
}, [column.id, dispatch]);
dispatch(resultsSlice.actions.sort(id));
}, [dispatch, id]);

return (
<Tooltip tooltip={translate(column.translationKey)}>
<Tooltip tooltip={translate(translationKey)}>
<button
aria-label={translate(column.translationKey)}
className={classNames(styles.headerButton, column.className)}
key={column.id}
aria-label={translate(translationKey)}
className={classNames(styles.headerButton, className)}
style={style}
type="button"
onClick={handleClick}
>
<span className={styles.cell}>
<span className={styles.headerButtonLabel}>{translate(column.translationKey)}</span>
{Icon && <Icon className={styles.headerButtonIcon} />}

{sort.column === column.id && (
{!Icon && <span className={styles.headerButtonLabel}>{translate(translationKey)}</span>}

{sort.column === id && (
<>
{sort.direction === SortDirection.Ascending && <SortUp className={styles.sortIcon} />}
{sort.direction === SortDirection.Descending && <SortDown className={styles.sortIcon} />}
Expand Down
Loading

0 comments on commit 39aeb23

Please sign in to comment.