[docs] Autocomplete styling tweaks (#2732)

This PR tweaks the styling in the autocomplete. It removes icons and
matches styles to the sidebar. It improves the mobile design to hide the
search bar on mobile.

### Change Type

- [ ] `patch` — Bug fix
- [ ] `minor` — New feature
- [ ] `major` — Breaking change
- [ ] `dependencies` — Changes to package dependencies[^1]
- [x] `documentation` — Changes to the documentation only[^2]
- [ ] `tests` — Changes to any test code only[^2]
- [ ] `internal` — Any other changes that don't affect the published
package[^2]
- [ ] I don't know
This commit is contained in:
Steve Ruiz 2024-02-05 20:46:07 +00:00 committed by GitHub
parent e6e4e7f6cb
commit 538734782c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 81819 additions and 81820 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,8 +1,8 @@
import { SearchResult } from '@/types/search-types'
import { getDb } from '@/utils/ContentDatabase'
import { SEARCH_RESULTS, searchBucket, sectionTypeBucket } from '@/utils/search-api'
import assert from 'assert'
import { NextRequest } from 'next/server'
import { SEARCH_RESULTS, searchBucket, sectionTypeBucket } from '@/utils/search-api'
type Data = {
results: {
@ -42,36 +42,44 @@ export async function GET(req: NextRequest) {
const queryResults = await vdb.query(query, 25)
queryResults.sort((a, b) => b.score - a.score)
const headings = await Promise.all(
queryResults.map(async (result) => {
if (result.type !== 'heading') return // bleg
const headings = (
await Promise.all(
queryResults.map(async (result) => {
try {
if (result.type !== 'heading') return // bleg
const article = await db.db.get(
`SELECT id, title, description, categoryId, sectionId, keywords FROM articles WHERE id = ?`,
result.id
)
assert(article, `No article found for heading ${result.id}`)
const category = await db.db.get(
`SELECT id, title FROM categories WHERE id = ?`,
article.categoryId
)
const section = await db.db.get(
`SELECT id, title FROM sections WHERE id = ?`,
article.sectionId
)
const heading = await db.db.get(`SELECT * FROM headings WHERE slug = ?`, result.slug)
assert(heading, `No heading found for ${result.id} ${result.slug}`)
const article = await db.db.get(
`SELECT id, title, description, categoryId, sectionId, keywords FROM articles WHERE id = ?`,
result.id
)
assert(article, `No article found for heading ${result.id}`)
const category = await db.db.get(
`SELECT id, title FROM categories WHERE id = ?`,
article.categoryId
)
const section = await db.db.get(
`SELECT id, title FROM sections WHERE id = ?`,
article.sectionId
)
const heading = await db.db.get(`SELECT * FROM headings WHERE slug = ?`, result.slug)
assert(heading, `No heading found for ${result.id} ${result.slug}`)
return {
id: result.id,
article,
category,
section,
heading,
score: result.score,
}
})
)
return {
id: result.id,
article,
category,
section,
heading,
score: result.score,
}
} catch (e: any) {
console.error(e.message)
// something went wrong
return
}
})
)
).filter(Boolean)
const visited = new Set<string>()
for (const result of headings) {

View file

@ -1,7 +1,7 @@
import { SearchResult } from '@/types/search-types'
import { getDb } from '@/utils/ContentDatabase'
import { NextRequest } from 'next/server'
import { SEARCH_RESULTS, searchBucket, sectionTypeBucket } from '@/utils/search-api'
import { NextRequest } from 'next/server'
type Data = {
results: {

View file

@ -47,11 +47,11 @@
}
.autocomplete__cancel {
display: none;
display: none;
}
.autocomplete__wrapper:focus-within .autocomplete__cancel {
display: flex;
display: flex;
}
.autocomplete__cancel {
@ -73,16 +73,25 @@
}
.autocomplete__item__icon {
width: 24px;
height: 24px;
width: 24px;
height: 24px;
flex: 0 0 24px;
}
.autocomplete__group {
font-size: 14px;
font-weight: normal;
font-size: 12px;
padding-left: 8px;
font-weight: 500;
margin-bottom: 4px;
position: relative;
letter-spacing: 0.5px;
height: 40px;
display: flex;
align-items: center;
justify-content: flex-start;
color: var(--color-text-secondary);
text-transform: uppercase;
--bg: transparent;
white-space: nowrap;
}
@ -110,16 +119,12 @@
font-weight: bold;
}
@media (min-width: 640px) {
.autocomplete__item {
height: 2.25rem;
font-size: 15px;
}
}
.autocomplete__popover {
margin-top: 4px;
position: relative;
z-index: 50;
min-width: 240px;
font-size: 14px;
display: flex;
max-height: min(var(--popover-available-height, 300px), 300px);
flex-direction: column;
@ -135,39 +140,60 @@
outline: 2px solid transparent;
outline-offset: 2px;
box-shadow:
0 10px 15px -3px rgb(0 0 0 / 0.1),
0 10px 14px -3px rgb(0 0 0 / 0.1),
0 4px 6px -4px rgb(0 0 0 / 0.1);
}
:is([data-theme="dark"] .autocomplete__popover) {
:is([data-theme='dark'] .autocomplete__popover) {
border-color: hsl(204 3% 26%);
background-color: hsl(204 3% 18%);
color: hsl(204 20% 100%);
box-shadow:
0 10px 15px -3px rgb(0 0 0 / 0.25),
0 10px 14px -3px rgb(0 0 0 / 0.25),
0 4px 6px -4px rgb(0 0 0 / 0.1);
}
.autocomplete__empty {
padding: 0px 8px;
}
.autocomplete__item {
position: relative;
padding: 0px 8px;
display: flex;
cursor: pointer;
scroll-margin: 0.5rem;
align-items: center;
gap: 0.5rem;
border-radius: 0.25rem;
padding: 0.5rem;
outline: none !important;
white-space: nowrap;
overflow: hidden;
justify-content: flex-start;
color: var(--color-text);
--bg: transparent;
height: 32px;
flex-grow: 2;
font-size: 14px;
}
.autocomplete__item:hover {
background-color: hsl(204 100% 80% / 0.4);
.autocomplete__item::after {
position: absolute;
display: block;
content: '';
inset: 1px 0px;
background-color: var(--bg);
border-radius: var(--border-radius-menu);
transition: background-color 0.12s ease-in-out;
transition-delay: 0s;
}
.autocomplete__item[data-active-item] {
background-color: hsl(204 100% 40%);
color: hsl(204 20% 100%);
.autocomplete__item[data-active-item]::after {
background-color: var(--color-tint-2);
}
@media (hover: hover) {
.autocomplete__item:hover {
color: var(--color-text);
}
.autocomplete__item:hover::after {
--bg: var(--color-tint-1);
}
}
.autocomplete__item:active,
@ -176,18 +202,18 @@
padding-bottom: 7px;
}
:is([data-theme="dark"] .autocomplete__item) {
:is([data-theme='dark'] .autocomplete__item) {
color: hsl(204 20% 100%);
}
:is([data-theme="dark"] .autocomplete__item__icon path) {
:is([data-theme='dark'] .autocomplete__item__icon path) {
fill: hsl(204 20% 100%);
}
:is([data-theme="dark"] .autocomplete__item:hover) {
:is([data-theme='dark'] .autocomplete__item:hover) {
background-color: hsl(204 100% 40% / 0.25);
}
:is([data-theme="dark"] .autocomplete__item)[data-active-item] {
:is([data-theme='dark'] .autocomplete__item)[data-active-item] {
background-color: hsl(204 100% 40%);
}

View file

@ -11,7 +11,7 @@ import {
import { ComponentType, ForwardedRef, forwardRef, startTransition, useState } from 'react'
import './Autocomplete.css'
import { Icon } from './Icon'
import Spinner from './Spinner'
import { Spinner } from './Spinner'
export type DropdownOption = {
label: string
@ -59,10 +59,10 @@ const Autocomplete = forwardRef(function Autocomplete(
if (filteredOptions.length === 0) return null
return (
<ComboboxGroup>
<ComboboxGroup key={group}>
{groupsToLabel?.[group] && (
<ComboboxGroupLabel className="autocomplete__group">
{groupsToLabel?.[group]}
<ComboboxGroupLabel key={`${group}-group`} className="autocomplete__group">
{groupsToLabel[group]}
</ComboboxGroupLabel>
)}
{filteredOptions.map(({ label, value }) => {
@ -111,7 +111,7 @@ const Autocomplete = forwardRef(function Autocomplete(
{value && (
<ComboboxPopover className="autocomplete__popover">
{customUI}
{options.length === 0 && <span>No results found.</span>}
{options.length === 0 && <span className="autocomplete__empty">No results found.</span>}
{options.length !== 0 && renderedGroups}
</ComboboxPopover>
)}
@ -120,4 +120,4 @@ const Autocomplete = forwardRef(function Autocomplete(
)
})
export default Autocomplete
export { Autocomplete }

View file

@ -5,7 +5,7 @@ import { debounce } from '@/utils/debounce'
import { useRouter } from 'next/navigation'
import { useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import Autocomplete, { DropdownOption } from './Autocomplete'
import { Autocomplete, DropdownOption } from './Autocomplete'
const HOST_URL =
process.env.NODE_ENV === 'development'
@ -67,6 +67,7 @@ export function Search() {
const handleSearchTypeChange = () => {
setSearchType(searchType === SEARCH_TYPE.AI ? SEARCH_TYPE.NORMAL : SEARCH_TYPE.AI)
handleInputChange(query)
}
useEffect(() => {
@ -95,7 +96,6 @@ export function Search() {
}
groups={['examples', 'docs', 'reference']}
groupsToLabel={{ examples: 'Examples', docs: 'Articles', reference: 'Reference' }}
groupsToIcon={{ examples: CodeIcon, docs: DocIcon, reference: ReferenceIcon }}
options={searchResults}
isLoading={isLoading}
onInputChange={handleInputChange}
@ -111,52 +111,3 @@ export function Search() {
</div>
)
}
/*!
* Author: Dazzle UI
* License: https://www.svgrepo.com/page/licensing/#CC%20Attribution
*/
const CodeIcon = ({ className }: { className?: string }) => (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M9.95305 16.9123L8.59366 18.3608L2.03125 12.2016L8.19037 5.63922L9.64868 7.00791L4.85826 12.112L9.95254 16.8932L9.95305 16.9123Z"
fill="#000000"
/>
<path
d="M14.0478 16.9123L15.4072 18.3608L21.9696 12.2016L15.8105 5.63922L14.3522 7.00791L19.1426 12.112L14.0483 16.8932L14.0478 16.9123Z"
fill="#000000"
/>
</svg>
)
/*!
* Author: Solar Icons
* License: https://www.svgrepo.com/page/licensing/#CC%20Attribution
*/
const DocIcon = ({ className }: { className?: string }) => (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M3 10C3 6.22876 3 4.34315 4.17157 3.17157C5.34315 2 7.22876 2 11 2H13C16.7712 2 18.6569 2 19.8284 3.17157C21 4.34315 21 6.22876 21 10V14C21 17.7712 21 19.6569 19.8284 20.8284C18.6569 22 16.7712 22 13 22H11C7.22876 22 5.34315 22 4.17157 20.8284C3 19.6569 3 17.7712 3 14V10Z"
stroke="#000"
stroke-width="1.5"
/>
<path d="M8 12H16" stroke="#000" stroke-width="1.5" stroke-linecap="round" />
<path d="M8 8H16" stroke="#000" stroke-width="1.5" stroke-linecap="round" />
<path d="M8 16H13" stroke="#000" stroke-width="1.5" stroke-linecap="round" />
</svg>
)
/*!
* Author: Konstantin Filatov
* License: https://www.svgrepo.com/page/licensing/#CC%20Attribution
*/
const ReferenceIcon = ({ className }: { className?: string }) => (
<svg className={className} viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M19 23H7C4.27504 23 2 20.7055 2 18V6C2 3.23858 4.23858 1 7 1H19C20.6569 1 22 2.34315 22 4V20C22 21.6569 20.6569 23 19 23ZM7 3C5.34315 3 4 4.34315 4 6V14.9996C4.83566 14.3719 5.87439 14 7 14H19C19.3506 14 19.6872 14.0602 20 14.1707V4C20 3.44772 19.5523 3 19 3H18V9C18 9.3688 17.797 9.70765 17.4719 9.88167C17.1467 10.0557 16.7522 10.0366 16.4453 9.83205L14 8.20185L11.5547 9.83205C11.2478 10.0366 10.8533 10.0557 10.5281 9.88167C10.203 9.70765 10 9.3688 10 9V3H7ZM12 3H16V7.13148L14.5547 6.16795C14.2188 5.94402 13.7812 5.94402 13.4453 6.16795L12 7.13148V3ZM19 16C19.5523 16 20 16.4477 20 17V20C20 20.5523 19.5523 21 19 21H7C5.5135 21 4.04148 19.9162 4.04148 18.5C4.04148 17.0532 5.5135 16 7 16H19Z"
fill="#000"
/>
</svg>
)

View file

@ -136,20 +136,15 @@ function SidebarCategory({
)
if (articles.length === 0) return null
const value = `${linkCtx?.categoryId}-${group}-${linkCtx?.articleId}`
return (
<Accordion.Item
key={group}
value={`${linkCtx?.categoryId}-${group}-${linkCtx?.articleId}`}
>
<Accordion.Trigger
className="sidebar__section__group__title"
style={{ marginLeft: '8px', paddingRight: '8px' }}
>
<Accordion.Item key={value} value={value}>
<Accordion.Trigger className="sidebar__section__group__title">
{group}
<Chevron />
</Accordion.Trigger>
<Accordion.Content>
<ul className="sidebar__list sidebar__group" style={{ paddingLeft: '8px' }}>
<ul className="sidebar__list sidebar__group">
{articles.map((link) => (
<SidebarLink key={link.url} headings={headings} {...link} />
))}
@ -202,7 +197,7 @@ function SidebarArticle({
?.filter((heading) => heading.level < 4)
.map((heading) => (
<li
key={heading.slug}
key={`${heading.slug}`}
data-heading-level={heading.title === 'Constructor' ? 2 : heading.level}
>
<Link href={`#${heading.slug}`} title={heading.title} className="sidebar__link">

View file

@ -1,5 +1,5 @@
// TODO(mime): copied from tldraw package, needs to be in another shared location.
export default function Spinner(props: React.SVGProps<SVGSVGElement>) {
export function Spinner(props: React.SVGProps<SVGSVGElement>) {
return (
<svg width={16} height={16} viewBox="0 0 16 16" {...props}>
<g strokeWidth={2} fill="none" fillRule="evenodd">

View file

@ -774,9 +774,15 @@ body {
font-size: 14px;
}
.sidebar,
.sidebar button,
.sidebar a {
font-size: 14px;
font-weight: 600;
}
.sidebar a,
.sidebar .sidebar__link {
font-weight: bold;
text-decoration: none;
}
@ -813,7 +819,6 @@ body {
.sidebar__list li .sidebar__category li a,
.sidebar__list li .sidebar__article li a {
padding-left: 8px;
font-size: 13px;
}
.sidebar__list li .sidebar__category li[data-heading-level='3'] a,
@ -821,6 +826,10 @@ body {
padding-left: 16px;
}
.sidebar__group {
padding-left: 8px;
}
.sidebar__list li .sidebar__category .sidebar__group li[data-heading-level='2'] a,
.sidebar__list li .sidebar__article .sidebar__group li[data-heading-level='2'] a {
padding-left: 16px;
@ -892,7 +901,6 @@ body {
}
.sidebar__section__group__title {
font-size: 14px;
font-weight: normal;
margin-bottom: 4px;
position: relative;
@ -906,6 +914,8 @@ body {
border: 0;
width: 100%;
cursor: pointer;
padding-right: 8px;
margin-left: 8px;
}
.sidebar__sections__list > *:nth-last-of-type(n + 2) > .sidebar__list {
@ -955,10 +965,6 @@ body {
box-shadow: var(--shadow-small);
}
.sidebar__category__title {
font-size: 14px;
}
/* --------------------- Search --------------------- */
.sidebar .search__wrapper {
@ -1007,6 +1013,7 @@ body {
cursor: pointer;
margin: 4px 0;
background: transparent;
padding: 0px 8px 8px 8px;
}
/* --------------------- Desktop M --------------------- */
@ -1077,6 +1084,15 @@ body {
pointer-events: all;
}
.search__wrapper {
display: none;
}
.layout__header {
grid-template-columns: auto auto;
justify-content: space-between;
}
.sidebar {
display: none;
width: 100%;

View file

@ -125,7 +125,10 @@ const MATCH_HEADINGS = /(?:^|\n)(#{1,6})\s+(.+?)(?=\n|$)/g
function getHeadingLinks(content: string) {
let match
const headings: ArticleHeadings = []
const visited = new Set<string>()
while ((match = MATCH_HEADINGS.exec(content)) !== null) {
if (visited.has(match[2])) continue
visited.add(match[2])
slugs.reset()
headings.push({
level: match[1].length,

File diff suppressed because one or more lines are too long