mirror of
https://github.com/acid-info/logos-press-engine.git
synced 2025-02-23 14:48:08 +00:00
add layout and its interactions
This commit is contained in:
parent
871936702f
commit
e7308edd41
5
.idea/.gitignore
generated
vendored
Normal file
5
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
6
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
</profile>
|
||||
</component>
|
12
.idea/lpe-frontend.iml
generated
Normal file
12
.idea/lpe-frontend.iml
generated
Normal file
@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/lpe-frontend.iml" filepath="$PROJECT_DIR$/.idea/lpe-frontend.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
58
src/components/FilterTags/FilterTags.tsx
Normal file
58
src/components/FilterTags/FilterTags.tsx
Normal file
@ -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 (
|
||||
<Container>
|
||||
<Tags>
|
||||
{tags.map((tag, index) => (
|
||||
<Tag
|
||||
size="small"
|
||||
disabled={false}
|
||||
key={index}
|
||||
onClick={() => onTagClick(tag)}
|
||||
selected={selectedTags.includes(tag)}
|
||||
variant={selectedTags.includes(tag) ? 'filled' : 'outlined'}
|
||||
>
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
</Tags>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
`
|
1
src/components/FilterTags/index.ts
Normal file
1
src/components/FilterTags/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as FilterTags } from './HeaderTags'
|
@ -1 +0,0 @@
|
||||
export { default as Header } from './Header';
|
@ -1,52 +0,0 @@
|
||||
import { Tag } from "@acid-info/lsd-react";
|
||||
import styled from "@emotion/styled";
|
||||
|
||||
export default function Header() {
|
||||
return (
|
||||
<Container>
|
||||
<Tags>
|
||||
<Tag size="small" disabled={false}>
|
||||
Privacy
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false}>
|
||||
Security
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false}>
|
||||
Liberty
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false}>
|
||||
Censorship
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false}>
|
||||
Decentralization
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false} style={{ whiteSpace: "nowrap" }}>
|
||||
Openness / inclusivity
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false}>
|
||||
Innovation
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false}>
|
||||
Interview
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false}>
|
||||
Podcast
|
||||
</Tag>
|
||||
<Tag size="small" disabled={false}>
|
||||
Law
|
||||
</Tag>
|
||||
</Tags>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 16px 0 16px 16px;
|
||||
`;
|
||||
|
||||
const Tags = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
overflow-x: auto;
|
||||
padding-right: 16px;
|
||||
`;
|
@ -1 +0,0 @@
|
||||
export { default as HeaderTags } from './HeaderTags';
|
@ -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 (
|
||||
<Container>
|
||||
<Typography genericFontFamily="serif" component="span" variant="h2">
|
||||
LOGOS →{" "}
|
||||
LOGOS →{' '}
|
||||
<Title genericFontFamily="serif" component="span" variant="h2">
|
||||
PRESS ENGINE
|
||||
</Title>
|
||||
@ -14,17 +14,17 @@ export default function Header() {
|
||||
Blog with media written by Logos members
|
||||
</Description>
|
||||
</Container>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 16px 16px 8px 16px;
|
||||
`;
|
||||
`
|
||||
|
||||
const Title = styled(Typography)`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
`
|
||||
|
||||
const Description = styled(Typography)`
|
||||
margin-top: 6px;
|
||||
`;
|
||||
`
|
1
src/components/Hero/index.ts
Normal file
1
src/components/Hero/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Hero } from './Hero'
|
@ -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) {
|
||||
</Selector>
|
||||
</Icons>
|
||||
</Container>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
||||
`;
|
||||
`
|
||||
|
5
src/components/Navbar/NavbarFiller.tsx
Normal file
5
src/components/Navbar/NavbarFiller.tsx
Normal file
@ -0,0 +1,5 @@
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
export const NavbarFiller = styled.div`
|
||||
height: var(--lpe-nav-rendered-height);
|
||||
`
|
@ -1,8 +0,0 @@
|
||||
.searchBox {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.searchBox > div {
|
||||
border-left: none !important;
|
||||
border-right: none !important;
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import { Autocomplete } from "@acid-info/lsd-react";
|
||||
import styles from "./Search.module.css";
|
||||
|
||||
export default function Search() {
|
||||
return (
|
||||
<Autocomplete
|
||||
className={styles.searchBox}
|
||||
placeholder="Search through the LPE posts.."
|
||||
withIcon
|
||||
/>
|
||||
);
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { default as Search } from './Search';
|
11
src/components/Searchbar/Search.module.css
Normal file
11
src/components/Searchbar/Search.module.css
Normal file
@ -0,0 +1,11 @@
|
||||
.searchBox {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.active::placeholder{
|
||||
/*opacity: 1 !important;*/
|
||||
}
|
||||
|
||||
.searchBox > div {
|
||||
border: none !important;
|
||||
}
|
130
src/components/Searchbar/Searchbar.tsx
Normal file
130
src/components/Searchbar/Searchbar.tsx
Normal file
@ -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<string | undefined>(undefined)
|
||||
const [filterTags, setFilterTags] = useState<string[]>([])
|
||||
|
||||
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<HTMLInputElement>) => {
|
||||
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 (
|
||||
<SearchbarContainer onUnfocus={() => setActive(false)}>
|
||||
<Autocomplete
|
||||
className={styles.searchBox}
|
||||
placeholder={
|
||||
searchScope === ESearchScope.GLOBAL
|
||||
? copyConfigs.search.searchbarPlaceholders.global()
|
||||
: copyConfigs.search.searchbarPlaceholders.article()
|
||||
}
|
||||
withIcon
|
||||
value={query as string}
|
||||
onFocus={() => setActive(true)}
|
||||
inputProps={{
|
||||
onChange: (e) => setQuery(e.target.value),
|
||||
onKeyUp: (e) => handleEnter(e),
|
||||
className: isCollapsed ? styles.active : '',
|
||||
}}
|
||||
/>
|
||||
<TagsWrapper className={active ? 'active' : ''}>
|
||||
<FilterTags
|
||||
tags={copyConfigs.search.filterTags}
|
||||
onTagClick={handleTagClick}
|
||||
selectedTags={filterTags}
|
||||
/>
|
||||
</TagsWrapper>
|
||||
|
||||
<Collapsed
|
||||
className={isCollapsed ? 'enabled' : ''}
|
||||
onClick={() => setActive(true)}
|
||||
>
|
||||
{constructCollapseText()}
|
||||
</Collapsed>
|
||||
</SearchbarContainer>
|
||||
)
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
`
|
58
src/components/Searchbar/SearchbarContainer.tsx
Normal file
58
src/components/Searchbar/SearchbarContainer.tsx
Normal file
@ -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<HTMLDivElement>(
|
||||
uiConfigs.navbarRenderedHeight,
|
||||
)
|
||||
const { isOutside } = useOutsideClick<HTMLDivElement>(stickyRef)
|
||||
const isScrolling = useIsScrolling()
|
||||
|
||||
useEffect(() => {
|
||||
if (isOutside && onUnfocus) {
|
||||
onUnfocus()
|
||||
}
|
||||
if (isScrolling && onUnfocus) {
|
||||
console.log('scrolling', isScrolling)
|
||||
onUnfocus()
|
||||
}
|
||||
}, [isOutside, stickyRef, isScrolling])
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchBarWrapper ref={stickyRef} className={sticky ? 'sticky' : ''}>
|
||||
{children}
|
||||
</SearchBarWrapper>
|
||||
<div
|
||||
style={{
|
||||
height: `${height}px`,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const SearchBarWrapper = styled.div<Props>`
|
||||
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;
|
||||
}
|
||||
`
|
1
src/components/Searchbar/index.ts
Normal file
1
src/components/Searchbar/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Searchbar } from './Searchbar'
|
20
src/configs/copy.configs.ts
Normal file
20
src/configs/copy.configs.ts
Normal file
@ -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',
|
||||
],
|
||||
},
|
||||
}
|
3
src/configs/ui.configs.ts
Normal file
3
src/configs/ui.configs.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const uiConfigs = {
|
||||
navbarRenderedHeight: 45,
|
||||
}
|
20
src/layouts/ArticleLayout/Article.layout.tsx
Normal file
20
src/layouts/ArticleLayout/Article.layout.tsx
Normal file
@ -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<any>) {
|
||||
const isDarkState = useIsDarkState()
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
|
||||
<NavbarFiller />
|
||||
<Searchbar searchScope={ESearchScope.ARTICLE} />
|
||||
</header>
|
||||
<main>{props.children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
1
src/layouts/ArticleLayout/index.ts
Normal file
1
src/layouts/ArticleLayout/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as ArticleLayout } from './Article.layout'
|
22
src/layouts/DefaultLayout/Default.layout.tsx
Normal file
22
src/layouts/DefaultLayout/Default.layout.tsx
Normal file
@ -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<any>) {
|
||||
const isDarkState = useIsDarkState()
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
|
||||
<NavbarFiller />
|
||||
<Hero />
|
||||
<Searchbar />
|
||||
</header>
|
||||
<main>{props.children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
1
src/layouts/DefaultLayout/index.ts
Normal file
1
src/layouts/DefaultLayout/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as DefaultLayout } from './Default.layout'
|
@ -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 (
|
||||
<>
|
||||
<Navbar isDark={isDarkState.get()} toggle={isDarkState.toggle} />
|
||||
<Header />
|
||||
<HeaderTags />
|
||||
<Search />
|
||||
</>
|
||||
);
|
||||
}
|
@ -1 +0,0 @@
|
||||
export { default as HeaderLayout } from './Header.layout';
|
@ -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) => <DefaultLayout>{page}</DefaultLayout>)
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={isDark ? defaultThemes.dark : defaultThemes.light}>
|
||||
<Component {...pageProps} />
|
||||
<Head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
|
||||
<title>Acid</title>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"
|
||||
/>
|
||||
</Head>
|
||||
<Global
|
||||
styles={css`
|
||||
@ -22,8 +32,12 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
:root {
|
||||
--lpe-nav-rendered-height: ${uiConfigs.navbarRenderedHeight}px;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</ThemeProvider>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
14
src/pages/article/[:slug].tsx
Normal file
14
src/pages/article/[:slug].tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { NextPage } from 'next'
|
||||
import { ArticleLayout } from '@/layouts/ArticleLayout'
|
||||
|
||||
type Props = NextPage<{}>
|
||||
|
||||
const ArticlePage = (props: Props) => {
|
||||
return <article>article</article>
|
||||
}
|
||||
|
||||
ArticlePage.getLayout = function getLayout(page) {
|
||||
return <ArticleLayout>{page}</ArticleLayout>
|
||||
}
|
||||
|
||||
export default ArticlePage
|
@ -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 (
|
||||
<>
|
||||
<Head>
|
||||
<title>Logos Press Engine</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Blog with media written by Logos members"
|
||||
/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<main>
|
||||
<HeaderLayout />
|
||||
<PostsDemo />
|
||||
</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
4
src/types/ui.types.ts
Normal file
4
src/types/ui.types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export enum ESearchScope {
|
||||
GLOBAL = 'global',
|
||||
ARTICLE = 'article',
|
||||
}
|
1
src/utils/general.utils.ts
Normal file
1
src/utils/general.utils.ts
Normal file
@ -0,0 +1 @@
|
||||
export const nope = (...args: any) => {}
|
59
src/utils/ui.utils.ts
Normal file
59
src/utils/ui.utils.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
export const useSticky = <T>(dy: number = 0) => {
|
||||
const stickyRef = useRef<T>(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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user