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 →{' '}
+
+ 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
+}