diff --git a/app/components/Article/Contents.tsx b/app/components/Article/Contents.tsx
index cba8c768..c0ae2d26 100644
--- a/app/components/Article/Contents.tsx
+++ b/app/components/Article/Contents.tsx
@@ -2,6 +2,7 @@ import {useRef, useEffect} from 'react'
import useIsMobile from '~/hooks/isMobile'
import {questionUrl} from '~/routesMapper'
import type {Glossary, PageId, GlossaryEntry} from '~/server-utils/stampy'
+import {togglePopup} from '../popups'
const footnoteHTML = (el: HTMLDivElement, e: HTMLAnchorElement): string | null => {
const id = e.getAttribute('href') || ''
@@ -19,19 +20,12 @@ const footnoteHTML = (el: HTMLDivElement, e: HTMLAnchorElement): string | null =
return elem.innerHTML
}
-const scrollToElement = (e: HTMLElement, offset?: number) => {
- const elementPosition = e.getBoundingClientRect().top + window.pageYOffset
- const offsetPosition = elementPosition - (offset || 0)
-
- window.scrollTo({top: offsetPosition, behavior: 'smooth'})
-}
-
const addPopup = (e: HTMLElement, id: string, contents: string, mobile?: boolean): HTMLElement => {
const preexisting = document.getElementById(id)
if (preexisting) return preexisting
const popup = document.createElement('div')
- popup.className = 'link-popup bordered small'
+ popup.className = 'link-popup bordered small background'
popup.innerHTML = contents
popup.id = id
@@ -43,14 +37,9 @@ const addPopup = (e: HTMLElement, id: string, contents: string, mobile?: boolean
popup.addEventListener('mouseover', () => popup.classList.add('shown'))
popup.addEventListener('mouseout', () => popup.classList.remove('shown'))
} else {
- const togglePopup = (event: Event) => {
- event.preventDefault()
- popup.classList.toggle('shown')
- document.body.classList.toggle('noscroll')
- scrollToElement(e, 16)
- }
- popup.addEventListener('click', togglePopup)
- e.addEventListener('click', togglePopup)
+ const toggle = () => popup.classList.toggle('shown')
+ popup.addEventListener('click', togglePopup(toggle, e))
+ e.addEventListener('click', togglePopup(toggle, e))
}
return popup
diff --git a/app/components/Article/article.css b/app/components/Article/article.css
index ed5b1d8f..2163894d 100644
--- a/app/components/Article/article.css
+++ b/app/components/Article/article.css
@@ -102,10 +102,7 @@ article .glossary-entry {
article .link-popup {
visibility: hidden;
- opacity: 0;
z-index: 4;
- position: absolute;
- top: 0;
width: 512px;
}
@@ -218,25 +215,14 @@ article .banner h3 .title {
flex-direction: row;
flex-wrap: wrap;
}
+
+ article .link-popup {
+ width: 100%;
+ }
article .footer-comtainer > div:nth-child(-n + 2) {
flex: 1 1;
}
article {
margin: 0;
}
-
- article .link-popup {
- position: fixed;
- top: 0;
- left: 0;
- width: 100vw;
- height: 100vh;
- background-color: #ff000033;
- overflow: scroll;
- }
- article .link-popup .footnote {
- margin: auto;
- background-color: white;
- width: 80%;
- }
}
diff --git a/app/components/ArticlesDropdown/dropdown.css b/app/components/ArticlesDropdown/dropdown.css
index bb39a400..745f3af1 100644
--- a/app/components/ArticlesDropdown/dropdown.css
+++ b/app/components/ArticlesDropdown/dropdown.css
@@ -5,7 +5,6 @@
}
.articles-dropdown-container {
- z-index: 100;
top: 80px;
position: absolute;
justify-content: space-between;
diff --git a/app/components/ArticlesDropdown/index.tsx b/app/components/ArticlesDropdown/index.tsx
index a98ba688..fb8d46b6 100644
--- a/app/components/ArticlesDropdown/index.tsx
+++ b/app/components/ArticlesDropdown/index.tsx
@@ -8,11 +8,50 @@ import Button from '~/components/Button'
import './dropdown.css'
import useIsMobile from '~/hooks/isMobile'
+type LinkProps = {
+ to: string
+ text: string
+ pageid?: string
+ className?: string
+ onClick: () => void
+}
+const Link = ({to, text, pageid, className, onClick}: LinkProps) => (
+
+
+ {text}
+
+
+)
+
+type ArticlesSectionProps = {
+ category: Category
+ toc: TOCItem[]
+ className?: string
+ hide: () => void
+}
+const ArticlesSection = ({category, toc, className, hide}: ArticlesSectionProps) => (
+
+
{category} sections
+ {toc
+ .filter((item) => item.category === category)
+ .map((item: TOCItem) => (
+
+ ))}
+
+)
+
export type ArticlesDropdownProps = {
toc: TOCItem[]
categories: Tag[]
+ fullWidth?: boolean
}
-export const ArticlesDropdown = ({toc, categories}: ArticlesDropdownProps) => {
+export const ArticlesDropdown = ({toc, categories, fullWidth}: ArticlesDropdownProps) => {
// The dropdown works by using the onHover pseudoclass, so will only hide once
// the mouse leaves it. When using client side changes, the mouse doesn't leave
// it, so it's always shown (until the mouse is moved out, of course). To get around
@@ -22,63 +61,26 @@ export const ArticlesDropdown = ({toc, categories}: ArticlesDropdownProps) => {
const hide = () => setHidden(true)
useEffect(() => setHidden(false), [hidden])
const mobile = useIsMobile()
- const Link = ({
- to,
- text,
- pageid,
- className,
- }: {
- to: string
- text: string
- pageid?: string
- className?: string
- }) => (
-
-
- {text}
-
-
- )
-
- const ArticlesSection = ({
- category,
- toc,
- className,
- }: {
- category: Category
- toc: TOCItem[]
- className?: string
- }) => (
-
-
{category} sections
- {toc
- .filter((item) => item.category === category)
- .map((item: TOCItem) => (
-
- ))}
-
- )
return hidden ? null : (
-
-
+
+
-
+
{/*sorted right side*/}
Browse by category
@@ -87,7 +89,13 @@ export const ArticlesDropdown = ({toc, categories}: ArticlesDropdownProps) => {
?.sort(sortFuncs['by number of questions'])
.slice(0, 12)
.map((tag) => (
-
+
))}
diff --git a/app/components/Button/button.css b/app/components/Button/button.css
index b44d9267..0f4a5060 100644
--- a/app/components/Button/button.css
+++ b/app/components/Button/button.css
@@ -183,6 +183,7 @@
/* #### Composite button #### */
.composite-button {
cursor: pointer;
+ display: flex;
}
.composite-button > form .button,
diff --git a/app/components/Button/index.tsx b/app/components/Button/index.tsx
index c578dfcd..2183cfdd 100644
--- a/app/components/Button/index.tsx
+++ b/app/components/Button/index.tsx
@@ -26,6 +26,8 @@ const Button = ({
(secondary && 'button-secondary') || 'button',
className,
tooltip && !secondary && 'tooltip',
+ secondary && active && 'active',
+ secondary && !active && !disabled && 'inactive',
]
.filter((i) => i)
.join(' ')
@@ -33,7 +35,7 @@ const Button = ({
return (
{
if (disabled) {
@@ -49,7 +51,7 @@ const Button = ({
}
return (
{
+const ReferencePopup = (
+ citation: Citation & {className?: string; onClose?: (event: MouseEvent) => void}
+) => {
const parsed = citation.text?.match(/^###.*?###\s+"""(.*?)"""$/ms)
if (!parsed) return undefined
+
return (
-
-
-
Referenced excerpt
-
+
+
+
+
Referenced excerpt
+
+
)
}
-const ReferenceLink = (citation: Citation) => {
+const ReferenceLink = (citation: Citation & {mobile?: boolean}) => {
+ const ref = useRef
(null)
+ const [shown, setShown] = useState(false)
+ const clickHandler = !citation.mobile
+ ? undefined
+ : togglePopup(() => setShown((current) => !current), ref.current || undefined)
+
const {id, index} = citation
if (!index || index > MAX_REFERENCES) return ''
return (
-
+
{index}
-
+
)
}
@@ -149,6 +171,7 @@ const Reference = (citation: Citation) => {
}
const ChatbotReply = ({question, phase, content, citationsMap}: AssistantEntry) => {
+ const mobile = useIsMobile()
const citations = [] as Citation[]
citationsMap?.forEach((v) => {
citations.push(v)
@@ -191,7 +214,7 @@ const ChatbotReply = ({question, phase, content, citationsMap}: AssistantEntry)
if (chunk?.match(/(\[\d+\])/)) {
const refId = chunk.slice(1, chunk.length - 1)
const ref = citationsMap?.get(refId)
- return ref &&
+ return ref &&
} else if (chunk === '\n') {
return
} else {
diff --git a/app/components/Chatbot/chat_entry.css b/app/components/Chatbot/chat_entry.css
index 8521394f..e8d7a2c1 100644
--- a/app/components/Chatbot/chat_entry.css
+++ b/app/components/Chatbot/chat_entry.css
@@ -147,26 +147,32 @@ article.stampy {
}
.reference-contents {
- visibility: hidden;
transition: visibility 0.2s;
- width: 512px;
word-wrap: break-word;
- background-color: white;
+ text-decoration: unset;
+ width: 512px;
border: 1px solid var(--colors-cool-grey-200, #dfe3e9);
padding: var(--spacing-40);
- position: absolute;
- transform: translateX(50%);
- text-decoration: unset;
- right: 0px;
- top: 40px;
+ background-color: var(--colors-white);
+ padding: var(--spacing-40);
+ position: relative;
}
.reference-contents .inner-html {
height: 275px;
overflow: hidden;
}
-.reference-contents:hover,
-.reference-link:hover + .reference-contents {
+@media (min-width: 780px) {
+ .reference-popup {
+ display: inline-block;
+ position: absolute;
+ transform: translate(-50%, 35px);
+ min-width: 30vw;
+ }
+}
+
+.reference-popup:hover,
+.reference-link:hover + .reference-popup {
visibility: visible;
transition-delay: 0s;
}
diff --git a/app/components/Chatbot/index.tsx b/app/components/Chatbot/index.tsx
index c181b66d..062d1dcf 100644
--- a/app/components/Chatbot/index.tsx
+++ b/app/components/Chatbot/index.tsx
@@ -87,7 +87,7 @@ const QuestionInput = ({
return (
{results.length > 0 ? (
diff --git a/app/components/Nav/Mobile/index.tsx b/app/components/Nav/Mobile/index.tsx
index 9465eada..c97e9d4e 100644
--- a/app/components/Nav/Mobile/index.tsx
+++ b/app/components/Nav/Mobile/index.tsx
@@ -1,9 +1,10 @@
-import React from 'react'
+import {ElementType, useState} from 'react'
import {Link} from '@remix-run/react'
import AISafetyIcon from '~/components/icons-generated/Aisafety'
-import {ListLarge} from '~/components/icons-generated'
-import {XLarge} from '~/components/icons-generated'
-import {CarrotLarge} from '~/components/icons-generated'
+import ListLarge from '~/components/icons-generated/ListLarge'
+import XLarge from '~/components/icons-generated/XLarge'
+import CarrotLarge from '~/components/icons-generated/CarrotLarge'
+import BotIcon from '~/components/icons-generated/Bot'
import OpenBookIcon from '~/components/icons-generated/OpenBook'
import MagnifyingLarge from '~/components/icons-generated/MagnifyingLarge'
import {NavProps} from '~/components/Nav'
@@ -12,80 +13,91 @@ import '../nav.css'
import './navMobile.css'
import ArticlesDropdown from '~/components/ArticlesDropdown'
import Search from '~/components/search'
-export const MobileNav = ({toc, categories}: NavProps) => {
- const [showMenu, setShowMenu] = React.useState(false)
- const [showSearch, setShowSearch] = React.useState(false)
- const [showArticles, setShowArticles] = React.useState(false)
- const toggleMenu = () => {
- setShowMenu(false)
- setShowArticles(false)
- setShowSearch(false)
- }
- const toggleSearch = () => {
- setShowSearch(!showSearch)
- }
- return (
-
+ )
)
}
+
+export const MobileNav = ({toc, categories}: NavProps) => {
+ const [current, setCurrent] = useState
('initial')
+ if (['initial', 'menu'].includes(current)) {
+ return
+ } else if (current === 'search') {
+ return setCurrent('initial')} />
+ } else {
+ return (
+
+ )
+ }
+}
export default MobileNav
diff --git a/app/components/Nav/Mobile/navMobile.css b/app/components/Nav/Mobile/navMobile.css
index 2d2fd9bc..3d8c3b17 100644
--- a/app/components/Nav/Mobile/navMobile.css
+++ b/app/components/Nav/Mobile/navMobile.css
@@ -1,96 +1,53 @@
@media only screen and (max-width: 780px) {
- .top-header {
- padding: 0;
- }
- .top-nav {
- justify-content: space-between;
- padding: var(--spacing-16);
- }
- .top-logo {
- padding: 0;
- }
- .composite-elements {
+ .flex-row {
display: flex;
+ flex-direction: row;
align-items: center;
- }
- .space-between {
justify-content: space-between;
+ gap: var(--spacing-8);
}
- .flex-start {
- justify-content: flex-start;
- }
- .mobile-menu > .button {
- border-radius: 0;
- padding: var(--spacing-80) var(--spacing-16);
- }
- .mobile-menu > .button:first-child {
- border-bottom: 0;
- }
- .icon-margin {
- margin-right: var(--spacing-8);
- }
- .articles-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: var(--spacing-16);
- border-bottom: 1px solid var(--colors-cool-grey-200);
- }
- .back-icon {
- cursor: pointer;
- transform: rotate(180deg);
- }
- .articles-mobile {
- padding: var(--spacing-24);
+
+ .top-header {
+ padding: var(--spacing-8) 0;
}
- .articles-dropdown-container {
- visibility: visible !important;
- flex-direction: column;
- border: 0;
+
+ .top-header .menu-item {
box-shadow: none;
- position: static !important;
- padding: 0 !important;
- width: 100% !important;
- }
- .search-icon {
- margin-right: var(--spacing-16);
- }
- .search-content > svg:nth-of-type(2) {
- visibility: hidden;
+ height: 80px;
+ border-bottom: solid 1px var(--colors-cool-grey-200);
+ border-radius: 0;
+ padding: 0 24px;
+ gap: var(--spacing-16);
}
- .mobile-searchbar {
+
+ .top-header.background {
+ background-color: var(--colors-white);
display: flex;
- flex-direction: row-reverse;
- flex-wrap: nowrap;
- align-content: center;
justify-content: flex-start;
align-items: flex-start;
+ flex-direction: column;
+ }
+ .top-header.background > * {
+ margin: inherit;
width: 100%;
}
- .mobile-searchbar > svg {
- margin-top: var(--spacing-12);
- margin-left: var(--spacing-16);
+
+ .search-bar {
+ padding: var(--spacing-24) var(--spacing-40);
+ gap: var(--spacing-16);
}
- .mobile-searchbar > * .search-box.expandable:focus-within,
- .mobile-searchbar > * .search-box.expandable {
+ .search-bar .search-box:focus-within,
+ .search-bar .search-box {
width: 100%;
}
- .container-search-results-mobile {
- height: 50vh;
- width: 100%;
- position: inherit;
- margin-top: var(--spacing-8);
- overflow: scroll;
+
+ .container-search-results {
+ width: calc(100% - 48px);
}
- .mobile-searchbar > div {
+
+ .articles-mobile .articles-dropdown-container {
+ visibility: visible;
+ flex-direction: column;
width: 100%;
}
- .expanded {
- position: fixed;
- z-index: 100;
- height: 100vh;
- width: 100vw;
- background: var(--colors-cool-grey-100);
- overflow: scroll;
- }
}
diff --git a/app/components/SearchResults/Dropdown.tsx b/app/components/SearchResults/Dropdown.tsx
index 16231c87..3ab2bc48 100644
--- a/app/components/SearchResults/Dropdown.tsx
+++ b/app/components/SearchResults/Dropdown.tsx
@@ -22,19 +22,25 @@ export interface SearchResultsProps {
url: string
}
-export const SearchResults = ({results}: {results: SearchResultsProps[]}) => {
+export const SearchResults = ({
+ results,
+ onSelect,
+}: {
+ results: SearchResultsProps[]
+ onSelect?: () => void
+}) => {
const isMobile = useIsMobile()
const noResults = results.length === 0
if (noResults) {
return (
-
+
)
}
return (
-
+
{results.map((result, i) => (
diff --git a/app/components/popups.ts b/app/components/popups.ts
new file mode 100644
index 00000000..3a8fdc52
--- /dev/null
+++ b/app/components/popups.ts
@@ -0,0 +1,14 @@
+export const scrollToElement = (e: HTMLElement, offset?: number) => {
+ const elementPosition = e.getBoundingClientRect().top + window.pageYOffset
+ const offsetPosition = elementPosition - (offset || 0)
+
+ window.scrollTo({top: offsetPosition, behavior: 'smooth'})
+}
+
+export const togglePopup =
+ (onToggle: () => void, reference?: HTMLElement) => (event: MouseEvent | React.MouseEvent) => {
+ event.preventDefault()
+ onToggle()
+ document.body.classList.toggle('noscroll')
+ reference && scrollToElement(reference, 16)
+ }
diff --git a/app/components/search.tsx b/app/components/search.tsx
index ca776976..8a795612 100644
--- a/app/components/search.tsx
+++ b/app/components/search.tsx
@@ -8,9 +8,10 @@ import useOutsideOnClick from '~/hooks/useOnOutsideClick'
type Props = {
limitFromUrl?: number
+ className?: string
}
-export default function Search({limitFromUrl}: Props) {
+export default function Search({limitFromUrl, className}: Props) {
const [showResults, setShowResults] = useState(false)
const [searchPhrase, setSearchPhrase] = useState('')
@@ -33,7 +34,7 @@ export default function Search({limitFromUrl}: Props) {
const handleChange = debounce(searchFn, 100)
return (
-
setShowResults(true)} ref={clickDetectorRef}>
+
setShowResults(true)} ref={clickDetectorRef}>
{isPendingSearch && results.length == 0 && (
@@ -41,6 +42,7 @@ export default function Search({limitFromUrl}: Props) {
)}
{!isPendingSearch && searchPhrase && showResults && (
setShowResults(false)}
results={results.map((r) => ({
title: r.title,
url: questionUrl(r),
diff --git a/app/root.css b/app/root.css
index 209b275f..a2b14600 100644
--- a/app/root.css
+++ b/app/root.css
@@ -459,6 +459,12 @@ svg {
.flex-double {
flex-grow: 2;
}
+.auto-left {
+ margin-left: auto;
+}
+.auto-right {
+ margin-right: auto;
+}
.centered {
margin: auto;
@@ -470,6 +476,11 @@ svg {
margin: 0 auto;
}
+.reverse-icon {
+ cursor: pointer;
+ transform: rotate(180deg);
+}
+
.pointer {
cursor: pointer;
}
@@ -603,6 +614,22 @@ svg {
padding: var(--spacing-32);
flex-direction: column;
}
+
+ .background {
+ position: fixed;
+ top: 0;
+ left: 0;
+ background-color: #1b2b3e99;
+ width: 100vw;
+ height: 100vh;
+ overflow: scroll;
+ }
+
+ .background > * {
+ margin: 198px auto;
+ background-color: var(--colors-white);
+ width: 80%;
+ }
} /* end mobile */
p,
textarea,