Add expression set details page

This commit is contained in:
Thiago Chaves 2022-07-13 21:15:32 +03:00
parent 47ea97adaf
commit 1a4c2ce98f
9 changed files with 243 additions and 16 deletions

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-settings" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z" />
<circle cx="12" cy="12" r="3" />
</svg>

After

Width:  |  Height:  |  Size: 865 B

View File

@ -11,8 +11,8 @@ export function ExpressionSetCard({
}: ExpressionSetCardProps) { }: ExpressionSetCardProps) {
return ( return (
<article className="expression-set-card"> <article className="expression-set-card">
<h2 className="expression-set-card-name"> <h2 className="expression-set-card-header">
{name} <span className="expression-set-card-name">{name}</span>
<span className="expression-set-card-word-count"> <span className="expression-set-card-word-count">
{word_count} word(s) {word_count} word(s)
</span> </span>

View File

@ -0,0 +1,46 @@
import Image from "next/image";
import Link from "next/link";
export interface ExpressionSetInfo {
id: number;
name: string;
description: string;
word_count: number;
}
export function ExpressionSetInfo({
id,
name,
description,
word_count,
}: ExpressionSetInfo) {
return (
<section className="expression-set-info">
<h2 className="expression-set-info-header">
<span className="expression-set-info-name">{name}</span>
<Link
href={{
pathname: "expression-set/settings",
query: {
"set-id": id,
},
}}
passHref
>
<a className="icon-button justify-end">
<Image
src="/icons/settings.svg"
width="24"
height="24"
alt="settings"
/>
</a>
</Link>
</h2>
<span className="expression-set-info-word-count">
{word_count} word(s)
</span>
<p className="expression-set-info-description">{description}</p>
</section>
);
}

View File

@ -0,0 +1 @@
export * from "./ExpressionSetInfo";

View File

@ -6,12 +6,15 @@ import { UrlObject } from "url";
export interface NavigationItemProps { export interface NavigationItemProps {
text: string; text: string;
iconUrl?: string; iconUrl?: string;
href: string | UrlObject; href: string;
} }
export function NavigationItem({ text, iconUrl, href }: NavigationItemProps) { export function NavigationItem({ text, iconUrl, href }: NavigationItemProps) {
const router = useRouter(); const router = useRouter();
const active = router.pathname === href;
let active = false;
if (href === "/" && router.pathname === "/") active = true;
if (href !== "/" && router.pathname.startsWith(href)) active = true;
return ( return (
<li className={active ? "navigation-item active" : "navigation-item"}> <li className={active ? "navigation-item active" : "navigation-item"}>

View File

@ -1,15 +1,17 @@
import { PropsWithChildren } from "react"; import { PropsWithChildren } from "react";
import { Navigation } from "../Navigation"; import { Navigation } from "../Navigation";
export interface PageProps {} export interface PageProps {
className?: string;
}
export function Page({ children }: PropsWithChildren<PageProps>) { export function Page({ className, children }: PropsWithChildren<PageProps>) {
return ( return (
<div className="page"> <div className="page">
<header> <header>
<Navigation /> <Navigation />
</header> </header>
<main>{children}</main> <main className={className}>{children}</main>
</div> </div>
); );
} }

View File

@ -0,0 +1,70 @@
import type { NextPage } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import { ExpressionSetInfo } from "../../components/ExpressionSetInfo/ExpressionSetInfo";
import { Page } from "../../components/Page";
import { useExpressionData } from "../../hooks/useExpressionData";
const ExpressionSetDetailsPage: NextPage = () => {
const { query } = useRouter();
const { expression_sets, expression_to_expression_set } = useExpressionData();
// Fallback for expression set not found
const expression_set = expression_sets.find(
(item) => item.id === Number.parseInt(query["set-id"] as string)
);
if (!expression_set)
return (
<Page>
<p className="details">Expression set not found</p>
</Page>
);
// Fallback for expression set empty
const word_count = expression_to_expression_set.filter(
(item) => item.expression_set_id === expression_set.id
).length;
if (!word_count)
return (
<Page>
<div className="expression-set-page">
<ExpressionSetInfo
id={expression_set.id}
name={expression_set.name}
description={expression_set.description}
word_count={word_count}
/>
<p className="details grow scroll">
No expressions left in this set.
</p>
</div>
</Page>
);
return (
<Page>
<div className="expression-set-page">
<ExpressionSetInfo
id={expression_set.id}
name={expression_set.name}
description={expression_set.description}
word_count={word_count}
/>
<p className="details grow scroll">{/* TODO add details */}</p>
<Link
href={{
pathname: "expression-sets/practice",
query: { "set-id": expression_set.id },
}}
passHref
>
<a className="navigation-item-button">
<span>Practice</span>
</a>
</Link>
</div>
</Page>
);
};
export default ExpressionSetDetailsPage;

View File

@ -1,10 +1,10 @@
import type { NextPage } from "next"; import type { NextPage } from "next";
import Link from "next/link"; import Link from "next/link";
import { ExpressionSetCard } from "../components/ExpressionSetCard"; import { ExpressionSetCard } from "../../components/ExpressionSetCard";
import { Page } from "../components/Page"; import { Page } from "../../components/Page";
import { useExpressionData } from "../hooks/useExpressionData"; import { useExpressionData } from "../../hooks/useExpressionData";
const ExpressionSetsPage: NextPage = () => { const ExpressionSetListPage: NextPage = () => {
const { expression_sets, expression_to_expression_set } = useExpressionData(); const { expression_sets, expression_to_expression_set } = useExpressionData();
return ( return (
@ -13,7 +13,10 @@ const ExpressionSetsPage: NextPage = () => {
{expression_sets.map(({ id, name, description }) => ( {expression_sets.map(({ id, name, description }) => (
<Link <Link
key={id} key={id}
href={{ pathname: "/practice", query: { "set-id": id } }} href={{
pathname: "/expression-sets/details",
query: { "set-id": id },
}}
passHref passHref
> >
<a> <a>
@ -34,4 +37,4 @@ const ExpressionSetsPage: NextPage = () => {
); );
}; };
export default ExpressionSetsPage; export default ExpressionSetListPage;

View File

@ -3,6 +3,16 @@
background-color: darkslategray; background-color: darkslategray;
} }
/* Generics */
.grow {
flex: 1;
}
.scroll {
overflow-y: auto;
}
/* Page */ /* Page */
.page { .page {
@ -52,21 +62,61 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background-color: lavender; background-color: lavenderblush;
border: 1px solid darkslategray; border: 1px solid darkslategray;
font-weight: bold; font-weight: bold;
height: 64px; height: 64px;
} }
.navigation-item > a:hover { .navigation-item > a:hover {
background-color: lavenderblush; background-color: antiquewhite;
} }
.navigation-item.active > a { .navigation-item.active > a {
background-color: lightsteelblue; background-color: bisque;
cursor: default; cursor: default;
} }
.navigation-item-button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: lavenderblush;
border: 1px solid darkslategray;
font-weight: bold;
height: 64px;
}
.navigation-item-button:hover {
background-color: antiquewhite;
}
/* Misc classes */
.icon-button {
display: flex;
align-items: center;
justify-content: middle;
flex-shrink: 0;
padding: 4px;
border: 1px solid rgba(0, 0, 0, 0);
border-radius: 8px;
}
.icon-button:hover {
background-color: lavenderblush;
border: 1px solid slategray;
}
.details {
color: darkslategray;
font-size: 15px;
margin: 20px 0px;
overflow-y: auto;
}
/* Expression cards */ /* Expression cards */
.expression-card { .expression-card {
@ -130,11 +180,18 @@
word-wrap: break-word; word-wrap: break-word;
} }
.expression-set-card-header {
display: flex;
flex-direction: row;
align-items: center;
}
.expression-set-card-name { .expression-set-card-name {
color: darkslategray; color: darkslategray;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
margin: 10px 0px; margin: 10px 0px;
text-transform: capitalize;
} }
.expression-set-card-word-count { .expression-set-card-word-count {
@ -150,3 +207,41 @@
margin: 20px 0px 10px; margin: 20px 0px 10px;
overflow-y: auto; overflow-y: auto;
} }
/* Expression set page */
.expression-set-page {
display: flex;
flex-direction: column;
height: 100%;
}
.expression-set-info-header {
display: flex;
flex-direction: row;
align-items: center;
}
.expression-set-info-name {
flex: 1;
color: darkslategray;
font-size: 24px;
font-weight: bold;
margin: 10px 0px;
text-transform: capitalize;
word-wrap: break-word;
}
.expression-set-info-word-count {
flex: 1;
color: slategray;
font-family: monospace;
font-size: 12px;
}
.expression-set-info-description {
color: darkslategray;
font-size: 15px;
margin: 20px 0px 10px;
overflow-y: auto;
}