Commit 212d918
Changed files (22)
.vscode
src
.vscode/extensions.json
@@ -3,5 +3,4 @@
"astro-build.astro-vscode",
"bradlc.vscode-tailwindcss"
],
- "unwantedRecommendations": []
}
\ No newline at end of file
.vscode/settings.json
@@ -3,4 +3,25 @@
"editor.tabSize": 2,
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
+ "css.validate": false,
+ "scss.validate": false,
+ "less.validate": false,
+ "stylelint.validate": [
+ "css",
+ "postcss",
+ "scss",
+ "astro",
+ ],
+ "eslint.validate": [
+ "javascript",
+ "javascriptreact",
+ "astro",
+ "typescript",
+ "typescriptreact"
+ ],
+ "eslint.useFlatConfig": true,
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "always",
+ "source.fixAll.stylelint": "always"
+ }
}
\ No newline at end of file
src/components/widgets/AuthorInfoCard.astro
src/components/widgets/Button.astro
@@ -14,28 +14,30 @@ if (href && onclick) throw new AstroParameterConflictError('href', 'onclick');
<slot />
</button>
-<style lang="sass">
- button
- display: flex
- align-items: center
- padding: 0.5rem
- margin: 0.5rem
- text-align: center
- word-break: break-word
- transition-duration: 300ms
- border-radius: 9999px
- width: fit-content
- height: fit-content
+<style lang="scss">
+ button {
+ display: flex;
+ align-items: center;
+ padding: 0.5rem;
+ margin: 0.5rem;
+ text-align: center;
+ word-break: break-word;
+ transition-duration: 300ms;
+ border-radius: 9999px;
+ width: fit-content;
+ height: fit-content;
- &:hover
- @apply bg-[var(--theme-color-light)] dark:bg-[var(--theme-color-dark)]
+ &:hover {
+ @apply bg-[var(--theme-color-light)] dark:bg-[var(--theme-color-dark)];
+ }
+ }
</style>
<script>
import { navigate } from 'astro:transitions/client';
document.querySelectorAll('button[href]').forEach((btn) => {
btn.addEventListener('click', () => {
- let href = btn.getAttribute('href') || '/404.html';
+ const href = btn.getAttribute('href') || '/404.html';
navigate(href);
});
});
src/components/widgets/DarkModeButton.astro
@@ -13,24 +13,16 @@ const { class: className, showText, ...rest } = Astro.props;
---
<Button
- class:list={[
- 'darkmode-btn',
- 'border-2 border-neutral-200 dark:border-neutral-800',
- className,
- ]}
+ class:list={['darkmode-btn', className]}
{...rest}
+ data-text-light={i18n(I18nKey.lightMode)}
+ data-text-dark={i18n(I18nKey.darkMode)}
+ data-text-auto={i18n(I18nKey.systemMode)}
>
<Icon class="darkmode-icon-light" name="material-symbols:light-mode-rounded" />
<Icon class="darkmode-icon-dark" name="material-symbols:dark-mode-rounded" />
<Icon class="darkmode-icon-auto" name="material-symbols:night-sight-auto-rounded" />
- <span class="px-2 ml-auto mr-2">
- {
- showText &&
- <span class="darkmode-text-light">{i18n(I18nKey.lightMode)}</span>
- <span class="darkmode-text-dark">{i18n(I18nKey.darkMode)}</span>
- <span class="darkmode-text-auto">{i18n(I18nKey.systemMode)}</span>
- }
- </span>
+ {showText && <span class="darkmode-text px-2 ml-auto mr-2" />}
</Button>
<script>
@@ -41,31 +33,26 @@ const { class: className, showText, ...rest } = Astro.props;
const iconLight = btn.querySelector('.darkmode-icon-light');
const iconDark = btn.querySelector('.darkmode-icon-dark');
const iconAuto = btn.querySelector('.darkmode-icon-auto');
- const textLight = btn.querySelector('.darkmode-text-light');
- const textDark = btn.querySelector('.darkmode-text-dark');
- const textAuto = btn.querySelector('.darkmode-text-auto');
+ const text = btn.querySelector('.darkmode-text');
if ('darkMode' in localStorage && localStorage.darkMode === 'true') {
iconLight?.classList.add('hidden');
iconDark?.classList.remove('hidden');
iconAuto?.classList.add('hidden');
- textLight?.classList.add('hidden');
- textDark?.classList.remove('hidden');
- textAuto?.classList.add('hidden');
+ if (text) text.textContent = btn.getAttribute('data-text-dark');
+ btn.setAttribute('title', btn.getAttribute('data-text-dark') || '');
} else if ('darkMode' in localStorage && localStorage.darkMode === 'false') {
iconLight?.classList.remove('hidden');
iconDark?.classList.add('hidden');
iconAuto?.classList.add('hidden');
- textLight?.classList.remove('hidden');
- textDark?.classList.add('hidden');
- textAuto?.classList.add('hidden');
+ if (text) text.textContent = btn.getAttribute('data-text-light');
+ btn.setAttribute('title', btn.getAttribute('data-text-light') || '');
} else {
iconLight?.classList.add('hidden');
iconDark?.classList.add('hidden');
iconAuto?.classList.remove('hidden');
- textLight?.classList.add('hidden');
- textDark?.classList.add('hidden');
- textAuto?.classList.remove('hidden');
+ if (text) text.textContent = btn.getAttribute('data-text-auto');
+ btn.setAttribute('title', btn.getAttribute('data-text-auto') || '');
}
});
}
src/components/AsideContent.astro
@@ -0,0 +1,7 @@
+---
+
+---
+
+<div id="aside-content">
+ {}
+</div>
src/components/misc/GlobalBackground.astro → src/components/GlobalBackground.astro
File renamed without changes
src/components/Sidebar.astro
@@ -1,5 +1,5 @@
---
-import { navbarConfig, themeConfig } from '@/config';
+import { navbarConfig } from '@/config';
import { Icon } from 'astro-icon/components';
@@ -15,10 +15,13 @@ import DarkModeButton from './widgets/DarkModeButton.astro';
</div>
<div
id="sidebar-menu"
- class="fixed md:hidden h-full z-50 -right-1/3 w-1/3 duration-500 ease-in-out border-l-2 border-l-neutral-200 dark:border-l-neutral-800 bg-neutral-50 dark:bg-neutral-950"
+ class="fixed md:hidden h-full z-50 -right-1/3 w-1/3 duration-500 ease-in-out border-l-2 theme-border theme-card-bg"
>
<div id="sidebar-site-data"></div>
- <DarkModeButton showText={true} class="text-lg !w-[calc(100%-1rem)]" />
+ <DarkModeButton
+ showText={true}
+ class="text-lg !w-[calc(100%-1rem)] border-2 theme-border"
+ />
<div id="sidebar-menu-text-items">
{
navbarConfig.navbarCenterItems.map((item) => (
src/components/SideToolBar.astro
@@ -1,8 +1,56 @@
---
-
+import { Icon } from 'astro-icon/components';
+import Button from './widgets/Button.astro';
+import DarkModeButton from './widgets/DarkModeButton.astro';
---
-<div id="side-menu" class="close">
- <div id="sm-hide"></div>
- <div id="sm-show"></div>
+<div id="side-toolbar" transition:persist class="fixed bottom-10 right-0 text-xl">
+ <div id="stb-trigger" class="fixed bottom-0 right-0 w-12 h-32 -z-50"></div>
+ <div id="stb-hide" class="translate-x-full duration-500 ease-in-out">
+ <DarkModeButton id="stb-dark-mode" class="" />
+ </div>
+ <div id="stb-show" class="translate-x-full duration-500 ease-in-out">
+ <Button id="stb-show-more">
+ <Icon name="material-symbols:settings-rounded" class="animate-spin" />
+ </Button>
+ <Button id="stb-back-to-top">
+ <Icon name="material-symbols:arrow-upward-rounded" />
+ </Button>
+ </div>
</div>
+
+<script>
+ const stbHide = document.getElementById('stb-hide');
+ const stbShow = document.getElementById('stb-show');
+ const stbShowMore = document.getElementById('stb-show-more');
+ const stbBackToTop = document.getElementById('stb-back-to-top');
+ const stbTrigger = document.getElementById('stb-trigger');
+
+ stbTrigger?.addEventListener('click', () => {
+ stbShow?.classList.toggle('translate-x-full');
+ });
+
+ stbShowMore?.addEventListener('click', () => {
+ stbHide?.classList.toggle('translate-x-full');
+ });
+
+ window.addEventListener('scroll', () => {
+ if (window.scrollY > 0) stbShow?.classList.remove('translate-x-full');
+ else {
+ stbShow?.classList.add('translate-x-full');
+ stbHide?.classList.add('translate-x-full');
+ }
+ });
+</script>
+
+<style lang="scss">
+ #side-toolbar {
+ button {
+ @apply bg-[var(--theme-color-light)] dark:bg-[var(--theme-color-dark)];
+
+ &:hover {
+ @apply bg-[var(--theme-color-light-lighten)] dark:bg-[var(--theme-color-dark-lighten)];
+ }
+ }
+ }
+</style>
src/layouts/GlobalLayout.astro
@@ -1,5 +1,5 @@
---
-import { profileConfig, siteConfig, themeConfig } from '@/config';
+import { profileConfig, siteConfig } from '@/config';
import { ClientRouter } from 'astro:transitions';
interface Props {
@@ -45,7 +45,7 @@ const siteLang = lang.replace('_', '-');
<slot name="head" />
</head>
- <body class="bg-neutral-50 dark:bg-neutral-950 text-neutral-950 dark:text-neutral-50">
+ <body class="theme-bg theme-text">
<slot />
</body>
</html>
@@ -65,12 +65,6 @@ const siteLang = lang.replace('_', '-');
applyDarkMode();
</script>
-<style
- is:global
- define:vars={{
- 'theme-color-light': themeConfig.themeColorLight,
- 'theme-color-dark': themeConfig.themeColorDark,
- 'theme-color-sub-light': themeConfig.themeColorSubLight,
- 'theme-color-sub-dark': themeConfig.themeColorSubDark,
- }}
-></style>
+<style is:global lang="scss">
+ @use '../styles/globals';
+</style>
src/layouts/MainLayout.astro
@@ -15,6 +15,7 @@ const { title, description, lang } = Astro.props;
<GlobalLayout title={title} description={description} lang={lang}>
<slot slot="head" name="head" />
<Sidebar />
+ <SideToolBar />
<div id="body-wrap">
<Navbar />
<!-- Main content -->
src/styles/globals.scss
@@ -0,0 +1,22 @@
+@use './variables';
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer components {
+ .theme-bg {
+ @apply bg-[var(--theme-bg-color-light)] dark:bg-[var(--theme-bg-color-dark)];
+ }
+
+ .theme-card-bg {
+ @apply bg-[var(--theme-card-bg-color-light)] dark:bg-[var(--theme-card-bg-color-dark)];
+ }
+
+ .theme-text {
+ @apply text-[var(--theme-text-color-light)] dark:text-[var(--theme-text-color-dark)];
+ }
+
+ .theme-border {
+ @apply border-[var(--theme-border-color-light)] dark:border-[var(--theme-border-color-dark)];
+ }
+}
src/styles/variables.scss
@@ -0,0 +1,31 @@
+@use 'sass:color';
+
+$theme-color-light: #d75b3d;
+$theme-color-dark: #397d9f;
+$theme-bg-color-light: theme('colors.neutral.50');
+$theme-bg-color-dark: theme('colors.neutral.900');
+$theme-card-bg-color-light: $theme-bg-color-light;
+$theme-card-bg-color-dark: $theme-bg-color-dark;
+$theme-text-color-light: theme('colors.neutral.900');
+$theme-text-color-dark: theme('colors.neutral.100');
+$theme-border-color-light: theme('colors.neutral.200');
+$theme-border-color-dark: theme('colors.neutral.800');
+
+:root {
+ --theme-color-light: #{$theme-color-light};
+ --theme-color-dark: #{$theme-color-dark};
+ --theme-color-light-lighten: #{color.adjust($theme-color-light, $lightness: 20%)};
+ --theme-color-dark-lighten: #{color.adjust($theme-color-dark, $lightness: 20%)};
+ --theme-color-light-darken: #{color.adjust($theme-color-light, $lightness: -20%)};
+ --theme-color-dark-darken: #{color.adjust($theme-color-dark, $lightness: -20%)};
+ --theme-color-light-transparent: #{color.adjust($theme-color-light, $alpha: -0.5)};
+ --theme-color-dark-transparent: #{color.adjust($theme-color-dark, $alpha: -0.5)};
+ --theme-bg-color-light: #{$theme-bg-color-light};
+ --theme-bg-color-dark: #{$theme-bg-color-dark};
+ --theme-card-bg-color-light: #{$theme-card-bg-color-light};
+ --theme-card-bg-color-dark: #{$theme-card-bg-color-dark};
+ --theme-text-color-light: #{$theme-text-color-light};
+ --theme-text-color-dark: #{$theme-text-color-dark};
+ --theme-border-color-light: #{$theme-border-color-light};
+ --theme-border-color-dark: #{$theme-border-color-dark};
+}
src/types/config.ts
@@ -30,11 +30,14 @@ export type NavbarConfig = {
};
};
-export type ThemeConfig = {
- themeColorLight: Color;
- themeColorSubLight: Color;
- themeColorDark: Color;
- themeColorSubDark: Color;
+export type ToolBarConfig = {
+ enable: boolean;
+ items: {
+ icon: string;
+ text?: string;
+ href?: string;
+ onclick?: string;
+ }[];
};
export type LicenseConfig = {
src/types/Errors.ts
@@ -2,7 +2,7 @@ import { AstroError } from 'astro/errors';
export class AstroParameterConflictError extends AstroError {
constructor(...params: string[]) {
- let message = `The following parameters are in conflict: ${params.join(', ')}. Please ensure that only one of these parameters is provided.`;
+ const message = `The following parameters are in conflict: ${params.join(', ')}. Please ensure that only one of these parameters is provided.`;
super(message);
this.name = 'ParameterConflictError';
}
src/config.ts
@@ -3,7 +3,7 @@ import type {
ProfileConfig,
SiteConfig,
NavbarConfig,
- ThemeConfig,
+ ToolBarConfig,
} from './types/config';
import I18nKey from '@i18n/i18nKey';
@@ -24,10 +24,10 @@ export const profileConfig: ProfileConfig = {
export const navbarConfig: NavbarConfig = {
navbarCenterItems: [
- { text: i18n(I18nKey.archive), href: '/archive' },
- { text: i18n(I18nKey.categories), href: '/categories' },
- { text: i18n(I18nKey.tags), href: '/tags' },
- { text: i18n(I18nKey.about), href: '/about' },
+ { text: i18n(I18nKey.archive), href: '/archive/' },
+ { text: i18n(I18nKey.categories), href: '/categories/' },
+ { text: i18n(I18nKey.tags), href: '/tags/' },
+ { text: i18n(I18nKey.about), href: '/about/' },
],
navbarRightItems: {
onlyWide: [
@@ -54,11 +54,9 @@ export const navbarConfig: NavbarConfig = {
},
};
-export const themeConfig: ThemeConfig = {
- themeColorLight: '#D75B3D',
- themeColorSubLight: '#e5d5d5',
- themeColorDark: '#397D9F',
- themeColorSubDark: '#033D60',
+export const toolBarConfig: ToolBarConfig = {
+ enable: true,
+ items: [],
};
export const licenseConfig: LicenseConfig = {
astro.config.mjs
@@ -8,6 +8,7 @@ import icon from 'astro-icon';
export default defineConfig({
site: 'https://astral-halo.netilify.app/',
base: '/',
- trailingSlash: 'always',
+ output: 'static',
+ trailingSlash: 'ignore',
integrations: [tailwind({ nesting: true }), icon()],
});
eslint.config.js
@@ -34,14 +34,11 @@ export default [
},
},
{
- // Define the configuration for `<script>` tag.
- // Script in `<script>` is assigned a virtual file name with the `.js` extension.
files: ['**/*.{ts,tsx}', '**/*.astro/*.js'],
languageOptions: {
parser: typescriptParser,
},
rules: {
- // Note: you must disable the base rule as it can report incorrect errors
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
package.json
@@ -11,9 +11,11 @@
"dependencies": {
"@astrojs/tailwind": "^5.1.4",
"@iconify-json/material-symbols": "^1.2.12",
- "astro": "^5.1.5",
+ "astro": "^5.1.7",
"astro-compress": "2.3.5",
"astro-icon": "^1.1.5",
+ "autoprefixer": "^10.4.20",
+ "postcss-load-config": "^6.0.1",
"sass": "^1.83.4",
"tailwindcss": "^3.4.17",
"typescript": "^5.7.3"
@@ -22,10 +24,15 @@
"@astrojs/check": "^0.9.4",
"@astrojs/ts-plugin": "^1.10.4",
"astro-eslint-parser": "^1.1.0",
- "eslint": "^9.16.0",
+ "eslint": "^9.18.0",
"eslint-plugin-astro": "^1.3.1",
+ "globals": "^15.14.0",
+ "postcss-html": "^1.8.0",
"prettier": "^3.4.2",
"prettier-plugin-astro": "^0.14.1",
- "typescript-eslint": "^8.17.0"
+ "stylelint": "^16.13.2",
+ "stylelint-config-html": "^1.1.0",
+ "stylelint-config-standard-scss": "^14.0.0",
+ "typescript-eslint": "^8.20.0"
}
}
\ No newline at end of file
postcss.config.mjs
@@ -0,0 +1,12 @@
+import postcssImport from 'postcss-import';
+import postcssNesting from 'tailwindcss/nesting/index.js';
+import tailwindcss from 'tailwindcss';
+
+/** @type {import('postcss-load-config').Config} */
+export default {
+ plugins: {
+ 'postcss-import': postcssImport, // to combine multiple css files
+ 'tailwindcss/nesting': postcssNesting,
+ tailwindcss: tailwindcss,
+ },
+};
stylelint.config.mjs
@@ -0,0 +1,24 @@
+/** @type {import('stylelint').Config} */
+export default {
+ extends: ['stylelint-config-standard-scss', 'stylelint-config-html/astro'],
+ rules: {
+ 'scss/at-rule-no-unknown': [
+ true,
+ {
+ ignoreAtRules: [
+ // Tailwind CSS
+ 'tailwind',
+ 'apply',
+ 'layer',
+ 'config',
+ ],
+ },
+ ],
+ 'function-no-unknown': [
+ true,
+ {
+ ignoreFunctions: ['theme'],
+ },
+ ],
+ },
+};