diff --git a/package-lock.json b/package-lock.json index a680bd8..53402f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "nanoid": "5.0.7", "polished": "4.3.1", "tailwind-merge": "2.5.2", + "throttle-debounce": "5.0.2", "type-fest": "4.25.0" }, "devDependencies": { @@ -37,6 +38,7 @@ "@percy/cli": "1.29.1", "@tailwindcss/typography": "0.5.14", "@total-typescript/ts-reset": "0.5.1", + "@types/throttle-debounce": "5.0.2", "chalk": "5.3.0", "commander": "12.1.0", "concurrently": "8.2.2", @@ -2974,6 +2976,13 @@ "node": ">=8" } }, + "node_modules/@types/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@types/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/unist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.2.tgz", @@ -11843,6 +11852,15 @@ "node": ">=0.8" } }, + "node_modules/throttle-debounce": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", + "integrity": "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==", + "license": "MIT", + "engines": { + "node": ">=12.22" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", diff --git a/package.json b/package.json index a3e3da9..5339013 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "nanoid": "5.0.7", "polished": "4.3.1", "tailwind-merge": "2.5.2", + "throttle-debounce": "5.0.2", "type-fest": "4.25.0" }, "devDependencies": { @@ -61,6 +62,7 @@ "@percy/cli": "1.29.1", "@tailwindcss/typography": "0.5.14", "@total-typescript/ts-reset": "0.5.1", + "@types/throttle-debounce": "5.0.2", "chalk": "5.3.0", "commander": "12.1.0", "concurrently": "8.2.2", diff --git a/src/components/base-layout.astro b/src/components/base-layout.astro index 905cfdb..0280167 100644 --- a/src/components/base-layout.astro +++ b/src/components/base-layout.astro @@ -14,7 +14,7 @@ const lang = Astro.props.lang ?? getGlobalContext>(Astro) --- - + diff --git a/src/pages/index.astro b/src/pages/index.astro index f22b66a..c69637a 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -19,6 +19,7 @@ import { Metadata } from '@/web/components/metadata'; import Project from '@/web/components/project.astro'; import Reference from '@/web/components/reference.astro'; import { Section, SectionTitle, Subsection, SubsectionTitle } from '@/web/components/section'; +import { Sidebar, SidebarItem } from '@/web/components/sidebar'; import { Skill } from '@/web/components/skills'; import { initializeWebContext } from '@/web/utils/initialize-web-context'; @@ -35,7 +36,7 @@ await initializeWebContext(Astro, { }); --- - + @@ -43,8 +44,18 @@ await initializeWebContext(Astro, { - -
+ + About me + Skills + Work experience + Projects + References + Achievements + Education + Favorites + + +
Skills @@ -84,7 +95,7 @@ await initializeWebContext(Astro, {
-
+
Work @@ -94,7 +105,7 @@ await initializeWebContext(Astro, {
-
+
Projects @@ -102,7 +113,7 @@ await initializeWebContext(Astro, {
-
+
References @@ -110,7 +121,7 @@ await initializeWebContext(Astro, {
-
+
Achievements @@ -122,7 +133,7 @@ await initializeWebContext(Astro, {
-
+
Education @@ -130,7 +141,7 @@ await initializeWebContext(Astro, {
-
+
Favorites diff --git a/src/pages/template.astro b/src/pages/template.astro index 313173e..76d3c06 100644 --- a/src/pages/template.astro +++ b/src/pages/template.astro @@ -20,6 +20,16 @@ const props: ComponentProps = { }, }, metadata: 'main', + sidebar: [ + { sectionId: 'basics', icon: 'fa6-solid:user', text: 'About me' }, + { sectionId: 'skills', icon: 'fa6-solid:bars-progress', text: 'Skills' }, + { sectionId: 'jobs', icon: 'fa6-solid:suitcase', text: 'Work experience' }, + { sectionId: 'projects', icon: 'fa6-solid:rocket', text: 'Projects' }, + { sectionId: 'references', icon: 'fa6-solid:comment', text: 'References' }, + { sectionId: 'achievements', icon: 'fa6-solid:trophy', text: 'Achievements' }, + { sectionId: 'education', icon: 'fa6-solid:graduation-cap', text: 'Education' }, + { sectionId: 'favorites', icon: 'fa6-solid:star', text: 'Favorites' }, + ], basics: 'main', sections: [ { diff --git a/src/styles/colors/elements/default.astro b/src/styles/colors/elements/default.astro index 032a4d0..d0fa9b6 100644 --- a/src/styles/colors/elements/default.astro +++ b/src/styles/colors/elements/default.astro @@ -8,6 +8,7 @@ --text-title: var(--dusk-900); --text-primary: var(--dusk-700); --text-secondary: var(--dusk-500); + --text-contrast: var(--white); --icon-light: var(--dusk-400); --icon-main: var(--dusk-500); @@ -19,6 +20,7 @@ --bg-body: var(--dusk-50); --bg-card: var(--white); --bg-popover: var(--white); + --bg-tooltip: var(--dusk-950); --bg-light: var(--dusk-100); --bg-main: var(--dusk-200); @@ -34,6 +36,7 @@ --text-title: var(--white); --text-primary: var(--dusk-50); --text-secondary: var(--dusk-100); + --text-contrast: var(--dusk-950); --icon-light: var(--dusk-500); --icon-main: var(--dusk-400); @@ -45,6 +48,7 @@ --bg-body: var(--dusk-950); --bg-card: var(--dusk-900); --bg-popover: var(--dusk-950); + --bg-tooltip: var(--white); --bg-light: var(--dusk-800); --bg-main: var(--dusk-700); diff --git a/src/types/templates.ts b/src/types/templates.ts index 29ce6e2..f2e4981 100644 --- a/src/types/templates.ts +++ b/src/types/templates.ts @@ -20,6 +20,9 @@ type SectionBase = { /** Title displayed above the section content. */ title: string; + + /** Unique identifier for the section. Used to display the sidebar. */ + id?: string; }; type SectionEntries = Omit, 'entry'> & { diff --git a/src/web/components/basics/basics.astro b/src/web/components/basics/basics.astro index 31272f5..1460123 100644 --- a/src/web/components/basics/basics.astro +++ b/src/web/components/basics/basics.astro @@ -1,4 +1,6 @@ --- +import type { HTMLAttributes } from 'astro/types'; + import type { AsyncEntry } from '@/types/entries'; import RenderedContent from '@/web/components/rendered-content.astro'; @@ -7,16 +9,16 @@ import MyImage from './my-image.astro'; import PdfButton from './pdf-button.astro'; import Socials from './socials.astro'; -interface Props { +interface Props extends HTMLAttributes<'section'> { /** An entry from the `basics` collection. */ entry: AsyncEntry<'basics'>; } -const { entry } = Astro.props; +const { entry, ...props } = Astro.props; const { data: basics, body, render } = await entry; --- -
+
diff --git a/src/web/components/layout.astro b/src/web/components/layout.astro index 4bb1a87..bcea1d8 100644 --- a/src/web/components/layout.astro +++ b/src/web/components/layout.astro @@ -20,7 +20,10 @@ const { class: className, bodyClass, ...props } = Astro.props;
diff --git a/src/web/components/sidebar/index.ts b/src/web/components/sidebar/index.ts new file mode 100644 index 0000000..68ebe2e --- /dev/null +++ b/src/web/components/sidebar/index.ts @@ -0,0 +1,2 @@ +export { default as Sidebar } from './sidebar.astro'; +export { default as SidebarItem } from './sidebar-item.astro'; diff --git a/src/web/components/sidebar/sidebar-item.astro b/src/web/components/sidebar/sidebar-item.astro new file mode 100644 index 0000000..691613e --- /dev/null +++ b/src/web/components/sidebar/sidebar-item.astro @@ -0,0 +1,93 @@ +--- +import type { HTMLAttributes } from 'astro/types'; + +import { Icon } from '@/components/icon'; +import Tooltip from '@/web/components/tooltip.astro'; + +export interface Props extends HTMLAttributes<'main'> { + icon: string; + sectionId: string; +} + +const { icon, sectionId, title } = Astro.props; +--- + +
  • + + + + + + +
  • + + + + diff --git a/src/web/components/sidebar/sidebar.astro b/src/web/components/sidebar/sidebar.astro new file mode 100644 index 0000000..2af544c --- /dev/null +++ b/src/web/components/sidebar/sidebar.astro @@ -0,0 +1,17 @@ +--- +import type { HTMLAttributes } from 'astro/types'; + +import { cn } from '@/utils/cn'; + +export interface Props extends HTMLAttributes<'nav'> { + className?: string; +} + +const { class: className, ...props } = Astro.props; +--- + + diff --git a/src/web/components/tooltip.astro b/src/web/components/tooltip.astro new file mode 100644 index 0000000..ce8c445 --- /dev/null +++ b/src/web/components/tooltip.astro @@ -0,0 +1,90 @@ +--- +import { nanoid } from 'nanoid'; + +import { addElementAttributes } from '@/utils/html-string'; + +const trigger = await Astro.slots.render('trigger'); + +if (!trigger) { + throw new Error('Tooltip must have a trigger slot'); +} + +const id = nanoid(); +const triggerProps = { 'data-target': `tooltip-${id}` }; +--- + + + + + + diff --git a/src/web/templates/main-template.astro b/src/web/templates/main-template.astro index 0aadacc..23a75d0 100644 --- a/src/web/templates/main-template.astro +++ b/src/web/templates/main-template.astro @@ -19,6 +19,7 @@ import { Metadata } from '@/web/components/metadata'; import Project from '@/web/components/project.astro'; import Reference from '@/web/components/reference.astro'; import { Section, SectionTitle, Subsection, SubsectionTitle } from '@/web/components/section'; +import { Sidebar, SidebarItem } from '@/web/components/sidebar'; import { Skill } from '@/web/components/skills'; import type { WebContextData } from '@/web/utils/initialize-web-context'; import { initializeWebContext } from '@/web/utils/initialize-web-context'; @@ -36,6 +37,18 @@ interface Props { /** Name of file from `src/content/metadata``to use as page metadata. */ metadata: keyof DataEntryMap['metadata']; + /** Array of sidebar items to be displayed. */ + sidebar?: { + /** Id of the section to link to. By default section ids equals their collection keys. */ + sectionId: string; + + /** [Iconify](https://icon-sets.iconify.design/) icon to display in the sidebar. */ + icon: string; + + /** Text for the tooltip displayed on sidebar item hover. */ + text: string; + }[]; + /** Name of file from `src/content/basics``to use for the top resume section. */ basics: ValidContentEntrySlug<'basics'>; @@ -50,15 +63,26 @@ await initializeWebContext(Astro, { }); --- - + - + { + Astro.props.sidebar && ( + + {Astro.props.sidebar.map(({ icon, sectionId, text }) => ( + + {text} + + ))} + + ) + } + { Astro.props.sections.map((section) => ( -
    +
    {section.title} {section.collection === 'achievements' && ( diff --git a/tailwind.config.ts b/tailwind.config.ts index 073776c..596d26f 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -58,7 +58,7 @@ function getCustomColors() { // Colors used in the code for styling. Related CSS variables are located in `src/styles/colors/elements`. // prettier-ignore - const elementColors = ['button-bg', 'button-bg-contrast', 'web-heading-underline', 'pdf-heading-underline', 'primary-dark', 'primary', 'primary-light', 'primary-contrast', 'text-title', 'text-primary', 'text-secondary', 'icon-light', 'icon-main', 'icon-dark', 'border', 'separator', 'bg-body', 'bg-card', 'bg-popover', 'bg-light', 'bg-main', 'bg-dark']; + const elementColors = ['button-bg', 'button-bg-contrast', 'web-heading-underline', 'pdf-heading-underline', 'primary-dark', 'primary', 'primary-light', 'primary-contrast', 'text-title', 'text-primary', 'text-secondary', 'text-contrast', 'icon-light', 'icon-main', 'icon-dark', 'border', 'separator', 'bg-body', 'bg-card', 'bg-popover', 'bg-tooltip', 'bg-light', 'bg-main', 'bg-dark']; const result: Record> = {};