1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-25 17:55:23 -04:00

add stylelint

This commit is contained in:
Jake Jarvis 2025-03-12 09:44:06 -04:00
parent 8e89701453
commit 5b2caf4a96
Signed by: jake
SSH Key Fingerprint: SHA256:nCkvAjYA6XaSPUqc4TfbBQTpzr8Xj7ritg/sGInCdkc
21 changed files with 748 additions and 155 deletions

View File

@ -20,10 +20,12 @@
"typescript.tsdk": "node_modules/typescript/lib"
},
"extensions": [
"EditorConfig.EditorConfig",
"dbaeumer.vscode-eslint",
"EditorConfig.EditorConfig",
"prisma.prisma",
"unifiedjs.vscode-mdx"
"unifiedjs.vscode-mdx",
"esbenp.prettier-vscode",
"stylelint.vscode-stylelint"
]
}
},

View File

@ -1,6 +1,4 @@
/**
* @type {import("prettier").Config}
*/
/** @type {import("prettier").Config} */
const config = {
singleQuote: false,
jsxSingleQuote: false,

19
.stylelintrc.mjs Normal file
View File

@ -0,0 +1,19 @@
/* eslint-disable import/no-anonymous-default-export */
/** @type {import("stylelint").Config} */
export default {
extends: ["stylelint-config-standard", "stylelint-config-css-modules"],
rules: {
"selector-class-pattern": null,
"custom-property-pattern": null,
"media-feature-range-notation": null,
"rule-empty-line-before": [
"always-multi-line",
{
except: ["after-single-line-comment"],
ignore: ["inside-block"],
},
],
"color-hex-length": "long",
},
};

View File

@ -58,6 +58,7 @@
.result.success {
color: var(--colors-success);
}
.result.error {
color: var(--colors-error);
}

View File

@ -35,11 +35,13 @@
white-space: nowrap;
margin-right: 0.75em;
}
.meta .metaTag:before {
.meta .metaTag::before {
content: "\0023"; /* cosmetically hashtagify tags */
padding-right: 0.125em;
color: var(--colors-light);
}
.meta .metaTag:last-of-type {
margin-right: 0;
}

View File

@ -7,6 +7,7 @@
.section:first-of-type {
margin-top: 0.25em;
}
.section:last-of-type {
margin-bottom: 0.25em;
}

View File

@ -60,6 +60,7 @@
30% {
transform: rotate(0deg);
}
/* pause for ~9 out of 10 seconds */
100% {
transform: rotate(0deg);

View File

@ -11,7 +11,7 @@
.card {
flex-grow: 1;
width: 370px;
padding: 1.2em 1.2em 0.8em 1.2em;
padding: 1.2em 1.2em 0.8em;
border: 1px solid var(--colors-kindaLight);
border-radius: var(--radii-corner);
font-size: 0.9em;
@ -32,7 +32,6 @@
.card .meta {
display: flex;
flex-wrap: wrap;
align-items: baseline;
}
.card .metaItem {
@ -51,7 +50,7 @@
}
.card .metaIcon {
display: inline;
display: inline-block;
width: 1.25em;
height: 1.25em;
vertical-align: -0.25em;
@ -61,9 +60,10 @@
.card .metaLanguage {
display: inline-block;
position: relative;
width: 1.15em;
height: 1.15em;
width: 1.1em;
height: 1.1em;
line-height: 1;
margin-right: 0.5em;
border-radius: 50%;
vertical-align: text-top;
vertical-align: -0.2em;
}

View File

@ -114,11 +114,7 @@ export default async function Page() {
<div className={styles.grid}>
{repos?.map((repo) => (
<div key={repo.name} className={styles.card}>
<Link
// @ts-ignore
href={repo.url}
className={styles.name}
>
<Link href={repo.url} className={styles.name}>
{repo.name}
</Link>
@ -137,7 +133,6 @@ export default async function Page() {
{repo.stars && repo.stars > 0 && (
<div className={styles.metaItem}>
<Link
// @ts-ignore
href={`${repo.url}/stargazers`}
title={`${commaNumber(repo.stars)} ${repo.stars === 1 ? "star" : "stars"}`}
plain
@ -152,7 +147,6 @@ export default async function Page() {
{repo.forks && repo.forks > 0 && (
<div className={styles.metaItem}>
<Link
// @ts-ignore
href={`${repo.url}/network/members`}
title={`${commaNumber(repo.forks)} ${repo.forks === 1 ? "fork" : "forks"}`}
plain

View File

@ -1,7 +1,7 @@
:root {
--colors-backgroundInner: #ffffff;
--colors-backgroundOuter: #fcfcfc;
--colors-backgroundHeader: rgba(252, 252, 252, 0.7);
--colors-backgroundHeader: rgb(252 252 252 / 70%);
--colors-text: #202020;
--colors-mediumDark: #515151;
--colors-medium: #5e5e5e;
@ -11,7 +11,7 @@
--colors-superLight: #f4f4f4;
--colors-superDuperLight: #fbfbfb;
--colors-link: #0e6dc2;
--colors-linkUnderline: rgba(14, 109, 194, 0.4);
--colors-linkUnderline: rgb(14 109 194 / 40%);
--colors-success: #44a248;
--colors-error: #ff1b1b;
--colors-warning: #f78200;
@ -33,7 +33,7 @@
[data-theme="dark"] {
--colors-backgroundInner: #1e1e1e;
--colors-backgroundOuter: #252525;
--colors-backgroundHeader: rgba(37, 37, 37, 0.85);
--colors-backgroundHeader: rgb(37 37 37 / 85%);
--colors-text: #f1f1f1;
--colors-mediumDark: #d7d7d7;
--colors-medium: #b1b1b1;
@ -43,7 +43,7 @@
--colors-superLight: #272727;
--colors-superDuperLight: #1f1f1f;
--colors-link: #88c7ff;
--colors-linkUnderline: rgba(136, 199, 255, 0.4);
--colors-linkUnderline: rgb(136 199 255 / 40%);
--colors-success: #78df55;
--colors-error: #ff5151;
--colors-warning: #f2b702;

View File

@ -36,6 +36,7 @@
.codeBlock.highlight :global(.token.cdata) {
color: var(--colors-codeComment);
}
.codeBlock.highlight :global(.token.delimiter),
.codeBlock.highlight :global(.token.boolean),
.codeBlock.highlight :global(.token.keyword),
@ -46,11 +47,13 @@
.codeBlock.highlight :global(.token.url) {
color: var(--colors-codeKeyword);
}
.codeBlock.highlight :global(.token.tag),
.codeBlock.highlight :global(.token.builtin),
.codeBlock.highlight :global(.token.regex) {
color: var(--colors-codeNamespace);
}
.codeBlock.highlight :global(.token.property),
.codeBlock.highlight :global(.token.constant),
.codeBlock.highlight :global(.token.variable),
@ -60,29 +63,37 @@
.codeBlock.highlight :global(.token.char) {
color: var(--colors-codeVariable);
}
.codeBlock.highlight :global(.token.literal-property),
.codeBlock.highlight :global(.token.attr-name) {
color: var(--colors-codeAttribute);
}
.codeBlock.highlight :global(.token.function) {
color: var(--colors-codeLiteral);
}
.codeBlock.highlight :global(.token.tag .punctuation),
.codeBlock.highlight :global(.token.attr-value .punctuation) {
color: var(--colors-codePunctuation);
}
.codeBlock.highlight :global(.token.inserted) {
color: var(--colors-codeAddition);
}
.codeBlock.highlight :global(.token.deleted) {
color: var(--colors-codeDeletion);
}
.codeBlock.highlight :global(.token.url) {
text-decoration: underline;
}
.codeBlock.highlight :global(.token.bold) {
font-weight: bold;
}
.codeBlock.highlight :global(.token.italic) {
font-style: italic;
}
@ -91,9 +102,21 @@
position: absolute;
top: 0;
right: 0;
padding: 0.65em;
background-color: var(--colors-backgroundInner);
height: 3em;
width: 3em;
color: var(--colors-mediumDark);
border: 1px solid var(--colors-kindaLight);
border-top-right-radius: var(--radii-corner);
border-bottom-left-radius: var(--radii-corner);
background-color: var(--colors-backgroundHeader);
backdrop-filter: saturate(180%) blur(5px);
}
.cornerCopyButton > svg {
vertical-align: middle;
}
.cornerCopyButton:hover,
.cornerCopyButton:focus-visible {
color: var(--colors-link);
}

View File

@ -1,20 +0,0 @@
.button {
color: var(--colors-mediumDark);
line-height: 1px;
cursor: pointer;
}
.button:hover,
.button:focus-visible {
color: var(--colors-link);
}
.button.copied {
color: var(--colors-success) !important;
}
.icon {
width: 1.25em;
height: 1.25em;
vertical-align: -0.3em;
}

View File

@ -3,24 +3,18 @@
import { forwardRef, useState, useEffect } from "react";
import innerText from "react-innertext";
import copy from "copy-to-clipboard";
import clsx from "clsx";
import { ClipboardIcon, CheckIcon } from "lucide-react";
import type { ReactNode, Ref, ComponentPropsWithoutRef, ElementRef, MouseEventHandler } from "react";
import styles from "./CopyButton.module.css";
import type { ReactNode, Ref, ComponentPropsWithoutRef, ComponentRef, MouseEventHandler } from "react";
export type CopyButtonProps = ComponentPropsWithoutRef<"button"> & {
source: string | ReactNode;
timeout?: number;
};
const CopyButton = (
{ source, timeout = 2000, className, ...rest }: CopyButtonProps,
ref: Ref<ElementRef<"button">>
) => {
const CopyButton = ({ source, timeout = 2000, style, ...rest }: CopyButtonProps, ref: Ref<ComponentRef<"button">>) => {
const [copied, setCopied] = useState(false);
const handleCopy: MouseEventHandler<ElementRef<"button">> = (e) => {
const handleCopy: MouseEventHandler<ComponentRef<"button">> = (e) => {
// prevent unintentional double-clicks by unfocusing button
e.currentTarget.blur();
@ -54,13 +48,13 @@ const CopyButton = (
aria-label="Copy to clipboard"
onClick={handleCopy}
disabled={copied}
className={clsx(styles.button, copied && styles.copied, className)}
style={{ cursor: copied ? "default" : "pointer", ...style }}
{...rest}
>
{copied ? (
<CheckIcon size="1.25em" className={styles.icon} />
<CheckIcon size="1.25em" style={{ stroke: "var(--colors-success)" }} />
) : (
<ClipboardIcon size="1.25em" className={styles.icon} />
<ClipboardIcon size="1.25em" />
)}
</button>
);

View File

@ -66,6 +66,7 @@
8% {
transform: scale(1);
}
/* pause for ~9 out of 10 seconds */
100% {
transform: scale(1);

View File

@ -6,7 +6,6 @@ import MenuItem from "../MenuItem";
import ThemeToggle from "../ThemeToggle";
import { menuItems } from "../../lib/config/menu";
import type { ComponentPropsWithoutRef } from "react";
import type { LucideIcon } from "lucide-react";
import styles from "./Menu.module.css";
@ -29,7 +28,10 @@ const Menu = ({ className, ...rest }: MenuProps) => {
})}
<li className={styles.menuItem}>
<MenuItem icon={ThemeToggle as LucideIcon} />
<MenuItem
// @ts-expect-error
icon={ThemeToggle}
/>
</li>
</ul>
);

View File

@ -1,3 +1,4 @@
/* stylelint-disable-next-line selector-type-no-unknown */
.wrapper lite-youtube {
margin: 0 auto;
}

View File

@ -16,7 +16,8 @@ const compat = new FlatCompat({
export default [
{ ignores: ["README.md", ".next", ".vercel", "node_modules"] },
...compat.config({
extends: ["eslint:recommended", "next/core-web-vitals", "next/typescript"],
plugins: ["css-modules"],
extends: ["eslint:recommended", "next/core-web-vitals", "next/typescript", "plugin:css-modules/recommended"],
}),
...eslintCustomConfig,
eslintPluginPrettierRecommended,

View File

@ -77,7 +77,7 @@ const nextConfig: NextConfig = {
// NOTE: don't remove this, it ensures de-AMPing the site hasn't offended our google overlords too badly!
// https://developers.google.com/search/docs/advanced/experience/remove-amp#remove-only-amp
{ source: "/notes/:slug/amp.html", destination: "/notes/:slug/", permanent: true },
{ source: "/notes/:slug/amp.html", destination: "/notes/:slug", permanent: true },
// mastodon via subdomain:
// https://docs.joinmastodon.org/admin/config/#web_domain
@ -107,32 +107,29 @@ const nextConfig: NextConfig = {
permanent: true,
},
// google search console has tons of 404s for images prefixed with /public... why? no clue.
{ source: "/public/static/:path*", destination: "/static/:path*", permanent: true },
// remnants of previous sites/CMSes:
{ source: "/index.xml", destination: "/feed.xml", permanent: true },
{ source: "/feed", destination: "/feed.xml", permanent: true },
{ source: "/rss", destination: "/feed.xml", permanent: true },
{ source: "/blog/:path*", destination: "/notes/", permanent: true },
{ source: "/archives/:path*", destination: "/notes/", permanent: true },
{ source: "/blog/:path*", destination: "/notes", permanent: true },
{ source: "/archives/:path*", destination: "/notes", permanent: true },
{ source: "/resume", destination: "/static/resume.pdf", permanent: false },
{ source: "/resume.pdf", destination: "/static/resume.pdf", permanent: false },
// WordPress permalinks:
{
source: "/2016/02/28/millenial-with-hillary-clinton",
destination: "/notes/millenial-with-hillary-clinton/",
destination: "/notes/millenial-with-hillary-clinton",
permanent: true,
},
{
source: "/2018/12/04/how-to-shrink-linux-virtual-disk-vmware",
destination: "/notes/how-to-shrink-linux-virtual-disk-vmware/",
destination: "/notes/how-to-shrink-linux-virtual-disk-vmware",
permanent: true,
},
{
source: "/2018/12/10/cool-bash-tricks-for-your-terminal-dotfiles",
destination: "/notes/cool-bash-tricks-for-your-terminal-dotfiles/",
destination: "/notes/cool-bash-tricks-for-your-terminal-dotfiles",
permanent: true,
},
],

View File

@ -9,7 +9,6 @@
"email": "jake@jarv.is",
"url": "https://github.com/jakejarvis"
},
"type": "module",
"scripts": {
"dev": "next dev -H 0.0.0.0",
"build": "next build",
@ -23,9 +22,9 @@
"@giscus/react": "^3.1.0",
"@mdx-js/loader": "^3.1.0",
"@mdx-js/react": "^3.1.0",
"@next/bundle-analyzer": "15.3.0-canary.0",
"@next/mdx": "15.3.0-canary.0",
"@next/third-parties": "15.3.0-canary.0",
"@next/bundle-analyzer": "15.3.0-canary.1",
"@next/mdx": "15.3.0-canary.1",
"@next/third-parties": "15.3.0-canary.1",
"@octokit/graphql": "^8.2.1",
"@octokit/graphql-schema": "^15.26.0",
"@prisma/client": "^6.5.0",
@ -37,7 +36,7 @@
"feed": "^4.2.2",
"lucide-react": "0.479.0",
"modern-normalize": "^3.0.1",
"next": "15.3.0-canary.0",
"next": "15.3.0-canary.1",
"obj-str": "^1.1.0",
"p-map": "^7.0.3",
"p-memoize": "^7.1.1",
@ -78,8 +77,9 @@
"@types/react-is": "^19.0.0",
"cross-env": "^7.0.3",
"eslint": "^9.22.0",
"eslint-config-next": "15.3.0-canary.0",
"eslint-config-next": "15.3.0-canary.1",
"eslint-config-prettier": "^10.1.1",
"eslint-plugin-css-modules": "^2.12.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-mdx": "^3.2.0",
@ -91,6 +91,10 @@
"prisma": "^6.5.0",
"schema-dts": "^1.1.5",
"simple-git-hooks": "^2.11.1",
"stylelint": "^16.15.0",
"stylelint-config-css-modules": "^4.4.0",
"stylelint-config-recommended": "^15.0.0",
"stylelint-config-standard": "^37.0.0",
"typescript": "5.8.2"
},
"optionalDependencies": {
@ -112,6 +116,7 @@
"eslint"
],
"*.css": [
"stylelint",
"prettier --check"
]
},

714
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@ -24,13 +20,6 @@
}
]
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}