Skip to content

Commit a3a09f6

Browse files
authored
Merge pull request #71 from webdevhome/jump-links
Jump links
2 parents 518b809 + 7fa96cc commit a3a09f6

File tree

15 files changed

+159
-58
lines changed

15 files changed

+159
-58
lines changed

index.css

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44

55
@layer base {
66
svg {
7-
vertical-align: middle;
8-
fill: currentColor;
7+
@apply align-middle fill-current;
98
}
109
}
1110

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "webdevhome.github.io",
3-
"version": "2.1.0",
3+
"version": "2.2.0",
44
"scripts": {
55
"dev": "vite",
6-
"build": "tsc && vite build",
6+
"build": "npm test && vite build",
77
"preview": "vite preview",
8-
"typecheck": "tsc"
8+
"test": "tsc && eslint src"
99
},
1010
"dependencies": {
1111
"@mdi/react": "^1.4.0",

src/components/App/App.tsx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {
2+
mdiArrowCollapseUp,
23
mdiArrowLeft,
34
mdiCheck,
45
mdiFormatListChecks,
@@ -17,6 +18,7 @@ import { FooterDivider } from '../Footer/FooterDivider'
1718
import { FooterGroup } from '../Footer/FooterGroup'
1819
import { AppAction } from '../Header/AppAction'
1920
import { AppHeader } from '../Header/AppHeader'
21+
import { JumpLinks } from '../JumpLinks/JumpLinks'
2022
import { LinkGroup } from '../Links/LinkGroup'
2123
import { Search } from '../Search/Search'
2224
import { AppContent } from './AppContent'
@@ -34,6 +36,13 @@ export const WebdevHome: FC = () => {
3436
const allLinks = useAllLinks()
3537
const hiddenLinksCount = useHiddenLinksCount()
3638

39+
function handleScrollTopClick() {
40+
const htmlEl = document.children.item(0)
41+
if (htmlEl === null) return
42+
43+
htmlEl.scrollTo({ top: 0, behavior: 'smooth' })
44+
}
45+
3746
return (
3847
<div className="min-h-full">
3948
<div
@@ -50,6 +59,11 @@ export const WebdevHome: FC = () => {
5059
<>
5160
{isCurrentAppMode(AppMode.default) ? (
5261
<>
62+
<AppAction
63+
icon={mdiArrowCollapseUp}
64+
label="Top"
65+
action={handleScrollTopClick}
66+
/>
5367
<AppAction
5468
icon={mdiMagnify}
5569
label="Search"
@@ -64,7 +78,7 @@ export const WebdevHome: FC = () => {
6478
<AppAction
6579
icon={mdiStickerTextOutline}
6680
active={toggleDescriptions.showDescriptions}
67-
label="Descriptions"
81+
label="Link info"
6882
action={toggleDescriptions.toggle}
6983
/>
7084
<AppAction
@@ -123,11 +137,14 @@ export const WebdevHome: FC = () => {
123137

124138
<div className="h-full">
125139
{isCurrentAppMode(AppMode.default, AppMode.customize) ? (
126-
<AppContent>
127-
{links.items.map((group) => (
128-
<LinkGroup group={group} key={group.name} />
129-
))}
130-
</AppContent>
140+
<>
141+
<JumpLinks />
142+
<AppContent>
143+
{links.items.map((group, index) => (
144+
<LinkGroup group={group} key={group.name} />
145+
))}
146+
</AppContent>
147+
</>
131148
) : (
132149
<Search />
133150
)}

src/components/App/AppContent.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ export const AppContent: FC<PropsWithChildren> = ({ children }) => {
66
<div
77
className={classNames(
88
'grid grid-cols-[repeat(auto-fill,minmax(290px,1fr))]',
9-
'gap-x-2 sm:gap-x-4 lg:gap-x-8 gap-y-20',
9+
'gap-x-2 sm:gap-x-4 lg:gap-x-8 gap-y-8',
1010
'px-page py-4 lg:py-8',
1111
)}
1212
>

src/components/Header/AppAction.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const AppAction: FC<Props> = ({
2020
return (
2121
<div
2222
className={classNames(
23-
'flex items-center',
23+
'flex flex-col items-center md:flex-row',
2424
'p-2',
2525
'rounded-md',
2626
'select-none',
@@ -44,7 +44,7 @@ export const AppAction: FC<Props> = ({
4444
>
4545
<MdiIcon path={icon} />
4646

47-
<div className="ml-2 text-sm font-semibold tracking-wide">{label}</div>
47+
<div className="md:ml-2 text-xs md:text-sm font-semibold">{label}</div>
4848
</div>
4949
)
5050
}

src/components/Header/AppHeader.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const AppHeader: FC<Props> = ({ actions }) => {
1111
<div
1212
className={classNames(
1313
'grid items-center',
14-
'grid-rows-[auto,auto] md:grid-rows-[auto] md:grid-cols-[1fr,auto]',
14+
'grid-rows-[auto,auto] lg:grid-rows-[auto] lg:grid-cols-[1fr,auto]',
1515
'px-page',
1616
)}
1717
>
@@ -20,7 +20,7 @@ export const AppHeader: FC<Props> = ({ actions }) => {
2020
{actions !== null ? (
2121
<div
2222
className={classNames(
23-
'flex gap-x-2 justify-center flex-wrap',
23+
'flex md:gap-x-2 justify-center flex-wrap',
2424
'py-2',
2525
)}
2626
>

src/components/Header/Logo.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ export const Logo: FC = () => {
77
className={classNames(
88
'font-mono text-2xl',
99
'tracking-wide',
10-
'text-center md:text-left text-gray-400 dark:text-gray-200',
11-
'pt-2 md:pt-0',
10+
'text-center lg:text-left text-gray-400 dark:text-gray-200',
11+
'pt-2 lg:pt-0',
12+
'text-nowrap',
1213
)}
1314
>
1415
<span>&lt;</span>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import classNames from 'classnames'
2+
import { FC } from 'react'
3+
import { slugify } from '../../utils/slugify'
4+
5+
interface Props {
6+
label: string
7+
color?: string
8+
}
9+
10+
export const JumpLink: FC<Props> = ({ label, color = 'gray' }) => {
11+
function handleClick() {
12+
const target = document.getElementById(slugify(label))
13+
if (target === null) return
14+
15+
target.scrollIntoView({ behavior: 'smooth' })
16+
}
17+
18+
return (
19+
<div
20+
className={classNames(
21+
'jump-link',
22+
'px-3 py-1 md:px-2.5 md:py-0.5',
23+
'rounded-md',
24+
'cursor-pointer',
25+
'text-sm font-semibold',
26+
`bg-${color}-100 dark:bg-${color}-600`,
27+
`text-${color}-800 dark:text-${color}-50`,
28+
)}
29+
onClick={handleClick}
30+
>
31+
{label}
32+
</div>
33+
)
34+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { mdiChevronDown, mdiChevronUp } from '@mdi/js'
2+
import classNames from 'classnames'
3+
import { FC, useState } from 'react'
4+
import { MdiIcon } from '../Icon/MdiIcon'
5+
import { JumpLink } from './JumpLink'
6+
import { links } from '../../links'
7+
import { useAllLinksInGroupAreHidden } from '../../stores/hiddenLinks/hiddenLinksHooks'
8+
9+
export const JumpLinks: FC = () => {
10+
const allLinksInGroupAreHidden = useAllLinksInGroupAreHidden()
11+
12+
const [isOpen, setIsOpen] = useState(false)
13+
14+
function handleToggleClick() {
15+
setIsOpen(!isOpen)
16+
}
17+
18+
return (
19+
<div className="jump-links px-page pt-4 lg:pt-8">
20+
<div
21+
className="flex items-center gap-x-2 md:hidden dark:text-white cursor-default select-none"
22+
onClick={handleToggleClick}
23+
>
24+
<MdiIcon path={isOpen ? mdiChevronUp : mdiChevronDown}></MdiIcon>
25+
<div>Jump to</div>
26+
</div>
27+
<div
28+
className={classNames('flex flex-wrap gap-1 md:flex pt-2', {
29+
hidden: !isOpen,
30+
block: isOpen,
31+
})}
32+
>
33+
{links.items
34+
.filter((group) => !allLinksInGroupAreHidden(group))
35+
.map((linkGroup, index) => (
36+
<JumpLink
37+
key={index}
38+
label={linkGroup.name}
39+
color={linkGroup.color}
40+
/>
41+
))}
42+
</div>
43+
</div>
44+
)
45+
}

src/components/Links/LinkGroup.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
useAllLinksInGroupAreHidden,
1414
useGetIsLinkHidden,
1515
} from '../../stores/hiddenLinks/hiddenLinksHooks'
16+
import { slugify } from '../../utils/slugify'
1617
import { MdiIcon } from '../Icon/MdiIcon'
1718
import { Link } from './Link'
1819
import { LinkGroupButton } from './LinkGroupButton'
@@ -34,20 +35,20 @@ export const LinkGroup: FC<Props> = ({ group }) => {
3435
}, [group.items, getIsLinkHidden])
3536

3637
const allGroupLinksAreHidden = useMemo(
37-
() => allLinksInGroupAreHidden(group.items.map((link) => link.url)),
38-
[allLinksInGroupAreHidden, group.items],
38+
() => allLinksInGroupAreHidden(group),
39+
[allLinksInGroupAreHidden, group],
3940
)
4041

4142
const handleToggleGroupClick = useCallback(
4243
(...items: LinkItem[]): void => {
43-
dispatch(toggleHiddenLinksGroup(items.map((link) => link.url)))
44+
dispatch(toggleHiddenLinksGroup(items))
4445
},
4546
[dispatch],
4647
)
4748

4849
const noVisibleLinksInGroup = useMemo(() => {
49-
return allLinksInGroupAreHidden(group.items.map((link) => link.url))
50-
}, [allLinksInGroupAreHidden, group.items])
50+
return allLinksInGroupAreHidden(group)
51+
}, [allLinksInGroupAreHidden, group])
5152

5253
const showHiddenLinksButtonLabel = useMemo(() => {
5354
const hiddenLinksCount = hiddenLinks.length
@@ -67,14 +68,17 @@ export const LinkGroup: FC<Props> = ({ group }) => {
6768
}
6869

6970
return (
70-
<div>
71+
<div
72+
id={slugify(group.name)}
73+
className="scroll-mt-32 md:scroll-mt-28 lg:scroll-mt-20"
74+
>
7175
<div className="flex gap-x-1 mb-2">
7276
<div
7377
className={classNames(
7478
'flex-auto',
7579
'px-4 py-2',
7680
`bg-${group.color ?? 'gray'}-100 dark:bg-${group.color ?? 'gray'}-600`,
77-
'font-semibold uppercase tracking-wider',
81+
'text-center font-semibold uppercase tracking-wider',
7882
`text-${group.color ?? 'gray'}-800 dark:text-${group.color ?? 'gray'}-50`,
7983
'rounded-md',
8084
)}

0 commit comments

Comments
 (0)