From e7308edd410b54fca68624ac5860d7d1c3d6eba8 Mon Sep 17 00:00:00 2001 From: amirhouieh Date: Thu, 27 Apr 2023 12:35:06 +0200 Subject: [PATCH] add layout and its interactions --- .idea/.gitignore | 5 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/lpe-frontend.iml | 12 ++ .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + src/components/FilterTags/FilterTags.tsx | 58 ++++++++ src/components/FilterTags/index.ts | 1 + src/components/Header/index.ts | 1 - src/components/HeaderTags/HeaderTags.tsx | 52 ------- src/components/HeaderTags/index.ts | 1 - .../{Header/Header.tsx => Hero/Hero.tsx} | 16 +-- src/components/Hero/index.ts | 1 + src/components/Navbar/Navbar.tsx | 29 ++-- src/components/Navbar/NavbarFiller.tsx | 5 + src/components/Search/Search.module.css | 8 -- src/components/Search/Search.tsx | 12 -- src/components/Search/index.ts | 1 - src/components/Searchbar/Search.module.css | 11 ++ src/components/Searchbar/Searchbar.tsx | 130 ++++++++++++++++++ .../Searchbar/SearchbarContainer.tsx | 58 ++++++++ src/components/Searchbar/index.ts | 1 + src/configs/copy.configs.ts | 20 +++ src/configs/ui.configs.ts | 3 + src/layouts/ArticleLayout/Article.layout.tsx | 20 +++ src/layouts/ArticleLayout/index.ts | 1 + src/layouts/DefaultLayout/Default.layout.tsx | 22 +++ src/layouts/DefaultLayout/index.ts | 1 + src/layouts/HeaderLayout/Header.layout.tsx | 17 --- src/layouts/HeaderLayout/index.ts | 1 - src/pages/_app.tsx | 32 +++-- src/pages/article/[:slug].tsx | 14 ++ src/pages/index.tsx | 16 +-- src/styles/globals.css | 107 -------------- src/types/ui.types.ts | 4 + src/utils/.placeholder | 0 src/utils/general.utils.ts | 1 + src/utils/ui.utils.ts | 59 ++++++++ 37 files changed, 496 insertions(+), 244 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/lpe-frontend.iml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 src/components/FilterTags/FilterTags.tsx create mode 100644 src/components/FilterTags/index.ts delete mode 100644 src/components/Header/index.ts delete mode 100644 src/components/HeaderTags/HeaderTags.tsx delete mode 100644 src/components/HeaderTags/index.ts rename src/components/{Header/Header.tsx => Hero/Hero.tsx} (77%) create mode 100644 src/components/Hero/index.ts create mode 100644 src/components/Navbar/NavbarFiller.tsx delete mode 100644 src/components/Search/Search.module.css delete mode 100644 src/components/Search/Search.tsx delete mode 100644 src/components/Search/index.ts create mode 100644 src/components/Searchbar/Search.module.css create mode 100644 src/components/Searchbar/Searchbar.tsx create mode 100644 src/components/Searchbar/SearchbarContainer.tsx create mode 100644 src/components/Searchbar/index.ts create mode 100644 src/configs/copy.configs.ts create mode 100644 src/configs/ui.configs.ts create mode 100644 src/layouts/ArticleLayout/Article.layout.tsx create mode 100644 src/layouts/ArticleLayout/index.ts create mode 100644 src/layouts/DefaultLayout/Default.layout.tsx create mode 100644 src/layouts/DefaultLayout/index.ts delete mode 100644 src/layouts/HeaderLayout/Header.layout.tsx delete mode 100644 src/layouts/HeaderLayout/index.ts create mode 100644 src/pages/article/[:slug].tsx create mode 100644 src/types/ui.types.ts delete mode 100644 src/utils/.placeholder create mode 100644 src/utils/general.utils.ts create mode 100644 src/utils/ui.utils.ts diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/lpe-frontend.iml b/.idea/lpe-frontend.iml new file mode 100644 index 0000000..0c8867d --- /dev/null +++ b/.idea/lpe-frontend.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c098ab5 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/components/FilterTags/FilterTags.tsx b/src/components/FilterTags/FilterTags.tsx new file mode 100644 index 0000000..f64ed11 --- /dev/null +++ b/src/components/FilterTags/FilterTags.tsx @@ -0,0 +1,58 @@ +import { Tag } from '@acid-info/lsd-react' +import styled from '@emotion/styled' +import { useState } from 'react' +import { nope } from '@/utils/general.utils' + +type FilterTagsProps = { + tags: string[] + selectedTags: string[] + onTagClick?: (tag: string) => void +} + +export default function FilterTags(props: FilterTagsProps) { + const { tags = [], onTagClick = nope, selectedTags } = props + return ( + + + {tags.map((tag, index) => ( + onTagClick(tag)} + selected={selectedTags.includes(tag)} + variant={selectedTags.includes(tag) ? 'filled' : 'outlined'} + > + {tag} + + ))} + + + ) +} + +const Container = styled.div` + padding: 8px 0; +` + +const Tags = styled.div` + display: flex; + gap: 8px; + overflow-x: auto; + padding-right: 14px; + + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ + + &::-webkit-scrollbar { + display: none; + } + + > *:first-child { + margin-left: 14px; + } + + > * { + white-space: nowrap; + } +` diff --git a/src/components/FilterTags/index.ts b/src/components/FilterTags/index.ts new file mode 100644 index 0000000..06b8e87 --- /dev/null +++ b/src/components/FilterTags/index.ts @@ -0,0 +1 @@ +export { default as FilterTags } from './HeaderTags' diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts deleted file mode 100644 index 2d31985..0000000 --- a/src/components/Header/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Header } from './Header'; \ No newline at end of file diff --git a/src/components/HeaderTags/HeaderTags.tsx b/src/components/HeaderTags/HeaderTags.tsx deleted file mode 100644 index 3e3ad9c..0000000 --- a/src/components/HeaderTags/HeaderTags.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Tag } from "@acid-info/lsd-react"; -import styled from "@emotion/styled"; - -export default function Header() { - return ( - - - - Privacy - - - Security - - - Liberty - - - Censorship - - - Decentralization - - - Openness / inclusivity - - - Innovation - - - Interview - - - Podcast - - - Law - - - - ); -} - -const Container = styled.div` - padding: 16px 0 16px 16px; -`; - -const Tags = styled.div` - display: flex; - gap: 8px; - overflow-x: auto; - padding-right: 16px; -`; diff --git a/src/components/HeaderTags/index.ts b/src/components/HeaderTags/index.ts deleted file mode 100644 index f80bc45..0000000 --- a/src/components/HeaderTags/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as HeaderTags } from './HeaderTags'; \ No newline at end of file diff --git a/src/components/Header/Header.tsx b/src/components/Hero/Hero.tsx similarity index 77% rename from src/components/Header/Header.tsx rename to src/components/Hero/Hero.tsx index d257254..ffa0c3b 100644 --- a/src/components/Header/Header.tsx +++ b/src/components/Hero/Hero.tsx @@ -1,11 +1,11 @@ -import { Typography } from "@acid-info/lsd-react"; -import styled from "@emotion/styled"; +import { Typography } from '@acid-info/lsd-react' +import styled from '@emotion/styled' -export default function Header() { +export default function Hero() { return ( - LOGOS →{" "} + LOGOS →{' '} PRESS ENGINE @@ -14,17 +14,17 @@ export default function Header() { Blog with media written by Logos members - ); + ) } const Container = styled.div` padding: 16px 16px 8px 16px; -`; +` const Title = styled(Typography)` white-space: nowrap; -`; +` const Description = styled(Typography)` margin-top: 6px; -`; +` diff --git a/src/components/Hero/index.ts b/src/components/Hero/index.ts new file mode 100644 index 0000000..a3465b7 --- /dev/null +++ b/src/components/Hero/index.ts @@ -0,0 +1 @@ +export { default as Hero } from './Hero' diff --git a/src/components/Navbar/Navbar.tsx b/src/components/Navbar/Navbar.tsx index 596940d..489c292 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -1,12 +1,12 @@ -import styled from "@emotion/styled"; -import { LogosIcon } from "../icons/LogosIcon"; -import { IconButton, Typography } from "@acid-info/lsd-react"; -import { MoonIcon } from "../icons/MoonIcon"; -import { SunIcon } from "../icons/SunIcon"; +import styled from '@emotion/styled' +import { LogosIcon } from '../icons/LogosIcon' +import { IconButton, Typography } from '@acid-info/lsd-react' +import { MoonIcon } from '../icons/MoonIcon' +import { SunIcon } from '../icons/SunIcon' interface NavbarProps { - isDark: boolean; - toggle: () => void; + isDark: boolean + toggle: () => void } export default function Navbar({ isDark, toggle }: NavbarProps) { @@ -22,22 +22,27 @@ export default function Navbar({ isDark, toggle }: NavbarProps) { - ); + ) } -const Container = styled.div` +const Container = styled.nav` display: flex; padding: 8px; align-items: center; justify-content: space-between; border-bottom: 1px solid rgb(var(--lsd-theme-primary)); -`; + position: fixed; + top: 0; + width: calc(100% - 16px); + background: rgb(var(--lsd-surface-primary)); + z-index: 100; +` const Icons = styled.div` display: flex; align-items: center; -`; +` const Selector = styled(IconButton)` border-left: none; -`; +` diff --git a/src/components/Navbar/NavbarFiller.tsx b/src/components/Navbar/NavbarFiller.tsx new file mode 100644 index 0000000..724707e --- /dev/null +++ b/src/components/Navbar/NavbarFiller.tsx @@ -0,0 +1,5 @@ +import styled from '@emotion/styled' + +export const NavbarFiller = styled.div` + height: var(--lpe-nav-rendered-height); +` diff --git a/src/components/Search/Search.module.css b/src/components/Search/Search.module.css deleted file mode 100644 index ba92798..0000000 --- a/src/components/Search/Search.module.css +++ /dev/null @@ -1,8 +0,0 @@ -.searchBox { - width: 100% !important; -} - -.searchBox > div { - border-left: none !important; - border-right: none !important; -} \ No newline at end of file diff --git a/src/components/Search/Search.tsx b/src/components/Search/Search.tsx deleted file mode 100644 index 97763b6..0000000 --- a/src/components/Search/Search.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { Autocomplete } from "@acid-info/lsd-react"; -import styles from "./Search.module.css"; - -export default function Search() { - return ( - - ); -} diff --git a/src/components/Search/index.ts b/src/components/Search/index.ts deleted file mode 100644 index 5f65bc8..0000000 --- a/src/components/Search/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Search } from './Search'; \ No newline at end of file diff --git a/src/components/Searchbar/Search.module.css b/src/components/Searchbar/Search.module.css new file mode 100644 index 0000000..71bfef8 --- /dev/null +++ b/src/components/Searchbar/Search.module.css @@ -0,0 +1,11 @@ +.searchBox { + width: 100% !important; +} + +.active::placeholder{ + /*opacity: 1 !important;*/ +} + +.searchBox > div { + border: none !important; +} diff --git a/src/components/Searchbar/Searchbar.tsx b/src/components/Searchbar/Searchbar.tsx new file mode 100644 index 0000000..1f03aaa --- /dev/null +++ b/src/components/Searchbar/Searchbar.tsx @@ -0,0 +1,130 @@ +import { Autocomplete } from '@acid-info/lsd-react' +import styles from './Search.module.css' +import { SearchbarContainer } from '@/components/Searchbar/SearchbarContainer' +import { copyConfigs } from '@/configs/copy.configs' +import { ESearchScope } from '@/types/ui.types' +import React, { useCallback, useEffect, useState } from 'react' +import FilterTags from '@/components/FilterTags/FilterTags' +import styled from '@emotion/styled' + +export type SearchbarProps = { + searchScope?: ESearchScope +} + +export default function Searchbar(props: SearchbarProps) { + const { searchScope = ESearchScope.GLOBAL } = props + + const [active, setActive] = useState(false) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(false) + + const [query, setQuery] = useState(undefined) + const [filterTags, setFilterTags] = useState([]) + + const performSearch = () => { + console.log('performing search', query, filterTags) + if (!validateSearch()) return + } + + useEffect(() => { + performSearch() + }, [filterTags]) + + const handleTagClick = (tag: string) => { + let newSelectedTags = [...filterTags] + if (newSelectedTags.includes(tag)) { + newSelectedTags = newSelectedTags.filter((t) => t !== tag) + } else { + newSelectedTags.push(tag) + } + setFilterTags(newSelectedTags) + } + + const handleEnter = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + performSearch() + } + } + + const validateSearch = () => { + return (query !== undefined && query.length > 0) || filterTags.length > 0 + } + + const isCollapsed = validateSearch() && !active + + const constructCollapseText = () => { + let txt = '' + if (query !== undefined && query.length > 0) { + txt += query + } + if (filterTags.length > 0) { + if (txt.length > 0) txt += ' . ' + txt += `${filterTags.map((t) => `[${t}]`).join(',')}` + } + return txt + } + + return ( + setActive(false)}> + setActive(true)} + inputProps={{ + onChange: (e) => setQuery(e.target.value), + onKeyUp: (e) => handleEnter(e), + className: isCollapsed ? styles.active : '', + }} + /> + + + + + setActive(true)} + > + {constructCollapseText()} + + + ) +} + +const TagsWrapper = styled.div` + transition: height 250ms ease-in-out; + overflow: hidden; + height: 0; + + &.active { + height: 45px; + } +` + +const Collapsed = styled.div` + background: rgb(var(--lsd-surface-primary)); + padding: 8px 14px; + width: calc(90% - 28px); + position: absolute; + z-index: auto; + + top: -100%; + left: 0; + + font-size: 14px; + + transition: top 250ms ease-in-out; + + &.enabled { + top: 0; + } +` diff --git a/src/components/Searchbar/SearchbarContainer.tsx b/src/components/Searchbar/SearchbarContainer.tsx new file mode 100644 index 0000000..2082909 --- /dev/null +++ b/src/components/Searchbar/SearchbarContainer.tsx @@ -0,0 +1,58 @@ +import styled from '@emotion/styled' +import { uiConfigs } from '@/configs/ui.configs' +import { useIsScrolling, useOutsideClick, useSticky } from '@/utils/ui.utils' +import { PropsWithChildren, useEffect } from 'react' +import { nope } from '@/utils/general.utils' + +type Props = PropsWithChildren<{ + onUnfocus?: () => void +}> + +export function SearchbarContainer({ children, onUnfocus = nope }: Props) { + const { sticky, stickyRef, height } = useSticky( + uiConfigs.navbarRenderedHeight, + ) + const { isOutside } = useOutsideClick(stickyRef) + const isScrolling = useIsScrolling() + + useEffect(() => { + if (isOutside && onUnfocus) { + onUnfocus() + } + if (isScrolling && onUnfocus) { + console.log('scrolling', isScrolling) + onUnfocus() + } + }, [isOutside, stickyRef, isScrolling]) + + return ( + <> + + {children} + +
+ + ) +} + +const SearchBarWrapper = styled.div` + display: block; + width: 100%; + background: rgb(var(--lsd-surface-primary)); + border-bottom: 1px solid rgb(var(--lsd-border-primary)); + border-top: 1px solid rgb(var(--lsd-border-primary)); + transition: all 0.2s ease-in-out; + position: relative; + + overflow: hidden; + + &.sticky { + position: fixed; + top: ${uiConfigs.navbarRenderedHeight - 1}px; + z-index: 100; + } +` diff --git a/src/components/Searchbar/index.ts b/src/components/Searchbar/index.ts new file mode 100644 index 0000000..6ece6a9 --- /dev/null +++ b/src/components/Searchbar/index.ts @@ -0,0 +1 @@ +export { default as Searchbar } from './Searchbar' diff --git a/src/configs/copy.configs.ts b/src/configs/copy.configs.ts new file mode 100644 index 0000000..9fe379c --- /dev/null +++ b/src/configs/copy.configs.ts @@ -0,0 +1,20 @@ +export const copyConfigs = { + search: { + searchbarPlaceholders: { + global: () => 'Search through the LPE posts...', + article: () => `Search through the article`, + }, + filterTags: [ + 'Privacy', + 'Security', + 'Liberty', + 'Censorship', + 'Decentralization', + 'Openness / inclusivity', + 'Innovation', + 'Interview', + 'Podcast', + 'Law', + ], + }, +} diff --git a/src/configs/ui.configs.ts b/src/configs/ui.configs.ts new file mode 100644 index 0000000..1036bc5 --- /dev/null +++ b/src/configs/ui.configs.ts @@ -0,0 +1,3 @@ +export const uiConfigs = { + navbarRenderedHeight: 45, +} diff --git a/src/layouts/ArticleLayout/Article.layout.tsx b/src/layouts/ArticleLayout/Article.layout.tsx new file mode 100644 index 0000000..0479971 --- /dev/null +++ b/src/layouts/ArticleLayout/Article.layout.tsx @@ -0,0 +1,20 @@ +import { Navbar } from '@/components/Navbar' +import useIsDarkState from '@/states/isDarkState/isDarkState' +import { PropsWithChildren } from 'react' +import { NavbarFiller } from '@/components/Navbar/NavbarFiller' +import { Searchbar } from '@/components/Searchbar' +import { ESearchScope } from '@/types/ui.types' + +export default function ArticleLayout(props: PropsWithChildren) { + const isDarkState = useIsDarkState() + return ( + <> +
+ + + +
+
{props.children}
+ + ) +} diff --git a/src/layouts/ArticleLayout/index.ts b/src/layouts/ArticleLayout/index.ts new file mode 100644 index 0000000..a30058d --- /dev/null +++ b/src/layouts/ArticleLayout/index.ts @@ -0,0 +1 @@ +export { default as ArticleLayout } from './Article.layout' diff --git a/src/layouts/DefaultLayout/Default.layout.tsx b/src/layouts/DefaultLayout/Default.layout.tsx new file mode 100644 index 0000000..7e1cf14 --- /dev/null +++ b/src/layouts/DefaultLayout/Default.layout.tsx @@ -0,0 +1,22 @@ +import { Navbar } from '@/components/Navbar' +import useIsDarkState from '@/states/isDarkState/isDarkState' +import { PropsWithChildren } from 'react' +import { Hero } from '@/components/Hero' +import { NavbarFiller } from '@/components/Navbar/NavbarFiller' +import { Searchbar } from '@/components/Searchbar' + +export default function DefaultLayout(props: PropsWithChildren) { + const isDarkState = useIsDarkState() + + return ( + <> +
+ + + + +
+
{props.children}
+ + ) +} diff --git a/src/layouts/DefaultLayout/index.ts b/src/layouts/DefaultLayout/index.ts new file mode 100644 index 0000000..c58a2b6 --- /dev/null +++ b/src/layouts/DefaultLayout/index.ts @@ -0,0 +1 @@ +export { default as DefaultLayout } from './Default.layout' diff --git a/src/layouts/HeaderLayout/Header.layout.tsx b/src/layouts/HeaderLayout/Header.layout.tsx deleted file mode 100644 index 3f9efb7..0000000 --- a/src/layouts/HeaderLayout/Header.layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { Header } from "@/components/Header"; -import { HeaderTags } from "@/components/HeaderTags"; -import { Navbar } from "@/components/Navbar"; -import { Search } from "@/components/Search"; -import useIsDarkState from "@/states/isDarkState/isDarkState"; - -export default function HeaderLayout() { - const isDarkState = useIsDarkState(); - return ( - <> - -
- - - - ); -} diff --git a/src/layouts/HeaderLayout/index.ts b/src/layouts/HeaderLayout/index.ts deleted file mode 100644 index aababef..0000000 --- a/src/layouts/HeaderLayout/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as HeaderLayout } from './Header.layout'; \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 4e15ff0..1b6048e 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,17 +1,27 @@ -import useIsDarkState from "@/states/isDarkState/isDarkState"; -import { defaultThemes, ThemeProvider } from "@acid-info/lsd-react"; -import { css, Global } from "@emotion/react"; -import type { AppProps } from "next/app"; -import Head from "next/head"; +import useIsDarkState from '@/states/isDarkState/isDarkState' +import { defaultThemes, ThemeProvider } from '@acid-info/lsd-react' +import { css, Global } from '@emotion/react' +import type { AppProps } from 'next/app' +import Head from 'next/head' +import { uiConfigs } from '@/configs/ui.configs' +import { DefaultLayout } from '@/layouts/DefaultLayout' export default function App({ Component, pageProps }: AppProps) { - const isDark = useIsDarkState().get(); + const isDark = useIsDarkState().get() + + //TODO: fix this + //@ts-ignore + const getLayout = + Component.getLayout || ((page) => {page}) return ( - - + Acid + + {getLayout()} - ); + ) } diff --git a/src/pages/article/[:slug].tsx b/src/pages/article/[:slug].tsx new file mode 100644 index 0000000..ccb7c14 --- /dev/null +++ b/src/pages/article/[:slug].tsx @@ -0,0 +1,14 @@ +import { NextPage } from 'next' +import { ArticleLayout } from '@/layouts/ArticleLayout' + +type Props = NextPage<{}> + +const ArticlePage = (props: Props) => { + return
article
+} + +ArticlePage.getLayout = function getLayout(page) { + return {page} +} + +export default ArticlePage diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 42cab72..0a3fb38 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,23 +1,9 @@ import PostsDemo from '@/components/Post/PostsDemo' -import { HeaderLayout } from '@/layouts/HeaderLayout' -import Head from 'next/head' export default function Home() { return ( <> - - Logos Press Engine - - - - -
- - -
+ ) } diff --git a/src/styles/globals.css b/src/styles/globals.css index d4f491e..e69de29 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -1,107 +0,0 @@ -:root { - --max-width: 1100px; - --border-radius: 12px; - --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', - 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', - 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace; - - --foreground-rgb: 0, 0, 0; - --background-start-rgb: 214, 219, 220; - --background-end-rgb: 255, 255, 255; - - --primary-glow: conic-gradient( - from 180deg at 50% 50%, - #16abff33 0deg, - #0885ff33 55deg, - #54d6ff33 120deg, - #0071ff33 160deg, - transparent 360deg - ); - --secondary-glow: radial-gradient( - rgba(255, 255, 255, 1), - rgba(255, 255, 255, 0) - ); - - --tile-start-rgb: 239, 245, 249; - --tile-end-rgb: 228, 232, 233; - --tile-border: conic-gradient( - #00000080, - #00000040, - #00000030, - #00000020, - #00000010, - #00000010, - #00000080 - ); - - --callout-rgb: 238, 240, 241; - --callout-border-rgb: 172, 175, 176; - --card-rgb: 180, 185, 188; - --card-border-rgb: 131, 134, 135; -} - -@media (prefers-color-scheme: dark) { - :root { - --foreground-rgb: 255, 255, 255; - --background-start-rgb: 0, 0, 0; - --background-end-rgb: 0, 0, 0; - - --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0)); - --secondary-glow: linear-gradient( - to bottom right, - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0), - rgba(1, 65, 255, 0.3) - ); - - --tile-start-rgb: 2, 13, 46; - --tile-end-rgb: 2, 5, 19; - --tile-border: conic-gradient( - #ffffff80, - #ffffff40, - #ffffff30, - #ffffff20, - #ffffff10, - #ffffff10, - #ffffff80 - ); - - --callout-rgb: 20, 20, 20; - --callout-border-rgb: 108, 108, 108; - --card-rgb: 100, 100, 100; - --card-border-rgb: 200, 200, 200; - } -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: rgb(var(--foreground-rgb)); - background: linear-gradient( - to bottom, - transparent, - rgb(var(--background-end-rgb)) - ) - rgb(var(--background-start-rgb)); -} - -a { - color: inherit; - text-decoration: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } -} diff --git a/src/types/ui.types.ts b/src/types/ui.types.ts new file mode 100644 index 0000000..6ef6e21 --- /dev/null +++ b/src/types/ui.types.ts @@ -0,0 +1,4 @@ +export enum ESearchScope { + GLOBAL = 'global', + ARTICLE = 'article', +} diff --git a/src/utils/.placeholder b/src/utils/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/src/utils/general.utils.ts b/src/utils/general.utils.ts new file mode 100644 index 0000000..3b10ad8 --- /dev/null +++ b/src/utils/general.utils.ts @@ -0,0 +1 @@ +export const nope = (...args: any) => {} diff --git a/src/utils/ui.utils.ts b/src/utils/ui.utils.ts new file mode 100644 index 0000000..46be6a1 --- /dev/null +++ b/src/utils/ui.utils.ts @@ -0,0 +1,59 @@ +import { useEffect, useRef, useState } from 'react' + +export const useSticky = (dy: number = 0) => { + const stickyRef = useRef(null) + const [sticky, setSticky] = useState(false) + const [offset, setOffset] = useState(0) + const [height, setHeight] = useState(0) + + useEffect(() => { + if (!stickyRef.current) { + return + } + setOffset(stickyRef.current.offsetTop) + setHeight(stickyRef.current.clientHeight) + }, [stickyRef, setOffset]) + + useEffect(() => { + const handleScroll = () => { + if (!stickyRef.current) { + return + } + setSticky(window.scrollY > offset - dy) + } + window.addEventListener('scroll', handleScroll) + return () => window.removeEventListener('scroll', handleScroll) + }, [dy, setSticky, stickyRef, offset]) + + return { stickyRef, sticky, height: sticky ? height : 0 } +} + +export const useOutsideClick = (ref) => { + const [isOutside, setIsOutside] = useState(false) + useEffect(() => { + function handleClickOutside(event) { + setIsOutside(ref.current && !ref.current.contains(event.target)) + } + document.addEventListener('mousedown', handleClickOutside) + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [ref]) + + return { isOutside } +} + +export const useIsScrolling = () => { + const [isScrolling, setIsScrolling] = useState(false) + useEffect(() => { + let timeout + function handleScroll() { + setIsScrolling(true) + clearTimeout(timeout) + timeout = setTimeout(() => setIsScrolling(false), 100) + } + window.addEventListener('scroll', handleScroll) + return () => window.removeEventListener('scroll', handleScroll) + }, [setIsScrolling]) + return isScrolling +}