diff --git a/index.css b/index.css index 5665affb..fce334da 100644 --- a/index.css +++ b/index.css @@ -4,8 +4,7 @@ @layer base { svg { - vertical-align: middle; - fill: currentColor; + @apply align-middle fill-current; } } diff --git a/package.json b/package.json index 45e40875..60e5fa31 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "webdevhome.github.io", - "version": "2.1.0", + "version": "2.2.0", "scripts": { "dev": "vite", - "build": "tsc && vite build", + "build": "npm test && vite build", "preview": "vite preview", - "typecheck": "tsc" + "test": "tsc && eslint src" }, "dependencies": { "@mdi/react": "^1.4.0", diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index efa6ef38..82f9a9f1 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -1,4 +1,5 @@ import { + mdiArrowCollapseUp, mdiArrowLeft, mdiCheck, mdiFormatListChecks, @@ -17,6 +18,7 @@ import { FooterDivider } from '../Footer/FooterDivider' import { FooterGroup } from '../Footer/FooterGroup' import { AppAction } from '../Header/AppAction' import { AppHeader } from '../Header/AppHeader' +import { JumpLinks } from '../JumpLinks/JumpLinks' import { LinkGroup } from '../Links/LinkGroup' import { Search } from '../Search/Search' import { AppContent } from './AppContent' @@ -34,6 +36,13 @@ export const WebdevHome: FC = () => { const allLinks = useAllLinks() const hiddenLinksCount = useHiddenLinksCount() + function handleScrollTopClick() { + const htmlEl = document.children.item(0) + if (htmlEl === null) return + + htmlEl.scrollTo({ top: 0, behavior: 'smooth' }) + } + return (
{ <> {isCurrentAppMode(AppMode.default) ? ( <> + { {
{isCurrentAppMode(AppMode.default, AppMode.customize) ? ( - - {links.items.map((group) => ( - - ))} - + <> + + + {links.items.map((group, index) => ( + + ))} + + ) : ( )} diff --git a/src/components/App/AppContent.tsx b/src/components/App/AppContent.tsx index c4b981c2..b12ff13a 100644 --- a/src/components/App/AppContent.tsx +++ b/src/components/App/AppContent.tsx @@ -6,7 +6,7 @@ export const AppContent: FC = ({ children }) => {
diff --git a/src/components/Header/AppAction.tsx b/src/components/Header/AppAction.tsx index 6ec49a50..fbb5a617 100644 --- a/src/components/Header/AppAction.tsx +++ b/src/components/Header/AppAction.tsx @@ -20,7 +20,7 @@ export const AppAction: FC = ({ return (
= ({ > -
{label}
+
{label}
) } diff --git a/src/components/Header/AppHeader.tsx b/src/components/Header/AppHeader.tsx index 71f047f2..58d11488 100644 --- a/src/components/Header/AppHeader.tsx +++ b/src/components/Header/AppHeader.tsx @@ -11,7 +11,7 @@ export const AppHeader: FC = ({ actions }) => {
@@ -20,7 +20,7 @@ export const AppHeader: FC = ({ actions }) => { {actions !== null ? (
diff --git a/src/components/Header/Logo.tsx b/src/components/Header/Logo.tsx index 44b9eaff..c0c7e95b 100644 --- a/src/components/Header/Logo.tsx +++ b/src/components/Header/Logo.tsx @@ -7,8 +7,9 @@ export const Logo: FC = () => { className={classNames( 'font-mono text-2xl', 'tracking-wide', - 'text-center md:text-left text-gray-400 dark:text-gray-200', - 'pt-2 md:pt-0', + 'text-center lg:text-left text-gray-400 dark:text-gray-200', + 'pt-2 lg:pt-0', + 'text-nowrap', )} > < diff --git a/src/components/JumpLinks/JumpLink.tsx b/src/components/JumpLinks/JumpLink.tsx new file mode 100644 index 00000000..12a7d3d5 --- /dev/null +++ b/src/components/JumpLinks/JumpLink.tsx @@ -0,0 +1,34 @@ +import classNames from 'classnames' +import { FC } from 'react' +import { slugify } from '../../utils/slugify' + +interface Props { + label: string + color?: string +} + +export const JumpLink: FC = ({ label, color = 'gray' }) => { + function handleClick() { + const target = document.getElementById(slugify(label)) + if (target === null) return + + target.scrollIntoView({ behavior: 'smooth' }) + } + + return ( +
+ {label} +
+ ) +} diff --git a/src/components/JumpLinks/JumpLinks.tsx b/src/components/JumpLinks/JumpLinks.tsx new file mode 100644 index 00000000..ea2a375b --- /dev/null +++ b/src/components/JumpLinks/JumpLinks.tsx @@ -0,0 +1,45 @@ +import { mdiChevronDown, mdiChevronUp } from '@mdi/js' +import classNames from 'classnames' +import { FC, useState } from 'react' +import { MdiIcon } from '../Icon/MdiIcon' +import { JumpLink } from './JumpLink' +import { links } from '../../links' +import { useAllLinksInGroupAreHidden } from '../../stores/hiddenLinks/hiddenLinksHooks' + +export const JumpLinks: FC = () => { + const allLinksInGroupAreHidden = useAllLinksInGroupAreHidden() + + const [isOpen, setIsOpen] = useState(false) + + function handleToggleClick() { + setIsOpen(!isOpen) + } + + return ( +
+
+ +
Jump to
+
+
+ {links.items + .filter((group) => !allLinksInGroupAreHidden(group)) + .map((linkGroup, index) => ( + + ))} +
+
+ ) +} diff --git a/src/components/Links/LinkGroup.tsx b/src/components/Links/LinkGroup.tsx index c8a423b3..2430399b 100644 --- a/src/components/Links/LinkGroup.tsx +++ b/src/components/Links/LinkGroup.tsx @@ -13,6 +13,7 @@ import { useAllLinksInGroupAreHidden, useGetIsLinkHidden, } from '../../stores/hiddenLinks/hiddenLinksHooks' +import { slugify } from '../../utils/slugify' import { MdiIcon } from '../Icon/MdiIcon' import { Link } from './Link' import { LinkGroupButton } from './LinkGroupButton' @@ -34,20 +35,20 @@ export const LinkGroup: FC = ({ group }) => { }, [group.items, getIsLinkHidden]) const allGroupLinksAreHidden = useMemo( - () => allLinksInGroupAreHidden(group.items.map((link) => link.url)), - [allLinksInGroupAreHidden, group.items], + () => allLinksInGroupAreHidden(group), + [allLinksInGroupAreHidden, group], ) const handleToggleGroupClick = useCallback( (...items: LinkItem[]): void => { - dispatch(toggleHiddenLinksGroup(items.map((link) => link.url))) + dispatch(toggleHiddenLinksGroup(items)) }, [dispatch], ) const noVisibleLinksInGroup = useMemo(() => { - return allLinksInGroupAreHidden(group.items.map((link) => link.url)) - }, [allLinksInGroupAreHidden, group.items]) + return allLinksInGroupAreHidden(group) + }, [allLinksInGroupAreHidden, group]) const showHiddenLinksButtonLabel = useMemo(() => { const hiddenLinksCount = hiddenLinks.length @@ -67,14 +68,17 @@ export const LinkGroup: FC = ({ group }) => { } return ( -
+
+ links: Array, ): SetHiddenLinksAction { return { type: SET_HIDDEN_LINKS, payload: links } } export function toggleHiddenLink( - link: LinkItem['url'] + link: LinkItem['url'], ): ToggleHiddenLinkAction { return { type: TOGGLE_HIDDEN_LINK, payload: link } } export function toggleHiddenLinksGroup( - links: Array + items: LinkItem[], ): ToggleHiddenLinksGroup { - return { type: TOGGLE_HIDDEN_LINKS_GROUP, payload: links } + return { type: TOGGLE_HIDDEN_LINKS_GROUP, payload: items.map((i) => i.url) } } diff --git a/src/stores/hiddenLinks/hiddenLinksHooks.ts b/src/stores/hiddenLinks/hiddenLinksHooks.ts index 3b59f5e5..55e19d41 100644 --- a/src/stores/hiddenLinks/hiddenLinksHooks.ts +++ b/src/stores/hiddenLinks/hiddenLinksHooks.ts @@ -1,14 +1,12 @@ import { useMemo } from 'react' import { useAppSelector } from '..' -import { LinkItem } from '../../links' +import { LinkGroup, LinkItem } from '../../links' -export function useAllLinksInGroupAreHidden(): ( - links: Array, -) => boolean { +export function useAllLinksInGroupAreHidden(): (group: LinkGroup) => boolean { const hiddenLinks = useAppSelector((state) => state.hiddenLinks.links) - return function allLinksInGroupAreHidden(links) { - return links.every((link) => hiddenLinks.includes(link)) + return function allLinksInGroupAreHidden(group) { + return group.items.every((link) => hiddenLinks.includes(link.url)) } } diff --git a/src/utils/slugify.ts b/src/utils/slugify.ts new file mode 100644 index 00000000..5b4f46e3 --- /dev/null +++ b/src/utils/slugify.ts @@ -0,0 +1,3 @@ +export function slugify(input: string): string { + return input.toLowerCase().replaceAll(' ', '-') +} \ No newline at end of file