diff --git a/apps/frontend/package.json b/apps/frontend/package.json index f407227..12fccd5 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -21,7 +21,7 @@ "@fontsource-variable/roboto-flex": "^5.1.0", "@fontsource/roboto": "^5.1.0", "@mdx-js/react": "^3.1.0", - "@star4/react": "^0.1.7", + "@star4/react": "^0.1.8", "@star4/vanilla-extract": "^0.0.14", "@tanstack/react-query": "^5.61.0", "@tanstack/react-query-devtools": "^5.61.0", diff --git a/apps/frontend/src/components/accordion/accordion.module.sass b/apps/frontend/src/components/accordion/accordion.module.sass index a51c59c..84edbef 100644 --- a/apps/frontend/src/components/accordion/accordion.module.sass +++ b/apps/frontend/src/components/accordion/accordion.module.sass @@ -21,12 +21,14 @@ transition-timing-function: star4.easing(emphasized) &__header + appearance: none outline: none border: none - // background-color: star4.color(surface-container-highest) + background: none cursor: pointer position: relative + width: 100% display: flex align-items: center justify-content: space-between @@ -39,6 +41,13 @@ color: star4.color(on-secondary-container) --star4-component-ripple-hover-color: #{star4.color(on-secondary-container)} --star4-component-ripple-pressed-color: #{star4.color(on-secondary-container)} + &__focus-ring + inset: 8px + border-radius: star4.shape(small) + + transition-property: inset + transition-duration: star4.duration(long2) + transition-timing-function: star4.easing(emphasized) &__headline @include star4.typescale(title, medium) font-family: var(--star4-typeface-brand) @@ -47,16 +56,28 @@ transition-timing-function: star4.easing(emphasized) &__icon - position: relative - display: flex - align-items: center - justify-content: center + // position: relative + // display: flex + // align-items: center + // justify-content: center - transform-origin: center + // transform-origin: center + + // transition-property: transform + // transition-duration: star4.duration(long2) + // transition-timing-function: star4.easing(emphasized) + + fill: none + stroke: currentcolor + stroke-width: 2px + width: 24 + height: 24 + + & > path + transition-property: d + transition-duration: star4.duration(long2) + transition-timing-function: star4.easing(emphasized) - transition-property: transform - transition-duration: star4.duration(long2) - transition-timing-function: star4.easing(emphasized) &__content-wrapper position: relative @@ -89,10 +110,10 @@ color: star4.color(on-surface) --star4-component-ripple-hover-color: #{star4.color(on-surface)} --star4-component-ripple-pressed-color: #{star4.color(on-surface)} + & .accordion__item__focus-ring + inset: 16px & .accordion__item__headline @include star4.typescale(title, large) - & .accordion__item__icon - transform: rotate(180deg) & .accordion__item__content-wrapper grid-template-rows: 1fr opacity: 1 diff --git a/apps/frontend/src/components/accordion/accordion.tsx b/apps/frontend/src/components/accordion/accordion.tsx index f460fcd..5398f73 100644 --- a/apps/frontend/src/components/accordion/accordion.tsx +++ b/apps/frontend/src/components/accordion/accordion.tsx @@ -1,4 +1,4 @@ -import { createIdentifiableElement, MaterialSymbol, Ripple } from "@star4/react"; +import { createIdentifiableElement, FocusRing, MaterialSymbol, Ripple } from "@star4/react"; import { Children, createContext, forwardRef, memo, useContext, useEffect, useRef, useState, type HTMLAttributes, type ReactNode } from "react" import clsx from "clsx"; @@ -119,7 +119,7 @@ const AccordionItemComponent = forwardRef(null); - const headerRef = useRef(null); + const headerRef = useRef(null); const { isFirst, isLast } = useAccordionItem(); @@ -137,18 +137,31 @@ const AccordionItemComponent = forwardRef -
setExpanded(prev => !prev)}> +

{headline}

-
+ {/*
-
-
+
*/} + + + +
diff --git a/apps/frontend/src/components/glossary/glossary-old.tsx b/apps/frontend/src/components/glossary/glossary-old.tsx new file mode 100644 index 0000000..598cf83 --- /dev/null +++ b/apps/frontend/src/components/glossary/glossary-old.tsx @@ -0,0 +1,183 @@ +import { ActionButton, ArrowForward, Button, createIdentifiableElement, IconButton, Lenis, ListItem, MaterialSymbol, Popover, usePresence } from "@star4/react"; +import { memo, useCallback, useEffect, useState, type CSSProperties, type UIEventHandler } from "react"; +import clsx from "clsx"; + +import styles from "./glossary.module.sass"; +import { Header } from "../header"; +import { Card } from "../card"; +import { THEME } from "~/theme"; +import { Accordion } from "../accordion"; + +import Budget from "./budget.mdx"; +import FinancePlan from "./finance-plan.mdx"; +import Economy from "./economy.mdx"; +import IncomeExpenses from "./income-expenses.mdx"; +import Investments from "./investments.mdx"; +import Loans from "./loans.mdx"; +import Rate from "./rate.mdx"; + +export namespace Glossary { + export type Props = {} +} + +const GlossaryComponent = function Glossary( + {}: Glossary.Props, +) { + const [isOpen, setIsOpen] = useState(false); + + const { isMounted, isEntering, isExiting, isAnimating, isVisible } = usePresence({ + source: isOpen, + transitionDuration: [800, 200], + }); + + useEffect( + () => { + const isScrollLocked = isMounted && !isAnimating + document.body.toggleAttribute( + "data-scroll-lock", + isScrollLocked, + ); + }, + [isMounted, isAnimating], + ); + + const [isScrolledUnder, setIsScrolledUnder] = useState(false); + const onScroll: UIEventHandler = useCallback( + (event) => { + const scrollY = event.currentTarget.scrollTop; + const newIsScrolledUnder = scrollY > 0; + setIsScrolledUnder(newIsScrolledUnder); + }, + [], + ); + return ( + <> + {/* setIsOpen(true)} + variant="filledTonal" + icon={} /> */} +
+
+ ); } diff --git a/apps/frontend/src/components/header/header.tsx b/apps/frontend/src/components/header/header.tsx index 30d17e0..178d54e 100644 --- a/apps/frontend/src/components/header/header.tsx +++ b/apps/frontend/src/components/header/header.tsx @@ -75,7 +75,7 @@ const HeaderComponent = forwardRef( {leading}
{headline} -
+
{trailing}
diff --git a/apps/frontend/src/components/shade/index.ts b/apps/frontend/src/components/shade/index.ts new file mode 100644 index 0000000..e9c89f1 --- /dev/null +++ b/apps/frontend/src/components/shade/index.ts @@ -0,0 +1 @@ +export * from "./shade"; diff --git a/apps/frontend/src/components/shade/shade.module.sass b/apps/frontend/src/components/shade/shade.module.sass new file mode 100644 index 0000000..f617e6c --- /dev/null +++ b/apps/frontend/src/components/shade/shade.module.sass @@ -0,0 +1,110 @@ +@use "~/sass/star4" + +$corner: 72 + +$backdrop-color: star4.color(surface-container-highest) +$container-color: star4.color(surface) + +@keyframes shade__backdrop-enter + from + backdrop-filter: blur(0px) + background-color: transparent + +.shade + &::backdrop + display: none + &__backdrop + position: absolute + inset: 0 + + animation-name: shade__backdrop-enter + animation-duration: 100ms + animation-timing-function: linear + animation-fill-mode: backwards + &--visible .shade__backdrop + backdrop-filter: blur(8px) + background-color: $backdrop-color + &--exiting .shade__backdrop + transition-property: backdrop-filter, background-color + transition-duration: 400ms + transition-timing-function: star4.easing(emphasized-accelerate) + + @keyframes shade__container-enter + from + bottom: 100% + transform: translateY(calc(-1px * $corner)) + + @keyframes shade__container__corner-enter + from + transform: scale(1) + + + &__container + position: absolute + top: 0 + right: 0 + left: 0 + bottom: 100% + overflow: hidden + + background-color: $container-color + + animation-name: shade__container-enter + animation-duration: 800ms + animation-timing-function: star4.easing(emphasized) + animation-fill-mode: backwards + overflow: visible + + transform: translateY(calc(-1px * $corner)) + + &::before, &::after + content: "" + position: absolute + top: 100% + width: #{$corner}px + height: #{$corner}px + background-color: $container-color + transform: scale(1) + animation-name: shade__container__corner-enter + animation-duration: 800ms + animation-timing-function: star4.easing(emphasized) + animation-fill-mode: backwards + + &::before + left: 0 + transform-origin: top left + // mask-image: radial-gradient(#{$corner}px at 100% 100%, #0000 99%, #000 101%) + clip-path: path("M 0 0 L #{$corner} 0 A #{$corner} #{$corner} 0 0 0 0 #{$corner}") + + &::after + right: 0 + transform-origin: top right + // mask-image: radial-gradient(#{$corner}px at 0% 100%, #0000 99%, #000 101%) + clip-path: path("M #{$corner} 0 L 0 0 A #{$corner} #{$corner} 0 0 1 #{$corner} #{$corner}") + &--visible .shade__container + bottom: 0 + transform: translateY(0) + + &::before, &::after + transform: scale(0) + + &--exiting .shade__container + transition-property: bottom, transform + transition-duration: 400ms + transition-timing-function: star4.easing(emphasized-accelerate) + + &::before, &::after + transition-property: transform + transition-duration: 400ms + transition-timing-function: star4.easing(emphasized-accelerate) + + &__scroller + + &__scroller + position: absolute + inset: 0 + overflow-x: hidden + overflow-y: scroll + + &--scroll-locked + overflow-y: hidden diff --git a/apps/frontend/src/components/shade/shade.tsx b/apps/frontend/src/components/shade/shade.tsx new file mode 100644 index 0000000..e5e68e6 --- /dev/null +++ b/apps/frontend/src/components/shade/shade.tsx @@ -0,0 +1,113 @@ +import { createIdentifiableElement, Lenis, Popover, useLenis, usePresence, usePreviousState } from "@star4/react"; +import type { ReactNode } from "@tanstack/react-router"; +import { forwardRef, memo, useCallback, useEffect, useState, type HTMLAttributes, type RefObject, type UIEventHandler } from "react" + +import clsx from "clsx"; + +import styles from "./shade.module.sass"; + +export namespace Shade { + export type Props = + & Omit< + Lenis.Wrapper.Props, + "children" + > + & { + onAnimate?: (entering: boolean, exiting: boolean) => void; + onMount?: () => void; + onUnmount?: () => void; + open?: boolean; + children?: ReactNode; + }; + export interface Element extends Lenis.Wrapper.Element {} +} + +const ShadeComponent = forwardRef( + function Shade( + { + className, + open = false, + onAnimate, + onMount, + onUnmount, + children, + ...rest + }, + forwardedRef, + ) { + const lenis = useLenis({ root: true })?.lenis; + + const { isMounted, isVisible, isEntering, isExiting, isAnimating } = usePresence({ + source: open, + transitionDuration: [800, 400] + }); + + useEffect( + () => { + const isScrollLocked = isMounted && !isAnimating; + + if(isScrollLocked) lenis?.stop(); + else lenis?.start(); + + document.body.toggleAttribute( + "data-scroll-lock", + isScrollLocked, + ); + }, + [lenis, isMounted, isAnimating], + ); + + useEffect( + () => { + if(isMounted) onMount?.(); + else onUnmount?.(); + }, + [isMounted], + ); + useEffect(() => { + onAnimate?.(isEntering, isExiting); + }, [isEntering, isExiting]); + + return ( + +
+
+ + + {children} + + +
+ + ); + } +); + +export const Shade = Object.assign( + memo(ShadeComponent), + createIdentifiableElement("IS_SHADE"), +) diff --git a/apps/frontend/src/routeTree.gen.ts b/apps/frontend/src/routeTree.gen.ts index e9af1ac..e16f4ad 100644 --- a/apps/frontend/src/routeTree.gen.ts +++ b/apps/frontend/src/routeTree.gen.ts @@ -18,9 +18,6 @@ import { Route as PlayIndexImport } from "./routes/play/index"; // Create Virtual Routes const IndexLazyImport = createFileRoute("/")(); -const authAuthLazyImport = createFileRoute("/(auth)/_auth")(); -const authAuthRegisterLazyImport = createFileRoute("/(auth)/_auth/register")(); -const authAuthLoginLazyImport = createFileRoute("/(auth)/_auth/login")(); // Create/Update Routes @@ -36,31 +33,6 @@ const PlayIndexRoute = PlayIndexImport.update({ getParentRoute: () => rootRoute, } as any); -const authAuthLazyRoute = authAuthLazyImport - .update({ - id: "/(auth)/_auth", - getParentRoute: () => rootRoute, - } as any) - .lazy(() => import("./routes/(auth)/_auth.lazy").then((d) => d.Route)); - -const authAuthRegisterLazyRoute = authAuthRegisterLazyImport - .update({ - id: "/register", - path: "/register", - getParentRoute: () => authAuthLazyRoute, - } as any) - .lazy(() => - import("./routes/(auth)/_auth.register.lazy").then((d) => d.Route), - ); - -const authAuthLoginLazyRoute = authAuthLoginLazyImport - .update({ - id: "/login", - path: "/login", - getParentRoute: () => authAuthLazyRoute, - } as any) - .lazy(() => import("./routes/(auth)/_auth.login.lazy").then((d) => d.Route)); - // Populate the FileRoutesByPath interface declare module "@tanstack/react-router" { @@ -72,13 +44,6 @@ declare module "@tanstack/react-router" { preLoaderRoute: typeof IndexLazyImport; parentRoute: typeof rootRoute; }; - "/(auth)/_auth": { - id: "/(auth)/_auth"; - path: "/"; - fullPath: "/"; - preLoaderRoute: typeof authAuthLazyImport; - parentRoute: typeof undefinedRoute; - }; "/play/": { id: "/play/"; path: "/play"; @@ -86,86 +51,43 @@ declare module "@tanstack/react-router" { preLoaderRoute: typeof PlayIndexImport; parentRoute: typeof rootRoute; }; - "/(auth)/_auth/login": { - id: "/(auth)/_auth/login"; - path: "/login"; - fullPath: "/login"; - preLoaderRoute: typeof authAuthLoginLazyImport; - parentRoute: typeof authAuthLazyImport; - }; - "/(auth)/_auth/register": { - id: "/(auth)/_auth/register"; - path: "/register"; - fullPath: "/register"; - preLoaderRoute: typeof authAuthRegisterLazyImport; - parentRoute: typeof authAuthLazyImport; - }; } } // Create and export the route tree -interface authAuthLazyRouteChildren { - authAuthLoginLazyRoute: typeof authAuthLoginLazyRoute; - authAuthRegisterLazyRoute: typeof authAuthRegisterLazyRoute; -} - -const authAuthLazyRouteChildren: authAuthLazyRouteChildren = { - authAuthLoginLazyRoute: authAuthLoginLazyRoute, - authAuthRegisterLazyRoute: authAuthRegisterLazyRoute, -}; - -const authAuthLazyRouteWithChildren = authAuthLazyRoute._addFileChildren( - authAuthLazyRouteChildren, -); - export interface FileRoutesByFullPath { - "/": typeof authAuthLazyRouteWithChildren; + "/": typeof IndexLazyRoute; "/play": typeof PlayIndexRoute; - "/login": typeof authAuthLoginLazyRoute; - "/register": typeof authAuthRegisterLazyRoute; } export interface FileRoutesByTo { - "/": typeof authAuthLazyRouteWithChildren; + "/": typeof IndexLazyRoute; "/play": typeof PlayIndexRoute; - "/login": typeof authAuthLoginLazyRoute; - "/register": typeof authAuthRegisterLazyRoute; } export interface FileRoutesById { __root__: typeof rootRoute; "/": typeof IndexLazyRoute; - "/(auth)/_auth": typeof authAuthLazyRouteWithChildren; "/play/": typeof PlayIndexRoute; - "/(auth)/_auth/login": typeof authAuthLoginLazyRoute; - "/(auth)/_auth/register": typeof authAuthRegisterLazyRoute; } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath; - fullPaths: "/" | "/play" | "/login" | "/register"; + fullPaths: "/" | "/play"; fileRoutesByTo: FileRoutesByTo; - to: "/" | "/play" | "/login" | "/register"; - id: - | "__root__" - | "/" - | "/(auth)/_auth" - | "/play/" - | "/(auth)/_auth/login" - | "/(auth)/_auth/register"; + to: "/" | "/play"; + id: "__root__" | "/" | "/play/"; fileRoutesById: FileRoutesById; } export interface RootRouteChildren { IndexLazyRoute: typeof IndexLazyRoute; - authAuthLazyRoute: typeof authAuthLazyRouteWithChildren; PlayIndexRoute: typeof PlayIndexRoute; } const rootRouteChildren: RootRouteChildren = { IndexLazyRoute: IndexLazyRoute, - authAuthLazyRoute: authAuthLazyRouteWithChildren, PlayIndexRoute: PlayIndexRoute, }; @@ -180,30 +102,14 @@ export const routeTree = rootRoute "filePath": "__root.tsx", "children": [ "/", - "/(auth)/_auth", "/play/" ] }, "/": { "filePath": "index.lazy.tsx" }, - "/(auth)/_auth": { - "filePath": "(auth)/_auth.lazy.tsx", - "children": [ - "/(auth)/_auth/login", - "/(auth)/_auth/register" - ] - }, "/play/": { "filePath": "play/index.tsx" - }, - "/(auth)/_auth/login": { - "filePath": "(auth)/_auth.login.lazy.tsx", - "parent": "/(auth)/_auth" - }, - "/(auth)/_auth/register": { - "filePath": "(auth)/_auth.register.lazy.tsx", - "parent": "/(auth)/_auth" } } } diff --git a/apps/frontend/src/routes/__root.tsx b/apps/frontend/src/routes/__root.tsx index cb8098f..2ff07f8 100644 --- a/apps/frontend/src/routes/__root.tsx +++ b/apps/frontend/src/routes/__root.tsx @@ -1,4 +1,4 @@ -import { Divider, Lenis } from "@star4/react"; +import { Divider, IconButton, Lenis, MaterialSymbol } from "@star4/react"; import { createRootRoute, createRootRouteWithContext, Outlet } from "@tanstack/react-router"; import { lazy, Suspense, type HTMLAttributes } from "react"; import { Glossary } from "~/components/glossary"; @@ -38,7 +38,16 @@ export const Route = createRootRouteWithContext()({ }}> -
} /> +
+ {/* } /> */} + + + } /> diff --git a/yarn.lock b/yarn.lock index 50bbb4f..dff609c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -272,7 +272,7 @@ __metadata: "@fontsource/roboto": "npm:^5.1.0" "@mdx-js/react": "npm:^3.1.0" "@mdx-js/rollup": "npm:^3.1.0" - "@star4/react": "npm:^0.1.7" + "@star4/react": "npm:^0.1.8" "@star4/vanilla-extract": "npm:^0.0.14" "@tanstack/react-query": "npm:^5.61.0" "@tanstack/react-query-devtools": "npm:^5.61.0" @@ -1195,9 +1195,9 @@ __metadata: languageName: node linkType: hard -"@star4/react@npm:^0.1.7": - version: 0.1.7 - resolution: "@star4/react@npm:0.1.7" +"@star4/react@npm:^0.1.8": + version: 0.1.8 + resolution: "@star4/react@npm:0.1.8" dependencies: "@floating-ui/dom": "npm:^1.6.12" "@floating-ui/react": "npm:^0.26.28" @@ -1212,7 +1212,7 @@ __metadata: lenis: "npm:^1.1.16" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" - checksum: 10c0/eb22fd7b25ed10e97d3e1052e6577106d971f9c2e0b9b8fd4d8304a4bec0d519da6494777a93c80d7b96288784837b2e133d5011018ff00518b3fe38765704b8 + checksum: 10c0/b8dd6b8c3839b45ea01f221211204a6e80d7fe73a41f4b57719a2ed74b04a094ec32a87d1cddfa64515ee6544773844aebbced1c8d74abd754ed905866bcb8a7 languageName: node linkType: hard