diff --git a/public/icons/settings.svg b/public/icons/settings.svg new file mode 100644 index 0000000..701e0d8 --- /dev/null +++ b/public/icons/settings.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/ExpressionSetCard/ExpressionSetCard.tsx b/src/components/ExpressionSetCard/ExpressionSetCard.tsx index c6f58d6..640b519 100644 --- a/src/components/ExpressionSetCard/ExpressionSetCard.tsx +++ b/src/components/ExpressionSetCard/ExpressionSetCard.tsx @@ -11,8 +11,8 @@ export function ExpressionSetCard({ }: ExpressionSetCardProps) { return (
-

- {name} +

+ {name} {word_count} word(s) diff --git a/src/components/ExpressionSetInfo/ExpressionSetInfo.tsx b/src/components/ExpressionSetInfo/ExpressionSetInfo.tsx new file mode 100644 index 0000000..2603d36 --- /dev/null +++ b/src/components/ExpressionSetInfo/ExpressionSetInfo.tsx @@ -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 ( +
+

+ {name} + + + settings + + +

+ + {word_count} word(s) + +

{description}

+
+ ); +} diff --git a/src/components/ExpressionSetInfo/index.ts b/src/components/ExpressionSetInfo/index.ts new file mode 100644 index 0000000..be1bd29 --- /dev/null +++ b/src/components/ExpressionSetInfo/index.ts @@ -0,0 +1 @@ +export * from "./ExpressionSetInfo"; diff --git a/src/components/Navigation/NavigationItem.tsx b/src/components/Navigation/NavigationItem.tsx index cc8cb78..07c4593 100644 --- a/src/components/Navigation/NavigationItem.tsx +++ b/src/components/Navigation/NavigationItem.tsx @@ -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 (
  • diff --git a/src/components/Page/Page.tsx b/src/components/Page/Page.tsx index 1eb8c24..e2c8dba 100644 --- a/src/components/Page/Page.tsx +++ b/src/components/Page/Page.tsx @@ -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) { +export function Page({ className, children }: PropsWithChildren) { return (
    -
    {children}
    +
    {children}
    ); } diff --git a/src/pages/expression-sets/details.tsx b/src/pages/expression-sets/details.tsx new file mode 100644 index 0000000..09d4261 --- /dev/null +++ b/src/pages/expression-sets/details.tsx @@ -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 ( + +

    Expression set not found

    +
    + ); + + // 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 ( + +
    + +

    + No expressions left in this set. +

    +
    +
    + ); + + return ( + +
    + +

    {/* TODO add details */}

    + + + Practice + + +
    +
    + ); +}; + +export default ExpressionSetDetailsPage; diff --git a/src/pages/expression-sets.tsx b/src/pages/expression-sets/index.tsx similarity index 65% rename from src/pages/expression-sets.tsx rename to src/pages/expression-sets/index.tsx index 5b00981..ab56714 100644 --- a/src/pages/expression-sets.tsx +++ b/src/pages/expression-sets/index.tsx @@ -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 }) => ( @@ -34,4 +37,4 @@ const ExpressionSetsPage: NextPage = () => { ); }; -export default ExpressionSetsPage; +export default ExpressionSetListPage; diff --git a/src/styles/components.css b/src/styles/components.css index efbab6d..2f34a41 100644 --- a/src/styles/components.css +++ b/src/styles/components.css @@ -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; +}