diff --git a/.gitignore b/.gitignore index 8f322f0..fb30cbd 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,5 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts + +.idea diff --git a/src/components/FilterTags/FilterTags.tsx b/src/components/FilterTags/FilterTags.tsx new file mode 100644 index 0000000..962528d --- /dev/null +++ b/src/components/FilterTags/FilterTags.tsx @@ -0,0 +1,56 @@ +import { Tag } from '@acid-info/lsd-react' +import styled from '@emotion/styled' +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)} + 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..4f7eb33 --- /dev/null +++ b/src/components/FilterTags/index.ts @@ -0,0 +1 @@ +export { default as FilterTags } from './FilterTags' diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx deleted file mode 100644 index d257254..0000000 --- a/src/components/Header/Header.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { Typography } from "@acid-info/lsd-react"; -import styled from "@emotion/styled"; - -export default function Header() { - return ( - - - LOGOS →{" "} - - PRESS ENGINE - - - - 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/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/Hero/Hero.tsx b/src/components/Hero/Hero.tsx new file mode 100644 index 0000000..733e13d --- /dev/null +++ b/src/components/Hero/Hero.tsx @@ -0,0 +1,46 @@ +import { Typography } from '@acid-info/lsd-react' +import styled from '@emotion/styled' + +export default function Hero() { + return ( + + + LOGOS →{' '} + <Title + style={{ whiteSpace: 'nowrap' }} + genericFontFamily="serif" + component="span" + variant="h2" + > + PRESS ENGINE + + + + Blog with media written by Logos members + + + ) +} + +const Container = styled.div` + display: flex; + flex-direction: column; + align-items: center; + padding: 16px 16px 8px 16px; + + @media (max-width: 768px) { + align-items: flex-start; + } +` + +const Title = styled(Typography)` + // temporary breakpoint + @media (min-width: 1440px) { + padding-block: 16px; + font-size: 90px; + } +` + +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..fefe2cd 100644 --- a/src/components/Navbar/Navbar.tsx +++ b/src/components/Navbar/Navbar.tsx @@ -1,18 +1,20 @@ -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 { IconButton, Typography } from '@acid-info/lsd-react' +import { LogosIcon } from '../Icons/LogosIcon' +import { SunIcon } from '../Icons/SunIcon' +import { MoonIcon } from '../Icons/MoonIcon' interface NavbarProps { - isDark: boolean; - toggle: () => void; + isDark: boolean + toggle: () => void } export default function Navbar({ isDark, toggle }: NavbarProps) { return ( - + + + toggle()}> {isDark ? : } @@ -22,22 +24,37 @@ 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 LogosIconContainer = styled.div` + display: flex; + align-items: center; + margin-left: auto; + @media (max-width: 768px) { + margin-left: unset; + } +` const Icons = styled.div` display: flex; align-items: center; -`; + margin-left: auto; +` 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/Post/Post.tsx b/src/components/Post/Post.tsx index 5ec3cc6..7ad990a 100644 --- a/src/components/Post/Post.tsx +++ b/src/components/Post/Post.tsx @@ -68,7 +68,7 @@ export default function Post({ {description} ), - [classType, description, size], + [classType, description], ) const _thumbnail = useMemo(() => { diff --git a/src/components/Post/PostsDemo.tsx b/src/components/Post/PostsDemo.tsx index f67cf9d..042fc0a 100644 --- a/src/components/Post/PostsDemo.tsx +++ b/src/components/Post/PostsDemo.tsx @@ -1,4 +1,5 @@ -import Post, { PostProps } from './Post' +import { PostContainer } from '../PostContainer' +import { PostProps } from './Post' const postsData: PostProps[] = [ { @@ -18,7 +19,7 @@ const postsData: PostProps[] = [ tags: ['Privacy', 'Security', 'Liberty'], }, { - aspectRatio: 'portrait', // different aspect ratio + aspectRatio: 'portrait', // different aspect ratio - portrait imageUrl: 'https://images.pexels.com/photos/4992820/pexels-photo-4992820.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2', date: new Date(), @@ -56,31 +57,67 @@ const postsData: PostProps[] = [ description: 'We built a pedal-powered generator and controller, which is practical to use as an energy source and exercise machine in a household -- and which you can integrate into a solar PV', }, + { + showImage: false, // without image + classType: 'article', + date: new Date(), + title: 'Satoshi breaks their silence: Inside the mind of the OG anon', + description: + "Bitcoin's creator reveals their feelings on privacy, CBDCs and their favorite NFT collection in an unprecedented interview with Acid.info", + author: 'Jason Freeman', + tags: ['Privacy', 'Security', 'Liberty'], + }, + { + aspectRatio: 'square', // square + imageUrl: + 'https://images.pexels.com/photos/6477673/pexels-photo-6477673.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2', + date: new Date(), + title: 'How to Build a Practical Household Bike Generator', + description: + 'We built a pedal-powered generator and controller, which is practical to use as an energy source and exercise machine in a household -- and which you can integrate into a solar PV', + author: 'Jason Freeman', + tags: ['Privacy', 'Security', 'Liberty'], + }, + { + // featured + imageUrl: + 'https://images.pexels.com/photos/6227715/pexels-photo-6227715.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2', + date: new Date(), + title: 'How to Build a Practical Household Bike Generator', + description: + 'We built a pedal-powered generator and controller, which is practical to use as an energy source and exercise machine in a household -- and which you can integrate into a solar PV', + author: 'Jason Freeman', + tags: ['Privacy', 'Security', 'Liberty'], + }, ] const PostsDemo = () => { return (
{/* For Demo purposes only. Use inline CSS and styled components temporarily */} -
- {postsData.map((post, index) => ( -
- -
- ))} -
+ + + + + +
) } diff --git a/src/components/Post/index.ts b/src/components/Post/index.ts index 1083f45..2d3d965 100644 --- a/src/components/Post/index.ts +++ b/src/components/Post/index.ts @@ -1,2 +1,3 @@ export { default as Post } from './Post' export { default as PostsDemo } from './PostsDemo' +export type { PostProps } from './Post' diff --git a/src/components/PostContainer/PostContainer.tsx b/src/components/PostContainer/PostContainer.tsx new file mode 100644 index 0000000..fbe4aac --- /dev/null +++ b/src/components/PostContainer/PostContainer.tsx @@ -0,0 +1,51 @@ +import { CommonProps } from '@acid-info/lsd-react/dist/utils/useCommonProps' +import styled from '@emotion/styled' +import { Post, PostProps } from '../Post' +import { Typography } from '@acid-info/lsd-react' + +export type PostContainerProps = CommonProps & + React.HTMLAttributes & { + title?: string + postsData: PostProps[] + } + +export default function PostContainer({ + title, + postsData, + ...props +}: PostContainerProps) { + return ( +
+ {title && ({title})} + + {postsData.map((post, index) => ( + + + + ))} + +
+ ) +} + +const Container = styled.div` + display: flex; + flex-direction: row; + padding: 16px; + gap: 24px; + + // temporariy breakpoint + @media (max-width: 768px) { + flex-direction: column; + } +` + +const PostWrapper = styled.div` + padding: 16px 0; + border-top: 1px solid rgb(var(--lsd-theme-primary)); + width: 100%; +` + +const Title = styled(Typography)` + padding: 0 16px; +` diff --git a/src/components/PostContainer/index.ts b/src/components/PostContainer/index.ts new file mode 100644 index 0000000..8ca9444 --- /dev/null +++ b/src/components/PostContainer/index.ts @@ -0,0 +1 @@ +export { default as PostContainer } from './PostContainer' 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..eadf012 --- /dev/null +++ b/src/components/Searchbar/Search.module.css @@ -0,0 +1,11 @@ +.searchBox { + width: 100% !important; +} + +.searchButton{ + border: none !important; +} + +.searchBox{ + border: none !important; +} diff --git a/src/components/Searchbar/Searchbar.tsx b/src/components/Searchbar/Searchbar.tsx new file mode 100644 index 0000000..9983b0a --- /dev/null +++ b/src/components/Searchbar/Searchbar.tsx @@ -0,0 +1,167 @@ +import { + TextField, + Autocomplete, + IconButton, + SearchIcon, + CloseIcon, +} 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('') + const [filterTags, setFilterTags] = useState([]) + + const validateSearch = useCallback(() => { + return query.length > 0 || filterTags.length > 0 + }, [query, filterTags]) + + const performSearch = useCallback(() => { + if (!validateSearch()) return + }, [validateSearch]) + + const performClear = useCallback(() => { + // TODO: clear input.value seems to be not working. When set to undefined, the input value is still there. + setQuery('') + setFilterTags([]) + }, [setQuery, setFilterTags]) + + useEffect(() => { + performSearch() + }, [filterTags, performSearch]) + + 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 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 + } + + const isCollapsed = validateSearch() && !active + + const placeholder = + searchScope === ESearchScope.GLOBAL + ? copyConfigs.search.searchbarPlaceholders.global() + : copyConfigs.search.searchbarPlaceholders.article() + + return ( + setActive(false)}> + + setActive(true)} + onChange={(e) => { + setQuery(e.target.value) + }} + /> +
+ + validateSearch() ? performClear() : performSearch() + } + > + {validateSearch() ? : } + +
+
+ + + + setActive(true)} + dangerouslySetInnerHTML={{ __html: constructCollapseText() }} + /> +
+ ) +} + +const TagsWrapper = styled.div` + transition: height 250ms ease-in-out; + overflow: hidden; + height: 0; + + &.active { + height: 45px; + } +` + +const Collapsed = styled.div` + display: flex; + align-items: baseline; + 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; + } + > * { + margin-right: 4px; + } + > *:not(:first-child) { + margin-left: 4px; + } + + b { + transform: translateY(-2px); + } +` +const SearchBox = styled.div` + display: flex; + align-items: center; + width: 100%; + height: 100%; +` diff --git a/src/components/Searchbar/SearchbarContainer.tsx b/src/components/Searchbar/SearchbarContainer.tsx new file mode 100644 index 0000000..755be2d --- /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, onUnfocus]) + + 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..e2f0ab5 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,17 +1,40 @@ -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' +import { ReactNode } from 'react' +import { NextComponentType, NextPageContext } from 'next' -export default function App({ Component, pageProps }: AppProps) { - const isDark = useIsDarkState().get(); +type NextLayoutComponentType

= NextComponentType< + NextPageContext, + any, + P +> & { + getLayout?: (page: ReactNode) => ReactNode +} + +type AppLayoutProps

= AppProps & { + Component: NextLayoutComponentType +} + +export default function App({ Component, pageProps }: AppLayoutProps) { + const isDark = useIsDarkState().get() + + const getLayout = + Component.getLayout || + ((page: ReactNode) => {page}) return ( - - + Acid + + {getLayout()} - ); + ) } diff --git a/src/pages/article/[:slug].tsx b/src/pages/article/[:slug].tsx new file mode 100644 index 0000000..f820bd8 --- /dev/null +++ b/src/pages/article/[:slug].tsx @@ -0,0 +1,15 @@ +import { NextPage } from 'next' +import { ArticleLayout } from '@/layouts/ArticleLayout' +import { ReactNode } from 'react' + +type Props = NextPage<{}> + +const ArticlePage = (props: Props) => { + return

article
+} + +ArticlePage.getLayout = function getLayout(page: ReactNode) { + 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..fcf9697 --- /dev/null +++ b/src/utils/ui.utils.ts @@ -0,0 +1,64 @@ +import { RefObject, 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: RefObject, +) => { + const [isOutside, setIsOutside] = useState(false) + + useEffect(() => { + function handleClickOutside(event: any) { + const isOutside = !!(ref.current && !ref.current.contains(event.target)) + setIsOutside(isOutside) + } + document.addEventListener('mousedown', handleClickOutside) + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [ref]) + + return { isOutside } +} + +export const useIsScrolling = () => { + const [isScrolling, setIsScrolling] = useState(false) + useEffect(() => { + let timeout: NodeJS.Timeout + function handleScroll() { + setIsScrolling(true) + clearTimeout(timeout) + timeout = setTimeout(() => setIsScrolling(false), 100) + } + window.addEventListener('scroll', handleScroll) + return () => window.removeEventListener('scroll', handleScroll) + }, [setIsScrolling]) + return isScrolling +}