Skip to content

Commit a36e440

Browse files
committed
Merge branch 'main' into feat-production-setup
# Conflicts: # package.json # pnpm-lock.yaml
2 parents 0b7dc94 + 3ff7798 commit a36e440

94 files changed

Lines changed: 4043 additions & 1402 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
browser:
6262
runs-on: ubuntu-latest
6363
container:
64-
image: mcr.microsoft.com/playwright:v1.57.0-noble
64+
image: mcr.microsoft.com/playwright:v1.58.0-noble
6565

6666
steps:
6767
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1

.github/workflows/semantic-pull-requests.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ jobs:
2323
uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
2424
with:
2525
scopes: |
26+
a11y
27+
deps
2628
docs
2729
i18n
28-
deps
30+
ui
2931
subjectPattern: ^(?![A-Z]).+$
3032
subjectPatternError: |
3133
The subject "{subject}" found in the pull request title "{title}"

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ We've added some `UnoCSS` utilities styles to help you with that:
232232
- Do not use `rtl-` classes, such as `rtl-left-0`.
233233
- For icons that should be rotated for RTL, add `class="rtl-flip"`. This can only be used for icons outside of elements with `dir="auto"`.
234234
- For absolute positioned elements, don't use `left/right`: for example `left-0`. Use `inset-inline-start/end` instead. `UnoCSS` shortcuts are `inset-is` for `inset-inline-start` and `inset-ie` for `inset-inline-end`. Example: `left-0` should be replaced with `inset-is-0`.
235-
- If you need to change the border radius for an entire left or right side, use `border-inline-start/end`. `UnoCSS` shortcuts are `rounded-is` for left side, `rounded-ie` for right side. Example: `rounded-l-5` should be replaced with `rounded-ie-5`.
235+
- If you need to change the border radius for an entire left or right side, use `border-inline-start/end`. `UnoCSS` shortcuts are `rounded-is` for left side, `rounded-ie` for right side. Example: `rounded-l-5` should be replaced with `rounded-is-5`.
236236
- If you need to change the border radius for one corner, use `border-start-end-radius` and similar rules. `UnoCSS` shortcuts are `rounded` + top/bottom as either `-bs` (top) or `-be` (bottom) + left/right as either `-is` (left) or `-ie` (right). Example: `rounded-tl-0` should be replaced with `rounded-bs-is-0`.
237237

238238
## Localization (i18n)

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
<p align="center">
66
<a href="https://npmx.dev/">
7-
<img width="1090" alt="Screenshot of npmx.dev showing the nuxt package" src="https://github.com/user-attachments/assets/229497a2-8491-461c-aa1d-fba981215340">
7+
<img width="1090" alt="Screenshot of npmx.dev showing the nuxt package" src="https://github.com/user-attachments/assets/1a2a3205-0227-46dc-b1f9-48f9a65691d3">
88
</a>
99
</p>
1010

app/app.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ const route = useRoute()
66
const router = useRouter()
77
const { locale, locales } = useI18n()
88
9-
// Initialize accent color before hydration to prevent flash
10-
initAccentOnPrehydrate()
9+
// Initialize user preferences (accent color, package manager) before hydration to prevent flash/CLS
10+
initPreferencesOnPrehydrate()
1111
1212
const isHomepage = computed(() => route.name === 'index')
1313

app/assets/main.css

Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,25 @@
2222
--border-subtle: oklch(0.239 0 0);
2323
--border-hover: oklch(0.371 0 0);
2424

25+
/* accent color, set by user from settings */
2526
--accent: var(--accent-color, oklch(1 0 0));
2627
--accent-muted: var(--accent-color, oklch(0.922 0 0));
2728

29+
/* syntax highlighting colors */
2830
--syntax-fn: oklch(0.727 0.137 299.149);
2931
--syntax-str: oklch(0.829 0.088 252.458);
3032
--syntax-kw: oklch(0.721 0.162 15.494);
3133
--syntax-comment: oklch(0.551 0.019 250.976);
34+
35+
/* badge colors for background & text */
36+
--badge-orange: oklch(0.67 0.185 55);
37+
--badge-yellow: oklch(0.588 0.183 91);
38+
--badge-green: oklch(0.566 0.202 165);
39+
--badge-cyan: oklch(0.571 0.181 210);
40+
--badge-blue: oklch(0.579 0.191 252);
41+
--badge-indigo: oklch(0.573 0.262 276.966);
42+
--badge-purple: oklch(0.495 0.172 295);
43+
--badge-pink: oklch(0.584 0.189 343);
3244
}
3345

3446
:root[data-theme='light'] {
@@ -49,9 +61,18 @@
4961
--accent-muted: var(--accent-color, oklch(0.205 0 0));
5062

5163
--syntax-fn: oklch(0.502 0.188 294.988);
52-
--syntax-str: oklch(0.54 0.191 257.481);
64+
--syntax-str: oklch(0.425 0.152 252);
5365
--syntax-kw: oklch(0.588 0.193 20.469);
5466
--syntax-comment: oklch(0.551 0.019 250.976);
67+
68+
--badge-blue: oklch(0.579 0.191 252);
69+
--badge-yellow: oklch(0.588 0.183 91);
70+
--badge-green: oklch(0.566 0.202 165);
71+
--badge-indigo: oklch(0.457 0.24 277.023);
72+
--badge-purple: oklch(0.495 0.172 295);
73+
--badge-orange: oklch(0.67 0.185 55);
74+
--badge-pink: oklch(0.584 0.189 343);
75+
--badge-cyan: oklch(0.571 0.181 210);
5576
}
5677

5778
html {
@@ -154,9 +175,9 @@ button {
154175

155176
/* Skip link */
156177
.skip-link {
157-
position: absolute;
178+
position: fixed;
158179
top: -100%;
159-
left: 0;
180+
inset-inline-start: 0;
160181
padding: 0.5rem 1rem;
161182
background: var(--fg);
162183
color: var(--bg);
@@ -165,6 +186,10 @@ button {
165186
transition: top 0.2s ease;
166187
}
167188

189+
.skip-link:hover {
190+
color: var(--bg);
191+
text-decoration: underline;
192+
}
168193
.skip-link:focus {
169194
top: 0;
170195
}
@@ -289,7 +314,7 @@ html.light .shiki span {
289314
.readme-content ul,
290315
.readme-content ol {
291316
margin: 1rem 0;
292-
padding-left: 1.5rem;
317+
padding-inline-start: 1.5rem;
293318
}
294319

295320
.readme-content ul {
@@ -310,17 +335,18 @@ html.light .shiki span {
310335
}
311336

312337
.readme-content blockquote {
313-
border-left: 2px solid var(--border);
314-
padding-left: 1rem;
338+
border-inline-start: 2px solid var(--border);
339+
padding-inline-start: 1rem;
315340
margin: 1.5rem 0;
316341
color: var(--fg-subtle);
317342
font-style: italic;
318343
}
319344

320345
/* GitHub-style callouts/alerts */
321346
.readme-content blockquote[data-callout] {
322-
border-left-width: 3px;
323-
padding: 1rem 1rem 1rem 1.25rem;
347+
border-inline-start-width: 3px;
348+
padding: 1rem;
349+
padding-inline-start: 1.25rem;
324350
background: var(--bg-subtle);
325351
font-style: normal;
326352
color: var(--fg-subtle);
@@ -335,7 +361,7 @@ html.light .shiki span {
335361
text-transform: uppercase;
336362
letter-spacing: 0.05em;
337363
margin-bottom: 0.5rem;
338-
padding-left: 1.5rem;
364+
padding-inline-start: 1.5rem;
339365
}
340366

341367
.readme-content blockquote[data-callout]::after {
@@ -356,7 +382,7 @@ html.light .shiki span {
356382

357383
/* Note - blue */
358384
.readme-content blockquote[data-callout='note'] {
359-
border-left-color: var(--syntax-str);
385+
border-inline-start-color: var(--syntax-str);
360386
background: rgba(59, 130, 246, 0.05);
361387
}
362388
.readme-content blockquote[data-callout='note']::before {
@@ -371,7 +397,7 @@ html.light .shiki span {
371397

372398
/* Tip - green */
373399
.readme-content blockquote[data-callout='tip'] {
374-
border-left-color: #22c55e;
400+
border-inline-start-color: #22c55e;
375401
background: rgba(34, 197, 94, 0.05);
376402
}
377403
.readme-content blockquote[data-callout='tip']::before {
@@ -386,7 +412,7 @@ html.light .shiki span {
386412

387413
/* Important - purple */
388414
.readme-content blockquote[data-callout='important'] {
389-
border-left-color: var(--syntax-fn);
415+
border-inline-start-color: var(--syntax-fn);
390416
background: rgba(168, 85, 247, 0.05);
391417
}
392418
.readme-content blockquote[data-callout='important']::before {
@@ -401,7 +427,7 @@ html.light .shiki span {
401427

402428
/* Warning - yellow/orange */
403429
.readme-content blockquote[data-callout='warning'] {
404-
border-left-color: #eab308;
430+
border-inline-start-color: #eab308;
405431
background: rgba(234, 179, 8, 0.05);
406432
}
407433
.readme-content blockquote[data-callout='warning']::before {
@@ -416,7 +442,7 @@ html.light .shiki span {
416442

417443
/* Caution - red */
418444
.readme-content blockquote[data-callout='caution'] {
419-
border-left-color: #ef4444;
445+
border-inline-start-color: #ef4444;
420446
background: rgba(239, 68, 68, 0.05);
421447
}
422448
.readme-content blockquote[data-callout='caution']::before {
@@ -443,7 +469,7 @@ html.light .shiki span {
443469
.readme-content td {
444470
border: 1px solid var(--border);
445471
padding: 0.75rem 1rem;
446-
text-align: left;
472+
text-align: start;
447473
}
448474

449475
.readme-content th {
@@ -499,6 +525,14 @@ input[type='search'] {
499525
appearance: none;
500526
}
501527

528+
@media screen and (max-width: 767px) {
529+
input,
530+
select,
531+
textarea {
532+
font-size: 16px !important;
533+
}
534+
}
535+
502536
input[type='search']::-webkit-search-decoration,
503537
input[type='search']::-webkit-search-cancel-button,
504538
input[type='search']::-webkit-search-results-button,

app/components/AccentColorPicker.vue

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22
import { useAccentColor } from '~/composables/useSettings'
33
44
const { accentColors, selectedAccentColor, setAccentColor } = useAccentColor()
5+
6+
onPrehydrate(el => {
7+
const settings = JSON.parse(localStorage.getItem('npmx-settings') || '{}')
8+
const id = settings.accentColorId
9+
if (id) {
10+
const input = el.querySelector<HTMLInputElement>(`input[value="${id}"]`)
11+
if (input) {
12+
input.checked = true
13+
}
14+
}
15+
})
516
</script>
617

718
<template>

app/components/AppTooltip.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ const isVisible = shallowRef(false)
1010
const tooltipId = useId()
1111
1212
const positionClasses: Record<string, string> = {
13-
top: 'bottom-full left-1/2 -translate-x-1/2 mb-1',
14-
bottom: 'top-full left-0 mt-1',
15-
left: 'right-full top-1/2 -translate-y-1/2 mr-2',
16-
right: 'left-full top-1/2 -translate-y-1/2 ml-2',
13+
top: 'bottom-full inset-is-1/2 -translate-x-1/2 mb-1',
14+
bottom: 'top-full inset-is-0 mt-1',
15+
left: 'inset-ie-full top-1/2 -translate-y-1/2 me-2',
16+
right: 'inset-is-full top-1/2 -translate-y-1/2 ms-2',
1717
}
1818
1919
const tooltipPosition = computed(() => positionClasses[props.position || 'bottom'])

app/components/ColumnPicker.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ function handleReset() {
9393
v-if="isOpen"
9494
ref="menuRef"
9595
:id="menuId"
96-
class="absolute right-0 mt-2 w-60 bg-bg-subtle border border-border rounded-lg shadow-lg z-20"
96+
class="absolute inset-ie-0 mt-2 w-60 bg-bg-subtle border border-border rounded-lg shadow-lg z-20"
9797
role="group"
9898
:aria-label="$t('filters.columns.show')"
9999
>
@@ -144,7 +144,7 @@ function handleReset() {
144144
<div class="border-t border-border py-1">
145145
<button
146146
type="button"
147-
class="w-full px-3 py-2 text-left text-sm font-mono text-fg-muted hover:bg-bg-muted hover:text-fg transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-inset"
147+
class="w-full px-3 py-2 text-start text-sm font-mono text-fg-muted hover:bg-bg-muted hover:text-fg transition-colors duration-200 focus-visible:ring-2 focus-visible:ring-fg focus-visible:ring-inset"
148148
@click="handleReset"
149149
>
150150
{{ $t('filters.columns.reset') }}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<script setup lang="ts">
2+
import type { JsrPackageInfo } from '#shared/types/jsr'
3+
import type { PackageManagerId } from '~/utils/install-command'
4+
5+
/**
6+
* A terminal-style execute command display for binary-only packages.
7+
* Renders all package manager variants with CSS-based visibility.
8+
*/
9+
10+
const props = defineProps<{
11+
packageName: string
12+
jsrInfo?: JsrPackageInfo | null
13+
isCreatePackage?: boolean
14+
}>()
15+
16+
const selectedPM = useSelectedPackageManager()
17+
18+
// Generate execute command parts for a specific package manager
19+
function getExecutePartsForPM(pmId: PackageManagerId) {
20+
return getExecuteCommandParts({
21+
packageName: props.packageName,
22+
packageManager: pmId,
23+
jsrInfo: props.jsrInfo,
24+
isBinaryOnly: true,
25+
isCreatePackage: props.isCreatePackage,
26+
})
27+
}
28+
29+
// Full execute command for copying (uses current selected PM)
30+
function getFullExecuteCommand() {
31+
return getExecuteCommand({
32+
packageName: props.packageName,
33+
packageManager: selectedPM.value,
34+
jsrInfo: props.jsrInfo,
35+
isBinaryOnly: true,
36+
isCreatePackage: props.isCreatePackage,
37+
})
38+
}
39+
40+
// Copy handler
41+
const { copied: executeCopied, copy: copyExecute } = useClipboard({ copiedDuring: 2000 })
42+
const copyExecuteCommand = () => copyExecute(getFullExecuteCommand())
43+
</script>
44+
45+
<template>
46+
<div class="relative group">
47+
<!-- Terminal-style execute command -->
48+
<div class="bg-bg-subtle border border-border rounded-lg overflow-hidden">
49+
<div class="flex gap-1.5 px-3 pt-2 sm:px-4 sm:pt-3">
50+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
51+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
52+
<span class="w-2.5 h-2.5 rounded-full bg-fg-subtle" />
53+
</div>
54+
<div class="px-3 pt-2 pb-3 sm:px-4 sm:pt-3 sm:pb-4 space-y-1">
55+
<!-- Execute command - render all PM variants, CSS controls visibility -->
56+
<div
57+
v-for="pm in packageManagers"
58+
:key="`execute-${pm.id}`"
59+
:data-pm-cmd="pm.id"
60+
class="flex items-center gap-2 group/executecmd"
61+
>
62+
<span class="text-fg-subtle font-mono text-sm select-none">$</span>
63+
<code class="font-mono text-sm"
64+
><span
65+
v-for="(part, i) in getExecutePartsForPM(pm.id)"
66+
:key="i"
67+
:class="i === 0 ? 'text-fg' : 'text-fg-muted'"
68+
>{{ i > 0 ? ' ' : '' }}{{ part }}</span
69+
></code
70+
>
71+
<button
72+
type="button"
73+
class="px-2 py-0.5 font-mono text-xs text-fg-muted bg-bg-subtle/80 border border-border rounded transition-colors duration-200 opacity-0 group-hover/executecmd:opacity-100 hover:(text-fg border-border-hover) active:scale-95 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-fg/50"
74+
:aria-label="$t('package.get_started.copy_command')"
75+
@click.stop="copyExecuteCommand"
76+
>
77+
{{ executeCopied ? $t('common.copied') : $t('common.copy') }}
78+
</button>
79+
</div>
80+
</div>
81+
</div>
82+
</div>
83+
</template>
84+
85+
<style>
86+
/* Hide all variants by default when preference is set */
87+
:root[data-pm] [data-pm-cmd] {
88+
display: none;
89+
}
90+
91+
/* Show only the matching package manager command */
92+
:root[data-pm='npm'] [data-pm-cmd='npm'],
93+
:root[data-pm='pnpm'] [data-pm-cmd='pnpm'],
94+
:root[data-pm='yarn'] [data-pm-cmd='yarn'],
95+
:root[data-pm='bun'] [data-pm-cmd='bun'],
96+
:root[data-pm='deno'] [data-pm-cmd='deno'],
97+
:root[data-pm='vlt'] [data-pm-cmd='vlt'] {
98+
display: flex;
99+
}
100+
101+
/* Fallback: when no data-pm is set (SSR initial), show npm as default */
102+
:root:not([data-pm]) [data-pm-cmd]:not([data-pm-cmd='npm']) {
103+
display: none;
104+
}
105+
</style>

0 commit comments

Comments
 (0)