Move from Svelte to Vanilla JS (#155)

This commit is contained in:
Konrad Szwarc 2023-01-17 18:10:43 +01:00 committed by GitHub
parent f478ee4972
commit 88ba9d450f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 236 additions and 196 deletions

View file

@ -7,5 +7,4 @@ endOfLine: 'auto'
pluginSearchDirs: false pluginSearchDirs: false
plugins: plugins:
- 'prettier-plugin-astro' - 'prettier-plugin-astro'
- 'prettier-plugin-svelte'
- 'prettier-plugin-tailwindcss' - 'prettier-plugin-tailwindcss'

View file

@ -4,7 +4,6 @@
"mgmcdermott.vscode-language-babel", "mgmcdermott.vscode-language-babel",
"EditorConfig.EditorConfig", "EditorConfig.EditorConfig",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"svelte.svelte-vscode",
"bradlc.vscode-tailwindcss", "bradlc.vscode-tailwindcss",
"redhat.vscode-yaml" "redhat.vscode-yaml"
] ]

View file

@ -5,14 +5,9 @@
"emmet.includeLanguages": { "javascript": "javascriptreact", "astro": "javascriptreact" }, "emmet.includeLanguages": { "javascript": "javascriptreact", "astro": "javascriptreact" },
"files.eol": "\n", "files.eol": "\n",
"prettier.documentSelectors": ["**/*.astro"], "prettier.documentSelectors": ["**/*.astro"],
"svelte.enable-ts-plugin": true,
"tailwindCSS.classAttributes": ["class", "className", "class:list"], "tailwindCSS.classAttributes": ["class", "className", "class:list"],
"tailwindCSS.experimental.classRegex": [["/\\* tw \\*/ ([^;]*);", "'([^']*)'"]], "tailwindCSS.experimental.classRegex": [["/\\* tw \\*/ ([^;]*);", "'([^']*)'"]],
"tailwindCSS.includeLanguages": { "tailwindCSS.includeLanguages": { "javascript": "javascriptreact", "astro": "javascriptreact" },
"javascript": "javascriptreact",
"astro": "javascriptreact",
"svelte": "javascriptreact"
},
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"typescript.enablePromptUseWorkspaceTsdk": true "typescript.enablePromptUseWorkspaceTsdk": true
} }

View file

@ -1,12 +1,11 @@
import image from '@astrojs/image'; import image from '@astrojs/image';
import svelte from '@astrojs/svelte';
import tailwind from '@astrojs/tailwind'; import tailwind from '@astrojs/tailwind';
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from 'rollup-plugin-visualizer';
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
integrations: [tailwind(), image(), svelte()], integrations: [tailwind(), image()],
vite: { vite: {
plugins: [visualizer()], plugins: [visualizer()],
}, },

41
package-lock.json generated
View file

@ -9,7 +9,8 @@
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@floating-ui/dom": "1.1.0", "@floating-ui/dom": "1.1.0",
"iconify-icon": "1.0.2" "iconify-icon": "1.0.2",
"nanoid": "4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/image": "0.12.1", "@astrojs/image": "0.12.1",
@ -5206,15 +5207,14 @@
"dev": true "dev": true
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.4", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.0.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "integrity": "sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==",
"dev": true,
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.js"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^14 || ^16 || >=18"
} }
}, },
"node_modules/napi-build-utils": { "node_modules/napi-build-utils": {
@ -5834,6 +5834,18 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true "dev": true
}, },
"node_modules/postcss/node_modules/nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/prebuild-install": { "node_modules/prebuild-install": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz",
@ -12187,10 +12199,9 @@
"dev": true "dev": true
}, },
"nanoid": { "nanoid": {
"version": "3.3.4", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-4.0.0.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", "integrity": "sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg=="
"dev": true
}, },
"napi-build-utils": { "napi-build-utils": {
"version": "1.0.2", "version": "1.0.2",
@ -12552,6 +12563,14 @@
"nanoid": "^3.3.4", "nanoid": "^3.3.4",
"picocolors": "^1.0.0", "picocolors": "^1.0.0",
"source-map-js": "^1.0.2" "source-map-js": "^1.0.2"
},
"dependencies": {
"nanoid": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz",
"integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==",
"dev": true
}
} }
}, },
"postcss-import": { "postcss-import": {

View file

@ -20,15 +20,13 @@
}, },
"dependencies": { "dependencies": {
"@floating-ui/dom": "1.1.0", "@floating-ui/dom": "1.1.0",
"iconify-icon": "1.0.2" "iconify-icon": "1.0.2",
"nanoid": "4.0.0"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/image": "0.12.1", "@astrojs/image": "0.12.1",
"@astrojs/react": "1.2.2", "@astrojs/react": "1.2.2",
"@astrojs/svelte": "1.0.2",
"@astrojs/tailwind": "2.1.3", "@astrojs/tailwind": "2.1.3",
"@types/react": "18.0.26",
"@types/react-dom": "18.0.10",
"astro": "1.9.2", "astro": "1.9.2",
"concurrently": "7.6.0", "concurrently": "7.6.0",
"iconify-icon-names": "1.1.0", "iconify-icon-names": "1.1.0",
@ -36,10 +34,8 @@
"postcss": "8.4.21", "postcss": "8.4.21",
"prettier": "2.8.2", "prettier": "2.8.2",
"prettier-plugin-astro": "0.7.2", "prettier-plugin-astro": "0.7.2",
"prettier-plugin-svelte": "2.9.0",
"prettier-plugin-tailwindcss": "0.2.1", "prettier-plugin-tailwindcss": "0.2.1",
"rollup-plugin-visualizer": "5.9.0", "rollup-plugin-visualizer": "5.9.0",
"svelte": "3.55.1",
"tailwindcss": "3.2.4", "tailwindcss": "3.2.4",
"typescript": "4.9.4" "typescript": "4.9.4"
} }

View file

@ -1,7 +1,7 @@
--- ---
import type { IconName } from '@/types/icon'; import type { IconName } from '@/types/icon';
import Icon from './icon.svelte'; import Icon from './icon.astro';
type IconButtonSize = 'small' | 'large'; type IconButtonSize = 'small' | 'large';
@ -28,5 +28,5 @@ const classes = /* tw */ {
--- ---
<a href={href} target={target} class:list={[classes.main, classes.active, classes.focus, sizeMap[size]]} {...rest}> <a href={href} target={target} class:list={[classes.main, classes.active, classes.focus, sizeMap[size]]} {...rest}>
<Icon client:load name={icon} size={16} /> <Icon name={icon} size={16} />
</a> </a>

View file

@ -1,19 +0,0 @@
<script lang="ts">
import type { Placement, offset, Padding } from '@floating-ui/dom';
import type { IconName } from '@/types/icon';
import Icon from './icon.svelte';
import Tooltip from './tooltip.svelte';
export let name: IconName;
export let size: number;
export let color: string | undefined = undefined;
export let content: string;
export let placement: Placement | undefined = undefined;
export let padding: Padding = 8;
export let spacing: Parameters<typeof offset>[0] = 8;
</script>
<Tooltip {content} {placement} {padding} {spacing}>
<Icon {name} {color} {size} />
</Tooltip>

22
src/components/icon.astro Normal file
View file

@ -0,0 +1,22 @@
---
import type { IconName } from '@/types/icon';
export interface Props {
name: IconName;
size: number;
color?: string;
class?: string;
}
const { name, size, color = 'currentColor', ...props } = Astro.props;
---
<iconify-icon
icon={name}
width={size}
height={size}
style={{ color, width: `${size}px`, height: `${size}px` }}
{...props}></iconify-icon>
<script>
import 'iconify-icon';
</script>

View file

@ -1,17 +0,0 @@
<script lang="ts">
import 'iconify-icon';
import type { IconName } from '@/types/icon';
import { isServer } from '@/utils/env';
export let name: IconName;
export let size: number;
export let color: string | undefined = undefined;
const dimensions = `width: ${size}px; height: ${size}px`;
</script>
{#if isServer}
<div style={dimensions} />
{:else}
<iconify-icon icon={name} width={size} height={size} style="color: {color}; {dimensions}"> ></iconify-icon>
{/if}

View file

@ -0,0 +1,34 @@
---
import type { SectionKey } from '@/types/data';
import type { IconName } from '@/types/icon';
import Icon from './icon.astro';
export interface Props {
section: SectionKey;
icon: IconName;
title?: string;
}
const { section, icon, title = '', ...props } = Astro.props;
---
<a
href={`#${section}`}
class="inline-flex h-10 w-10 items-center justify-center rounded-lg transition"
aria-label={`${section} section`}
data-tooltip={`${title || section.charAt(0).toUpperCase() + section.slice(1)}`}
data-tooltip-placement="left"
{...props}
>
<Icon name={icon} size={20} />
</a>
<style>
[aria-current='page'] {
@apply bg-primary-600 text-white;
}
:not([aria-current='page']) {
@apply text-gray-400 hover:bg-primary-600 hover:text-white dark:text-gray-200;
}
</style>

View file

@ -1,36 +0,0 @@
<script lang="ts">
import type { SectionKey } from '@/types/data';
import type { IconName } from '@/types/icon';
import hashState from '@/utils/hash-state';
import Icon from './icon.svelte';
import Tooltip from './tooltip.svelte';
export let section: SectionKey;
export let icon: IconName;
export let title: string = '';
let hash = hashState.getHash();
hashState.subscribe((newHash) => (hash = newHash));
const href = `#${section}`;
$: active = hash === href;
const classes = /* tw */ {
main: 'inline-flex h-10 w-10 items-center justify-center rounded-lg transition',
active: 'bg-primary-600 text-white',
inactive: 'text-gray-400 hover:bg-primary-600 hover:text-white dark:text-gray-200',
};
</script>
<Tooltip content={`${title || section.charAt(0).toUpperCase() + section.slice(1)}`} placement="left">
<a
{href}
class={`${classes.main} ${active ? classes.active : classes.inactive}`}
aria-current={active ? 'page' : undefined}
aria-label={`${section} section`}
>
<Icon name={icon} size={20} />
</a>
</Tooltip>

View file

@ -2,7 +2,7 @@
import type { Data } from '@/data'; import type { Data } from '@/data';
import isSectionKey from '@/utils/is-section-key'; import isSectionKey from '@/utils/is-section-key';
import SidebarItem from './sidebar-item.svelte'; import SidebarItem from './sidebar-item.astro';
export interface Props { export interface Props {
className?: string; className?: string;
@ -23,10 +23,31 @@ const sections = Object.keys(data).flatMap((key) => {
--- ---
<nav <nav
id="sidebar"
class:list={[ class:list={[
'hidden xl:flex flex-col w-max h-fit p-2 rounded-lg gap-2 bg-white dark:bg-gray-800 shadow-md', 'hidden xl:flex flex-col w-max h-fit p-2 rounded-lg gap-2 bg-white dark:bg-gray-800 shadow-md',
className, className,
]} ]}
> >
{sections.map((section) => <SidebarItem client:load {...section} />)} {sections.map((section) => <SidebarItem {...section} />)}
</nav> </nav>
<script>
import hashState from '@/utils/hash-state';
const sidebarItems = [...document.getElementById('sidebar')!.children] as HTMLAnchorElement[];
const setActiveItem = (hash: string) => {
sidebarItems.forEach((item) => {
if (item.href.endsWith(hash)) {
item.setAttribute('aria-current', 'page');
} else {
item.removeAttribute('aria-current');
}
});
};
setActiveItem(hashState.getHash());
hashState.subscribe(setActiveItem);
</script>

View file

@ -1,7 +1,7 @@
--- ---
import type { IconName } from '@/types/icon'; import type { IconName } from '@/types/icon';
import Icon from './icon.svelte'; import Icon from './icon.astro';
export interface Props { export interface Props {
name?: IconName; name?: IconName;
@ -14,6 +14,6 @@ const { name, color } = Astro.props;
<div <div
class="flex h-6 w-fit items-center gap-x-1.5 rounded bg-gray-100 px-2.5 text-sm font-medium tracking-wide text-gray-700 dark:bg-gray-700 dark:text-gray-100" class="flex h-6 w-fit items-center gap-x-1.5 rounded bg-gray-100 px-2.5 text-sm font-medium tracking-wide text-gray-700 dark:bg-gray-700 dark:text-gray-100"
> >
{name && <Icon client:load name={name} color={color} size={16} />} {name && <Icon name={name} color={color} size={16} />}
<slot /> <slot />
</div> </div>

View file

@ -0,0 +1,25 @@
---
import Icon from './icon.astro';
---
<button
id="theme-toggle"
class="fixed bottom-3 left-3 z-10 inline-flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 text-gray-400 shadow-xl transition focus:ring-primary-500 dark:bg-gray-600 dark:text-gray-200"
>
<Icon class="block dark:hidden" name="ri:moon-fill" size={20} />
<Icon class="hidden dark:block" name="ri:sun-line" size={20} />
</button>
<script>
const themeToggle = document.getElementById('theme-toggle')!;
const toggleTheme = () => {
const theme = localStorage.getItem('theme') ?? 'light';
const newTheme = theme === 'light' ? 'dark' : 'light';
document.documentElement.classList[newTheme === 'dark' ? 'add' : 'remove']('dark');
localStorage.setItem('theme', newTheme);
};
themeToggle.addEventListener('click', toggleTheme);
</script>

View file

@ -1,26 +0,0 @@
<script lang="ts">
import { isClient } from '@/utils/env';
import Icon from './icon.svelte';
let theme = localStorage.getItem('theme') ?? 'light';
const toggleTheme = () => {
theme = theme === 'light' ? 'dark' : 'light';
};
const handleThemeChange = () => {
if (isClient) {
document.documentElement.classList[theme === 'dark' ? 'add' : 'remove']('dark');
localStorage.setItem('theme', theme);
}
};
$: theme, handleThemeChange();
</script>
<button
on:click={toggleTheme}
class="fixed bottom-3 left-3 z-10 inline-flex h-10 w-10 items-center justify-center rounded-lg bg-gray-100 text-gray-400 shadow-xl transition focus:ring-primary-500 dark:bg-gray-600 dark:text-gray-200"
>
<Icon name={theme === 'light' ? 'ri:moon-fill' : 'ri:sun-line'} size={20} />
</button>

View file

@ -1,47 +0,0 @@
<script lang="ts">
import { computePosition, Placement, flip, shift, offset, Padding } from '@floating-ui/dom';
export let content: string;
export let placement: Placement | undefined;
export let padding: Padding = 8;
export let spacing: Parameters<typeof offset>[0] = 8;
let button: HTMLElement;
let tooltip: HTMLElement;
const updateTooltip = () => {
computePosition(button, tooltip, { placement, middleware: [offset(spacing), flip(), shift({ padding })] }).then(
({ x, y }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
}
);
};
const showTooltip = () => {
tooltip.style.display = 'block';
updateTooltip();
};
const hideTooltip = () => {
tooltip.style.display = '';
};
</script>
<div
class="flex h-fit w-fit items-center justify-center"
bind:this={button}
on:mouseenter={showTooltip}
on:mouseleave={hideTooltip}
>
<slot />
</div>
<div
bind:this={tooltip}
role="tooltip"
class="absolute top-0 left-0 hidden max-w-sm rounded-lg bg-gray-700 px-3 py-1 text-white dark:bg-gray-100 dark:text-gray-800 sm:max-w-xs"
>
{content}
</div>

View file

@ -1,9 +1,9 @@
--- ---
import Icon from '@/components/icon.svelte'; import Icon from '@/components/icon.astro';
--- ---
<div class="p-5"> <div class="p-5">
<!-- Available icon names here: https://icon-sets.iconify.design --> <!-- Available icon names here: https://icon-sets.iconify.design -->
<!-- Colors for simple icons here: https://simpleicons.org --> <!-- Colors for simple icons here: https://simpleicons.org -->
<Icon client:load name="simple-icons:react" size={24} color="#61DAFB" /> <Icon name="simple-icons:react" size={24} color="#61DAFB" />
</div> </div>

View file

@ -1,7 +1,7 @@
--- ---
import SidebarItem from '@/components/sidebar-item.svelte'; import SidebarItem from '@/components/sidebar-item.astro';
--- ---
<div class="p-5"> <div class="p-5">
<SidebarItem client:load icon="fa6-solid:bars-progress" section="experience" /> <SidebarItem icon="fa6-solid:bars-progress" section="experience" />
</div> </div>

View file

@ -1,6 +1,6 @@
--- ---
import Sidebar from '@/components/sidebar.astro'; import Sidebar from '@/components/sidebar.astro';
import ThemeToggle from '@/components/theme-toggle.svelte'; import ThemeToggle from '@/components/theme-toggle.astro';
import ExperienceSection from '@/sections/experience/experience-section.astro'; import ExperienceSection from '@/sections/experience/experience-section.astro';
import FavoritesSection from '@/sections/favorites/favorites-section.astro'; import FavoritesSection from '@/sections/favorites/favorites-section.astro';
import MainSection from '@/sections/main/main-section.astro'; import MainSection from '@/sections/main/main-section.astro';
@ -44,7 +44,7 @@ const seoImage = seo.image ? seo.image : '/favicon.svg';
</script> </script>
</head> </head>
<body class="flex justify-center overflow-x-hidden bg-gray-50 dark:bg-gray-900 xl:relative xl:left-7"> <body class="flex justify-center overflow-x-hidden bg-gray-50 dark:bg-gray-900 xl:relative xl:left-7">
<ThemeToggle client:load /> <ThemeToggle />
<main class="w-full max-w-5xl space-y-4 px-2 py-3 sm:space-y-6 sm:px-8 sm:py-12 lg:space-y-8 lg:py-20"> <main class="w-full max-w-5xl space-y-4 px-2 py-3 sm:space-y-6 sm:px-8 sm:py-12 lg:space-y-8 lg:py-20">
<MainSection {...data.main} /> <MainSection {...data.main} />
{data.skills && <SkillsSection {...data.skills} />} {data.skills && <SkillsSection {...data.skills} />}
@ -54,5 +54,6 @@ const seoImage = seo.image ? seo.image : '/favicon.svg';
{data.favorites && <FavoritesSection {...data.favorites} />} {data.favorites && <FavoritesSection {...data.favorites} />}
</main> </main>
<Sidebar data={data} className="sticky top-8 mt-20" /> <Sidebar data={data} className="sticky top-8 mt-20" />
<script src="../scripts/initialize-tooltips.ts"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,73 @@
import { computePosition, flip, shift, offset, autoUpdate, Placement } from '@floating-ui/dom';
import { nanoid } from 'nanoid';
interface UpdateTooltipOptions {
element: HTMLElement;
tooltip: HTMLElement;
placement: Placement;
}
const updateTooltip =
({ element, tooltip, placement }: UpdateTooltipOptions) =>
() => {
computePosition(element, tooltip, { placement, middleware: [offset(8), flip(), shift({ padding: 8 })] }).then(
({ x, y }) => {
Object.assign(tooltip.style, {
left: `${x}px`,
top: `${y}px`,
});
}
);
};
const tooltipClass =
/* tw */ 'absolute top-0 left-0 hidden max-w-sm animate-show rounded-lg bg-gray-700 px-3 py-1 text-white dark:bg-gray-100 dark:text-gray-800 sm:max-w-xs';
const createTooltip = (content: string) => {
const tooltip = document.createElement('div');
tooltip.innerText = content;
tooltip.setAttribute('id', `tooltip-${nanoid(8)}`);
tooltip.setAttribute('class', tooltipClass);
tooltip.setAttribute('role', 'tooltip');
return tooltip;
};
const addListeners = (element: HTMLElement, tooltip: HTMLElement, updateFn: () => void) => {
element.addEventListener('mouseenter', () => {
tooltip.style.display = 'block';
updateFn();
});
element.addEventListener('mouseleave', () => {
tooltip.style.display = '';
});
};
const creteTooltipsForElements = (elements: HTMLElement[]) => {
const tooltipsContainer = document.createElement('div');
const tooltips = elements.map((element) => {
const tooltip = createTooltip(element.dataset.tooltip ?? '');
tooltipsContainer.appendChild(tooltip);
return { tooltip, element };
});
document.body.appendChild(tooltipsContainer);
return tooltips;
};
const elements = [...document.querySelectorAll('[data-tooltip]')] as HTMLElement[];
const elementsWithTooltips = creteTooltipsForElements(elements);
elementsWithTooltips.forEach(({ element, tooltip }) => {
const placement = (element.dataset.tooltipPlacement ?? 'top') as Placement;
const updateFn = updateTooltip({ element, tooltip, placement });
element.setAttribute('aria-describedby', tooltip.id);
autoUpdate(element, tooltip, updateFn);
addListeners(element, tooltip, updateFn);
});

View file

@ -1,10 +1,8 @@
--- ---
import IconWithTooltip from '@/components/icon-with-tooltip.svelte';
import Typography from '@/components/typography.astro'; import Typography from '@/components/typography.astro';
import type { IconName } from '@/types/icon';
import type { LevelledSkill } from '@/types/skills-section'; import type { LevelledSkill } from '@/types/skills-section';
import Icon from '../../components/icon.svelte'; import Icon from '../../components/icon.astro';
import SkillLevel from './skill-level.astro'; import SkillLevel from './skill-level.astro';
export interface Props extends LevelledSkill {} export interface Props extends LevelledSkill {}
@ -17,21 +15,16 @@ const IconWrapper = url ? 'a' : 'div';
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<div class="flex h-5 items-center justify-between"> <div class="flex h-5 items-center justify-between">
<IconWrapper class="flex gap-2 h-5" {...(url && { href: url, target: '_blank', rel: 'noopener noreferrer' })}> <IconWrapper class="flex gap-2 h-5" {...(url && { href: url, target: '_blank', rel: 'noopener noreferrer' })}>
{icon && <Icon client:load name={icon} color={iconColor} size={20} />} {icon && <Icon name={icon} color={iconColor} size={20} />}
<Typography variant="tile-subtitle"> <Typography variant="tile-subtitle">
<span class="text-gray-700 dark:text-gray-300">{name}</span> <span class="text-gray-700 dark:text-gray-300">{name}</span>
</Typography> </Typography>
</IconWrapper> </IconWrapper>
{ {
description && ( description && (
<IconWithTooltip <div class="flex h-3.5 w-3.5" data-tooltip={description} data-tooltip-placement="top">
client:load <Icon name="fa6-solid:circle-info" color="#D1D5DB" size={14} />
name={'akar-icons:info-fill' as IconName} </div>
color="#D1D5DB"
size={14}
content={description}
placement="top"
/>
) )
} }
</div> </div>

View file

@ -21,6 +21,15 @@ module.exports = {
fluid200: 'repeat(auto-fit, minmax(200px, 1fr))', fluid200: 'repeat(auto-fit, minmax(200px, 1fr))',
fluid240: 'repeat(auto-fit, minmax(240px, 1fr))', fluid240: 'repeat(auto-fit, minmax(240px, 1fr))',
}, },
keyframes: {
show: {
from: { opacity: '0' },
to: { opacity: '1' },
},
},
animation: {
show: 'show 225ms ease-in-out',
},
}, },
}, },
plugins: [], plugins: [],