fix: portfolio
This commit is contained in:
parent
26368508c4
commit
55adf5ec2f
16 changed files with 323 additions and 81 deletions
1
.env
Normal file
1
.env
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
VITE_API_URL=http://localhost:5173/
|
||||||
|
|
@ -8,14 +8,11 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: self-hosted
|
||||||
container: oven/bun:1
|
container: git.mcpeakdev.com/mcpeakdev/bun-ci:latest
|
||||||
outputs:
|
outputs:
|
||||||
artifact_name: ${{ steps.meta.outputs.name }}
|
artifact_name: ${{ steps.meta.outputs.name }}
|
||||||
steps:
|
steps:
|
||||||
- name: Install git and node
|
|
||||||
run: apt-get update && apt-get install -y git nodejs
|
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|
@ -30,7 +27,13 @@ jobs:
|
||||||
echo "name=portfolio_${tag}" >> "$GITHUB_OUTPUT"
|
echo "name=portfolio_${tag}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: bun install --frozen-lockfile
|
run: |
|
||||||
|
printf '@nychthemeron:registry=https://git.mcpeakdev.com/api/packages/McPeakDev/npm/\n//git.mcpeakdev.com/api/packages/McPeakDev/npm/:_authToken=%s\n' "$BUN_AUTH_TOKEN" > .npmrc
|
||||||
|
bun install --frozen-lockfile
|
||||||
|
rm .npmrc
|
||||||
|
working-directory: ui
|
||||||
|
env:
|
||||||
|
BUN_AUTH_TOKEN: ${{ secrets.BUN_AUTH_TOKEN }}
|
||||||
|
|
||||||
- name: Audit dependencies
|
- name: Audit dependencies
|
||||||
run: bun audit || true
|
run: bun audit || true
|
||||||
|
|
@ -53,8 +56,8 @@ jobs:
|
||||||
publish:
|
publish:
|
||||||
needs: build
|
needs: build
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
runs-on: ubuntu-latest
|
runs-on: self-hosted
|
||||||
container: catthehacker/ubuntu:act-latest
|
container: git.mcpeakdev.com/mcpeakdev/docker-pub:latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
|
||||||
25
bun.lock
25
bun.lock
|
|
@ -5,13 +5,14 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "portfolio",
|
"name": "portfolio",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nychthemeron/library": "^0.0.1",
|
||||||
"vue": "^3.5.32",
|
"vue": "^3.5.32",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.59.1",
|
"@playwright/test": "^1.59.1",
|
||||||
"@tsconfig/node24": "^24.0.4",
|
"@tsconfig/node24": "^24.0.4",
|
||||||
"@types/jsdom": "^28.0.1",
|
"@types/jsdom": "^28.0.1",
|
||||||
"@types/node": "^24.12.2",
|
"@types/node": "^25.9.3",
|
||||||
"@vitejs/plugin-vue": "^6.0.6",
|
"@vitejs/plugin-vue": "^6.0.6",
|
||||||
"@vitest/eslint-plugin": "^1.6.16",
|
"@vitest/eslint-plugin": "^1.6.16",
|
||||||
"@vue/eslint-config-typescript": "^14.7.0",
|
"@vue/eslint-config-typescript": "^14.7.0",
|
||||||
|
|
@ -170,6 +171,8 @@
|
||||||
|
|
||||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||||
|
|
||||||
|
"@nychthemeron/library": ["@nychthemeron/library@0.0.1", "https://git.mcpeakdev.com/api/packages/McPeakDev/npm/%40nychthemeron%2Flibrary/-/0.0.1/library-0.0.1.tgz", { "peerDependencies": { "primevue": "^4.5.0", "vue": "^3.5.0" } }, "sha512-84pcTnF8Ead8D7TSLhKTS8ACpaOCTS0mylzkWqgGrNs4fnrH9tEJg874nFHegiFy5P1fuKCk4B7Yt/Pjc4NK/Q=="],
|
||||||
|
|
||||||
"@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="],
|
"@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="],
|
||||||
|
|
||||||
"@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="],
|
"@oxc-project/types": ["@oxc-project/types@0.133.0", "", {}, "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA=="],
|
||||||
|
|
@ -256,6 +259,16 @@
|
||||||
|
|
||||||
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
"@polka/url": ["@polka/url@1.0.0-next.29", "", {}, "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww=="],
|
||||||
|
|
||||||
|
"@primeuix/styled": ["@primeuix/styled@0.7.4", "", { "dependencies": { "@primeuix/utils": "^0.6.1" } }, "sha512-QSO/NpOQg8e9BONWRBx9y8VGMCMYz0J/uKfNJEya/RGEu7ARx0oYW0ugI1N3/KB1AAvyGxzKBzGImbwg0KUiOQ=="],
|
||||||
|
|
||||||
|
"@primeuix/styles": ["@primeuix/styles@2.0.3", "", { "dependencies": { "@primeuix/styled": "^0.7.4" } }, "sha512-2ykAB6BaHzR/6TwF8ShpJTsZrid6cVIEBVlookSdvOdmlWuevGu5vWOScgIwqWwlZcvkFYAGR/SUV3OHCTBMdw=="],
|
||||||
|
|
||||||
|
"@primeuix/utils": ["@primeuix/utils@0.6.4", "", {}, "sha512-pZ5f+vj7wSzRhC7KoEQRU5fvYAe+RP9+m39CTscZ3UywCD1Y2o6Fe1rRgklMPSkzUcty2jzkA0zMYkiJBD1hgg=="],
|
||||||
|
|
||||||
|
"@primevue/core": ["@primevue/core@4.5.5", "", { "dependencies": { "@primeuix/styled": "^0.7.4", "@primeuix/utils": "^0.6.2" }, "peerDependencies": { "vue": "^3.5.0" } }, "sha512-JpkXhq1ddc70JdsC3CC4dM+UbeeWuCW/8DpS9dNBfrOk824TLSlRlMEGFyVKqRMn5WPQvYLiy3xXfLQeNdSqhQ=="],
|
||||||
|
|
||||||
|
"@primevue/icons": ["@primevue/icons@4.5.5", "", { "dependencies": { "@primeuix/utils": "^0.6.2", "@primevue/core": "4.5.5" } }, "sha512-eteOhTdAOXEYE9qW1AOrBBgDxQ2szHJxSkEK1XVdV2TKxGM5FQf03Ovms0VDyZTc16XBIgvwYjXJQS0BPbhPaA=="],
|
||||||
|
|
||||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="],
|
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.3", "", { "os": "android", "cpu": "arm64" }, "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw=="],
|
||||||
|
|
||||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="],
|
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA=="],
|
||||||
|
|
@ -306,7 +319,7 @@
|
||||||
|
|
||||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||||
|
|
||||||
"@types/node": ["@types/node@24.13.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg=="],
|
"@types/node": ["@types/node@25.9.3", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg=="],
|
||||||
|
|
||||||
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
|
"@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="],
|
||||||
|
|
||||||
|
|
@ -720,6 +733,8 @@
|
||||||
|
|
||||||
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
|
||||||
|
|
||||||
|
"primevue": ["primevue@4.5.5", "", { "dependencies": { "@primeuix/styled": "^0.7.4", "@primeuix/styles": "^2.0.3", "@primeuix/utils": "^0.6.2", "@primevue/core": "4.5.5", "@primevue/icons": "4.5.5" } }, "sha512-Kv5REIewCdP806QaoU+4nBXfmpzOGFKkZ9qH4KsL6MjiAQVc4PUzypt8erl4r3Vzh3nr3aWZIxkxYRRsLGiX2A=="],
|
||||||
|
|
||||||
"proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="],
|
"proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="],
|
||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
@ -876,7 +891,9 @@
|
||||||
|
|
||||||
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
|
||||||
|
|
||||||
"@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
"@types/jsdom/@types/node": ["@types/node@24.13.1", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RSpUJGmvsJ1ZeBehQZFhIdpsz+bIpES0nIQXko4Ybq+N+kX6XvOq3Jo+iJ82FWLdblFq85AsMikd3m35jgezYg=="],
|
||||||
|
|
||||||
|
"@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
|
||||||
|
|
||||||
|
|
@ -918,6 +935,8 @@
|
||||||
|
|
||||||
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
"wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||||
|
|
||||||
|
"@types/jsdom/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
||||||
|
|
||||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||||
|
|
||||||
"editorconfig/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="],
|
"editorconfig/minimatch/brace-expansion": ["brace-expansion@2.1.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA=="],
|
||||||
|
|
|
||||||
1
bunfig.toml
Normal file
1
bunfig.toml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
env = false
|
||||||
|
|
@ -0,0 +1,112 @@
|
||||||
|
# Refactor portfolio to use `@nychthemeron/library`
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The portfolio currently ships a hand-rolled "Nychthemeron" design system:
|
||||||
|
local components in `src/components/ds/` (`NychButton`, `NychCard`,
|
||||||
|
`NychInputText`, `NychTextarea`, `NychMessage`, `NychTag`, `LaurelSpinner`,
|
||||||
|
`LaurelDivider`, `ImageSlot`) backed by ~1200 lines of token CSS in
|
||||||
|
`src/assets/styles/`.
|
||||||
|
|
||||||
|
`@nychthemeron/library` (now installed from the private registry at
|
||||||
|
`git.mcpeakdev.com`) is the extracted, PrimeVue-backed version of this same
|
||||||
|
design system: unstyled PrimeVue components (`Button`, `Card`, `InputText`,
|
||||||
|
`Textarea`, `Message`, `Tag`, `LoadingIcon`, plus others not yet used here)
|
||||||
|
wired up with the same `nych-*` passthrough classes, and ships the same
|
||||||
|
theme as CSS via `@nychthemeron/library/style` and `@nychthemeron/library/theme`.
|
||||||
|
|
||||||
|
Goal: replace the local duplicated implementation with the library, keeping
|
||||||
|
only what the library doesn't provide (`LaurelDivider`, `ImageSlot`, and the
|
||||||
|
580px static hero spinner).
|
||||||
|
|
||||||
|
## Styling / setup
|
||||||
|
|
||||||
|
- `src/main.ts`:
|
||||||
|
- Add `import '@nychthemeron/library/style'` and
|
||||||
|
`import '@nychthemeron/library/theme'`.
|
||||||
|
- Register PrimeVue: `app.use(PrimeVue)` (the library's components are
|
||||||
|
PrimeVue components under the hood).
|
||||||
|
- Keep the existing `import './assets/styles/styles.css'`, but trim its
|
||||||
|
`@import` manifest (see below) so it only covers portfolio-specific
|
||||||
|
CSS; the library CSS imports are added separately in `main.ts`.
|
||||||
|
- Delete `src/assets/styles/tokens/fonts.css`, `tokens/colors.css`,
|
||||||
|
`tokens/base.css` — fully superseded by `@nychthemeron/library/style`
|
||||||
|
(same theme CSS variables for both `hades`/`apollo` themes, font imports,
|
||||||
|
base reset, focus-ring, and the `nych-spin` keyframes).
|
||||||
|
- `src/assets/styles/tokens/components.css`: delete every section except
|
||||||
|
**LaurelDivider** (currently the last section, ~line 738 onward). Rename
|
||||||
|
the file to `tokens/laurel-divider.css` containing just that section.
|
||||||
|
Everything else (Button, Input/Textarea, Checkbox/Radio, ToggleSwitch,
|
||||||
|
Select, Card, Message, Tag, Dialog) is covered by
|
||||||
|
`@nychthemeron/library/theme`.
|
||||||
|
- Keep as-is: `tokens/spacing.css`, `tokens/typography.css`,
|
||||||
|
`portfolio.css`, brand SVGs in `src/assets/brand/`.
|
||||||
|
- Update `src/assets/styles/styles.css`'s `@import` manifest to drop the
|
||||||
|
removed files and add `tokens/laurel-divider.css` in place of
|
||||||
|
`tokens/components.css`.
|
||||||
|
|
||||||
|
## Component mapping
|
||||||
|
|
||||||
|
| Local component | Replacement | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `NychTag` | `Tag` from `@nychthemeron/library` | `severity` / `rounded` props pass straight through |
|
||||||
|
| `NychMessage` | `Message` | `severity` / `closable` pass straight through |
|
||||||
|
| `NychInputText` | `InputText` | `v-model`, `placeholder`, `type`, `invalid` pass through; drop the local `size="normal"` prop (PrimeVue `size` only accepts `small`/`large`/default — `normal` maps to "don't pass `size`") |
|
||||||
|
| `NychTextarea` | `Textarea` | `v-model`, `rows`, `placeholder`, `invalid` — direct match |
|
||||||
|
| `NychButton` | `Button` | `severity`, `disabled`, `loading`, default + `icon` slots — direct match. The library's `Button` has a built-in loading spinner (laurel wreath), replacing the local `LaurelSpinner` usage inside `NychButton` |
|
||||||
|
| `NychCard` | `Card` | slot rename: local `image` slot → PrimeVue `header` slot; local default slot → PrimeVue `content` slot; `title`/`subtitle` props match |
|
||||||
|
| `LaurelSpinner` (small, used inside `NychButton`) | dropped — covered by `Button`'s built-in loader | |
|
||||||
|
| `LaurelSpinner` (Hero, 580px static, `:spinning="false"`) | kept as a local component, rewritten as a thin wrapper around `NychLoadingIcon` imported from `@nychthemeron/library/components`, with scoped CSS to set `width`/`height` and override/disable the `nych-spin` animation when `spinning` is false | per approved "wrap with CSS overrides" approach |
|
||||||
|
| `LaurelDivider`, `ImageSlot` | unchanged, no library equivalent | |
|
||||||
|
|
||||||
|
## Files affected
|
||||||
|
|
||||||
|
**Deleted:**
|
||||||
|
- `src/components/ds/NychTag.vue`
|
||||||
|
- `src/components/ds/NychMessage.vue`
|
||||||
|
- `src/components/ds/NychInputText.vue`
|
||||||
|
- `src/components/ds/NychTextarea.vue`
|
||||||
|
- `src/components/ds/NychButton.vue`
|
||||||
|
- `src/components/ds/NychCard.vue`
|
||||||
|
- `src/assets/styles/tokens/fonts.css`
|
||||||
|
- `src/assets/styles/tokens/colors.css`
|
||||||
|
- `src/assets/styles/tokens/base.css`
|
||||||
|
|
||||||
|
**Rewritten:**
|
||||||
|
- `src/components/ds/LaurelSpinner.vue` — becomes a thin wrapper around the
|
||||||
|
library's `NychLoadingIcon`, preserving its existing `size` / `color` /
|
||||||
|
`spinning` prop API so `HeroSection.vue` doesn't need to change its usage.
|
||||||
|
- `src/assets/styles/tokens/components.css` → `tokens/laurel-divider.css`
|
||||||
|
(LaurelDivider section only).
|
||||||
|
- `src/assets/styles/styles.css` (updated `@import` manifest).
|
||||||
|
- `src/main.ts` (add PrimeVue + library CSS imports, `app.use(PrimeVue)`).
|
||||||
|
|
||||||
|
**Updated imports/usages:**
|
||||||
|
- `src/components/portfolio/SkillsSection.vue` — `NychTag` from library.
|
||||||
|
- `src/components/portfolio/WorkSection.vue` — `NychCard`, `NychTag` from
|
||||||
|
library; update `NychCard` slot usage (`image` → `header`, default →
|
||||||
|
`content`).
|
||||||
|
- `src/components/portfolio/ContactSection.vue` — `NychInputText`,
|
||||||
|
`NychTextarea`, `NychButton`, `NychMessage` from library.
|
||||||
|
- `src/components/portfolio/HeroSection.vue` — `NychButton` from library;
|
||||||
|
`LaurelSpinner` continues to be imported from `./ds/LaurelSpinner.vue`
|
||||||
|
(rewritten).
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
- `bun run build` and `bun run type-check` must pass.
|
||||||
|
- Visually verify via dev server (`bun run dev`):
|
||||||
|
- Hero section renders the large static laurel wreath at the same size.
|
||||||
|
- Skills/Work section tags render with correct severity colors.
|
||||||
|
- Work section cards render header image, title, content, footer tags.
|
||||||
|
- Contact form inputs/textarea/button/message render and behave
|
||||||
|
(loading spinner shows in button while "sending").
|
||||||
|
- Both `hades` and `apollo` themes (toggle `data-theme` on `<html>`) still
|
||||||
|
render correctly.
|
||||||
|
|
||||||
|
## Out of scope
|
||||||
|
|
||||||
|
- No changes to `LaurelDivider.vue` or `ImageSlot.vue` internals.
|
||||||
|
- No changes to other library components not currently used
|
||||||
|
(`Checkbox`, `Select`, `Dialog`, `ToggleSwitch`, `RadioButton`, etc.).
|
||||||
|
- No changes to the CI workflow or registry configuration.
|
||||||
|
|
@ -17,13 +17,14 @@
|
||||||
"format": "oxfmt src/"
|
"format": "oxfmt src/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@nychthemeron/library": "^0.0.1",
|
||||||
"vue": "^3.5.32"
|
"vue": "^3.5.32"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@playwright/test": "^1.59.1",
|
"@playwright/test": "^1.59.1",
|
||||||
"@tsconfig/node24": "^24.0.4",
|
"@tsconfig/node24": "^24.0.4",
|
||||||
"@types/jsdom": "^28.0.1",
|
"@types/jsdom": "^28.0.1",
|
||||||
"@types/node": "^24.12.2",
|
"@types/node": "^25.9.3",
|
||||||
"@vitejs/plugin-vue": "^6.0.6",
|
"@vitejs/plugin-vue": "^6.0.6",
|
||||||
"@vitest/eslint-plugin": "^1.6.16",
|
"@vitest/eslint-plugin": "^1.6.16",
|
||||||
"@vue/eslint-config-typescript": "^14.7.0",
|
"@vue/eslint-config-typescript": "^14.7.0",
|
||||||
|
|
|
||||||
1
sample.env
Normal file
1
sample.env
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
VITE_API_URL=http://localhost:5173/
|
||||||
52
src/__tests__/composables/useAPI.spec.ts
Normal file
52
src/__tests__/composables/useAPI.spec.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
|
|
||||||
|
import { useAPI } from "../../composables/useAPI";
|
||||||
|
|
||||||
|
describe("useAPI", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates properly", () => {
|
||||||
|
const api = useAPI();
|
||||||
|
expect(api).not.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns data", async () => {
|
||||||
|
const mockData = {
|
||||||
|
something: "test",
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.fetch = vi.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve(mockData),
|
||||||
|
} as Response),
|
||||||
|
);
|
||||||
|
|
||||||
|
const api = useAPI();
|
||||||
|
|
||||||
|
const data = await api.getPortfolioData();
|
||||||
|
|
||||||
|
expect(data).toStrictEqual(mockData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns undefined on failure", async () => {
|
||||||
|
const mockData = {
|
||||||
|
something: "test",
|
||||||
|
};
|
||||||
|
|
||||||
|
globalThis.fetch = vi.fn(() =>
|
||||||
|
Promise.reject({
|
||||||
|
ok: true,
|
||||||
|
json: () => Promise.resolve(mockData),
|
||||||
|
} as Response),
|
||||||
|
);
|
||||||
|
|
||||||
|
const api = useAPI();
|
||||||
|
|
||||||
|
const data = await api.getPortfolioData();
|
||||||
|
|
||||||
|
expect(data).toStrictEqual(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
20
src/composables/useAPI.ts
Normal file
20
src/composables/useAPI.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { useUtils } from "./useUtils";
|
||||||
|
|
||||||
|
export const useAPI = (baseURL = API_URL) => {
|
||||||
|
const { url } = useUtils();
|
||||||
|
|
||||||
|
const normalizedURL = url.normalizePath(baseURL.trim());
|
||||||
|
|
||||||
|
const generateURL = (path: string) => {
|
||||||
|
return normalizedURL + path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPortfolioData = async () =>
|
||||||
|
fetch(generateURL("/portfolio/"))
|
||||||
|
.then((resp) => resp.json())
|
||||||
|
.catch(() => undefined);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getPortfolioData,
|
||||||
|
};
|
||||||
|
};
|
||||||
11
src/composables/useUtils.ts
Normal file
11
src/composables/useUtils.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export const useUtils = () => {
|
||||||
|
const normalizePath = (path: string) => {
|
||||||
|
path.endsWith("/") ? path.slice(0, -1) : path;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: {
|
||||||
|
normalizePath,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
{
|
{
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
"include": ["vite-env.d.ts", "env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||||
"exclude": ["src/**/__tests__/*"],
|
"exclude": ["src/**/__tests__/*"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Extra safety for array and object lookups, but may have false positives.
|
// Extra safety for array and object lookups, but may have false positives.
|
||||||
"noUncheckedIndexedAccess": true,
|
"noUncheckedIndexedAccess": true,
|
||||||
|
|
||||||
// Path mapping for cleaner imports.
|
// Path mapping for cleaner imports.
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
},
|
},
|
||||||
|
|
||||||
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
// Specified here to keep it out of the root directory.
|
// Specified here to keep it out of the root directory.
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
{
|
{
|
||||||
"files": [],
|
"files": [],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.node.json"
|
"path": "./tsconfig.node.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.app.json"
|
"path": "./tsconfig.app.json"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.vitest.json"
|
"path": "./tsconfig.vitest.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
{
|
{
|
||||||
"extends": "./tsconfig.app.json",
|
"extends": "./tsconfig.app.json",
|
||||||
|
|
||||||
// Override to include only test files and clear exclusions.
|
// Override to include only test files and clear exclusions.
|
||||||
// .vue files must be explicitly included so vue-tsc enables .vue module
|
// .vue files must be explicitly included so vue-tsc enables .vue module
|
||||||
// resolution for this project (relying on transitive module resolution
|
// resolution for this project (relying on transitive module resolution
|
||||||
// from test imports is unreliable in `vue-tsc --build`).
|
// from test imports is unreliable in `vue-tsc --build`).
|
||||||
"include": ["src/**/__tests__/*", "env.d.ts", "src/**/*.vue"],
|
"include": ["src/**/__tests__/*", "env.d.ts", "src/**/*.vue"],
|
||||||
"exclude": [],
|
"exclude": [],
|
||||||
|
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Vitest runs in a different environment than the application code.
|
// Vitest runs in a different environment than the application code.
|
||||||
// Adjust lib and types accordingly.
|
// Adjust lib and types accordingly.
|
||||||
"lib": [],
|
"lib": ["dom", "es2015", "esnext"],
|
||||||
"types": ["node", "jsdom"],
|
"types": ["node", "jsdom"],
|
||||||
|
|
||||||
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
// `vue-tsc --build` produces a .tsbuildinfo file for incremental type-checking.
|
||||||
// Specified here to keep it out of the root directory.
|
// Specified here to keep it out of the root directory.
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo"
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.vitest.tsbuildinfo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
13
vite-env.d.ts
vendored
Normal file
13
vite-env.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
interface ViteTypeOptions {
|
||||||
|
strictImportMetaEnv: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_API_URL: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const API_URL: string;
|
||||||
|
|
@ -1,18 +1,24 @@
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from "node:url";
|
||||||
|
import { defineConfig, loadEnv } from "vite";
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
import vue from "@vitejs/plugin-vue";
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vueDevTools from "vite-plugin-vue-devtools";
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => {
|
||||||
plugins: [
|
const env = loadEnv(mode, process.cwd(), "VITE");
|
||||||
vue(),
|
const baseURL = fileURLToPath(new URL("./src", import.meta.url));
|
||||||
vueDevTools(),
|
|
||||||
],
|
return {
|
||||||
resolve: {
|
define: {
|
||||||
alias: {
|
API_URL: JSON.stringify(env.VITE_API_URL),
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
||||||
},
|
},
|
||||||
},
|
plugins: [vue(), vueDevTools()],
|
||||||
})
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": baseURL,
|
||||||
|
"~": baseURL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from "node:url";
|
||||||
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
|
import { mergeConfig, defineConfig, configDefaults } from "vitest/config";
|
||||||
import viteConfig from './vite.config'
|
import viteConfig from "./vite.config";
|
||||||
|
|
||||||
export default mergeConfig(
|
export default defineConfig((configEnv) =>
|
||||||
viteConfig,
|
mergeConfig(
|
||||||
defineConfig({
|
viteConfig(configEnv),
|
||||||
test: {
|
defineConfig({
|
||||||
environment: 'jsdom',
|
test: {
|
||||||
exclude: [...configDefaults.exclude, 'e2e/**'],
|
environment: "jsdom",
|
||||||
root: fileURLToPath(new URL('./', import.meta.url)),
|
exclude: [...configDefaults.exclude, "e2e/**"],
|
||||||
},
|
root: fileURLToPath(new URL("./", import.meta.url)),
|
||||||
}),
|
},
|
||||||
)
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue