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) {
|
}: 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>
|
||||||
|
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 {
|
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"}>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
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 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;
|
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user