Add expression set details page
This commit is contained in:
parent
47ea97adaf
commit
1a4c2ce98f
7
public/icons/settings.svg
Normal file
7
public/icons/settings.svg
Normal 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 |
@ -11,8 +11,8 @@ export function ExpressionSetCard({
|
||||
}: ExpressionSetCardProps) {
|
||||
return (
|
||||
<article className="expression-set-card">
|
||||
<h2 className="expression-set-card-name">
|
||||
{name}
|
||||
<h2 className="expression-set-card-header">
|
||||
<span className="expression-set-card-name">{name}</span>
|
||||
<span className="expression-set-card-word-count">
|
||||
{word_count} word(s)
|
||||
</span>
|
||||
|
46
src/components/ExpressionSetInfo/ExpressionSetInfo.tsx
Normal file
46
src/components/ExpressionSetInfo/ExpressionSetInfo.tsx
Normal 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>
|
||||
);
|
||||
}
|
1
src/components/ExpressionSetInfo/index.ts
Normal file
1
src/components/ExpressionSetInfo/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./ExpressionSetInfo";
|
@ -6,12 +6,15 @@ import { UrlObject } from "url";
|
||||
export interface NavigationItemProps {
|
||||
text: string;
|
||||
iconUrl?: string;
|
||||
href: string | UrlObject;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export function NavigationItem({ text, iconUrl, href }: NavigationItemProps) {
|
||||
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 (
|
||||
<li className={active ? "navigation-item active" : "navigation-item"}>
|
||||
|
@ -1,15 +1,17 @@
|
||||
import { PropsWithChildren } from "react";
|
||||
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 (
|
||||
<div className="page">
|
||||
<header>
|
||||
<Navigation />
|
||||
</header>
|
||||
<main>{children}</main>
|
||||
<main className={className}>{children}</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
70
src/pages/expression-sets/details.tsx
Normal file
70
src/pages/expression-sets/details.tsx
Normal 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;
|
@ -1,10 +1,10 @@
|
||||
import type { NextPage } from "next";
|
||||
import Link from "next/link";
|
||||
import { ExpressionSetCard } from "../components/ExpressionSetCard";
|
||||
import { Page } from "../components/Page";
|
||||
import { useExpressionData } from "../hooks/useExpressionData";
|
||||
import { ExpressionSetCard } from "../../components/ExpressionSetCard";
|
||||
import { Page } from "../../components/Page";
|
||||
import { useExpressionData } from "../../hooks/useExpressionData";
|
||||
|
||||
const ExpressionSetsPage: NextPage = () => {
|
||||
const ExpressionSetListPage: NextPage = () => {
|
||||
const { expression_sets, expression_to_expression_set } = useExpressionData();
|
||||
|
||||
return (
|
||||
@ -13,7 +13,10 @@ const ExpressionSetsPage: NextPage = () => {
|
||||
{expression_sets.map(({ id, name, description }) => (
|
||||
<Link
|
||||
key={id}
|
||||
href={{ pathname: "/practice", query: { "set-id": id } }}
|
||||
href={{
|
||||
pathname: "/expression-sets/details",
|
||||
query: { "set-id": id },
|
||||
}}
|
||||
passHref
|
||||
>
|
||||
<a>
|
||||
@ -34,4 +37,4 @@ const ExpressionSetsPage: NextPage = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ExpressionSetsPage;
|
||||
export default ExpressionSetListPage;
|
@ -3,6 +3,16 @@
|
||||
background-color: darkslategray;
|
||||
}
|
||||
|
||||
/* Generics */
|
||||
|
||||
.grow {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.scroll {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Page */
|
||||
|
||||
.page {
|
||||
@ -52,21 +62,61 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background-color: lavender;
|
||||
background-color: lavenderblush;
|
||||
border: 1px solid darkslategray;
|
||||
font-weight: bold;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.navigation-item > a:hover {
|
||||
background-color: lavenderblush;
|
||||
background-color: antiquewhite;
|
||||
}
|
||||
|
||||
.navigation-item.active > a {
|
||||
background-color: lightsteelblue;
|
||||
background-color: bisque;
|
||||
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-card {
|
||||
@ -130,11 +180,18 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.expression-set-card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.expression-set-card-name {
|
||||
color: darkslategray;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin: 10px 0px;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.expression-set-card-word-count {
|
||||
@ -150,3 +207,41 @@
|
||||
margin: 20px 0px 10px;
|
||||
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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user