Skip to content

Commit 56fa96e

Browse files
authored
Merge branch 'main' into serhalp/fix-version-sorting
2 parents 2acaf31 + 3d0ae04 commit 56fa96e

110 files changed

Lines changed: 6922 additions & 943 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.

.storybook/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { StorybookConfig } from '@storybook-vue/nuxt'
22

33
const config = {
44
stories: ['../app/**/*.stories.@(js|ts)'],
5-
addons: ['@storybook/addon-a11y', '@storybook/addon-docs'],
5+
addons: ['@storybook/addon-a11y', '@storybook/addon-docs', '@storybook/addon-themes'],
66
framework: '@storybook-vue/nuxt',
77
features: {
88
backgrounds: false,

.storybook/preview.ts

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Preview } from '@storybook-vue/nuxt'
2+
import { withThemeByDataAttribute } from '@storybook/addon-themes'
23
import { currentLocales } from '../config/i18n'
34
import { fn } from 'storybook/test'
45
import { ACCENT_COLORS } from '../shared/utils/constants'
@@ -58,31 +59,22 @@ const preview: Preview = {
5859
],
5960
},
6061
},
61-
theme: {
62-
name: 'Theme',
63-
description: 'Color mode',
64-
defaultValue: 'dark',
65-
toolbar: {
66-
icon: 'moon',
67-
dynamicTitle: true,
68-
items: [
69-
{ value: 'light', icon: 'sun', title: 'Light' },
70-
{ value: 'dark', icon: 'moon', title: 'Dark' },
71-
],
72-
},
73-
},
7462
},
7563
decorators: [
64+
withThemeByDataAttribute({
65+
themes: {
66+
Light: 'light',
67+
Dark: 'dark',
68+
},
69+
defaultTheme: 'Dark',
70+
attributeName: 'data-theme',
71+
}),
7672
(story, context) => {
77-
const { locale, theme, accentColor } = context.globals as {
73+
const { locale, accentColor } = context.globals as {
7874
locale: string
79-
theme: string
8075
accentColor?: string
8176
}
8277

83-
// Set theme from globals
84-
document.documentElement.setAttribute('data-theme', theme)
85-
8678
// Set accent color from globals
8779
if (accentColor) {
8880
document.documentElement.style.setProperty('--accent-color', `var(--swatch-${accentColor})`)

CONTRIBUTING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,11 @@ shared/ # Shared between app and server
179179
└── types/ # TypeScript type definitions
180180
181181
cli/ # Local connector CLI (separate workspace)
182+
182183
test/ # Vitest tests
183184
├── unit/ # Unit tests (*.spec.ts)
184-
── nuxt/ # Nuxt component tests
185-
tests/ # Playwright E2E tests
185+
── nuxt/ # Nuxt component tests
186+
└── e2e/ # Playwright E2E tests
186187
```
187188

188189
> [!TIP]

app/app.vue

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ if (import.meta.client) {
129129

130130
<AppHeader :show-logo="!isHomepage" />
131131

132+
<NuxtRouteAnnouncer v-slot="{ message }">
133+
{{ route.name === 'search' ? `${$t('search.title_packages')} - npmx` : message }}
134+
</NuxtRouteAnnouncer>
135+
132136
<div id="main-content" class="flex-1 flex flex-col" tabindex="-1">
133137
<NuxtPage />
134138
</div>

app/components/AppHeader.vue

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ const mobileLinks = computed<NavigationConfigWithGroups>(() => [
127127
128128
const showFullSearch = shallowRef(false)
129129
const showMobileMenu = shallowRef(false)
130-
const { env } = useAppConfig().buildInfo
130+
const { env, prNumber } = useAppConfig().buildInfo
131131
132132
// On mobile, clicking logo+search button expands search
133133
const route = useRoute()
@@ -227,6 +227,15 @@ onKeyStroke(
227227
{{ env === 'release' ? 'alpha' : env }}
228228
</span>
229229
</NuxtLink>
230+
<NuxtLink
231+
v-if="prNumber"
232+
:to="`https://github.com/npmx-dev/npmx.dev/pull/${prNumber}`"
233+
:aria-label="`Open GitHub pull request ${prNumber}`"
234+
>
235+
<span class="text-xs px-1.5 py-0.5 rounded badge-green font-sans font-medium ms-2">
236+
PR #{{ prNumber }}
237+
</span>
238+
</NuxtLink>
230239
</div>
231240
<!-- Spacer when logo is hidden on desktop -->
232241
<span v-else class="hidden sm:block w-1" />

app/components/Code/FileTree.vue

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,7 @@ watch(
9494
block
9595
:style="{ paddingLeft: `${depth * 12 + 32}px` }"
9696
>
97-
<svg
98-
class="size-[1em] me-1 shrink-0"
99-
viewBox="0 0 16 16"
100-
fill="currentColor"
101-
aria-hidden="true"
102-
>
97+
<svg class="size-[1em] me-1 shrink-0" viewBox="0 0 16 16" aria-hidden="true">
10398
<use :href="`/file-tree-sprite.svg#${getFileIcon(node.name)}`" />
10499
</svg>
105100
<span class="truncate">{{ node.name }}</span>

app/components/CollapsibleSection.vue

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { LinkBase } from '#components'
44
55
interface Props {
66
title: string
7+
subtitle?: string
78
isLoading?: boolean
89
headingLevel?: `h${number}`
910
id: string
@@ -81,7 +82,8 @@ useHead({
8182
<div class="flex items-center justify-between mb-3 px-1">
8283
<component
8384
:is="headingLevel"
84-
class="group text-xs text-fg-subtle uppercase tracking-wider flex items-center gap-2"
85+
class="group text-xs text-fg-subtle uppercase tracking-wider flex gap-2"
86+
:class="subtitle ? 'items-start' : 'items-center'"
8587
>
8688
<button
8789
:id="buttonId"
@@ -101,9 +103,14 @@ useHead({
101103
/>
102104
</button>
103105

104-
<LinkBase :to="`#${id}`">
105-
{{ title }}
106-
</LinkBase>
106+
<span>
107+
<LinkBase :to="`#${id}`">
108+
{{ title }}
109+
</LinkBase>
110+
<span v-if="subtitle" class="block text-2xs normal-case tracking-normal">{{
111+
subtitle
112+
}}</span>
113+
</span>
107114
</component>
108115

109116
<!-- Actions slot for buttons or other elements -->

app/components/Header/AuthModal.client.vue

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { useAtproto } from '~/composables/atproto/useAtproto'
33
import { authRedirect } from '~/utils/atproto/helpers'
44
import { isAtIdentifierString } from '@atproto/lex'
55
6+
const authModal = useModal('auth-modal')
7+
68
const handleInput = shallowRef('')
79
const errorMessage = shallowRef('')
810
const route = useRoute()
@@ -72,9 +74,22 @@ watch(user, async newUser => {
7274
</p>
7375
</div>
7476
</div>
75-
<ButtonBase class="w-full" @click="logout">
76-
{{ $t('auth.modal.disconnect') }}
77-
</ButtonBase>
77+
78+
<div class="flex flex-col space-y-4">
79+
<LinkBase
80+
variant="button-secondary"
81+
:to="{ name: 'profile-handle', params: { handle: user.handle } }"
82+
prefetch-on="interaction"
83+
class="w-full"
84+
@click="authModal.close()"
85+
>
86+
{{ $t('auth.modal.profile') }}
87+
</LinkBase>
88+
89+
<ButtonBase class="w-full" @click="logout">
90+
{{ $t('auth.modal.disconnect') }}
91+
</ButtonBase>
92+
</div>
7893
</div>
7994

8095
<!-- Disconnected state -->

app/components/Link/Base.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ const props = withDefaults(
1010
* `type` should never be used, because this will always be a link.
1111
* */
1212
type?: never
13+
/** Visual style of the link */
1314
variant?: 'button-primary' | 'button-secondary' | 'link'
15+
/** Size (only applicable for button variants) */
1416
size?: 'small' | 'medium'
17+
/** Makes the link take full width */
1518
block?: boolean
1619
20+
/** Keyboard shortcut hint */
1721
ariaKeyshortcuts?: string
1822
1923
/**
@@ -26,8 +30,10 @@ const props = withDefaults(
2630
*/
2731
rel?: never
2832
33+
/** Icon class to display */
2934
classicon?: IconClass
3035
36+
/** Link destination (internal or external URL) */
3137
to?: NuxtLinkProps['to']
3238
3339
/** always use `to` instead of `href` */
@@ -37,6 +43,7 @@ const props = withDefaults(
3743
noUnderline?: boolean
3844
3945
/**
46+
* Hide external link icon (deprecated)
4047
* @deprecated @todo remove this property and add separate clean component without this logic
4148
*/
4249
noNewTabIcon?: boolean
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import type { Meta, StoryObj } from '@storybook-vue/nuxt'
2+
import LinkBase from './Base.vue'
3+
4+
const meta = {
5+
component: LinkBase,
6+
args: {
7+
to: '/',
8+
default: 'Click me',
9+
},
10+
} satisfies Meta<typeof LinkBase>
11+
12+
export default meta
13+
type Story = StoryObj<typeof meta>
14+
15+
export const Default: Story = {}
16+
17+
export const ExternalLink: Story = {
18+
args: {
19+
to: 'https://example.com',
20+
default: 'External Link',
21+
},
22+
}
23+
24+
export const AnchorLink: Story = {
25+
args: {
26+
to: '#section',
27+
default: 'Anchor Link',
28+
},
29+
}
30+
31+
export const WithIcon: Story = {
32+
args: {
33+
classicon: 'i-lucide:check',
34+
default: 'Verified',
35+
},
36+
}
37+
38+
export const NoUnderline: Story = {
39+
args: {
40+
noUnderline: true,
41+
default: 'Link without underline',
42+
},
43+
}
44+
45+
export const Disabled: Story = {
46+
args: {
47+
disabled: true,
48+
default: 'Disabled Link',
49+
},
50+
}
51+
52+
export const ButtonPrimary: Story = {
53+
args: {
54+
variant: 'button-primary',
55+
default: 'Primary Button',
56+
},
57+
}
58+
59+
export const ButtonSecondary: Story = {
60+
args: {
61+
variant: 'button-secondary',
62+
default: 'Secondary Button',
63+
},
64+
}
65+
66+
export const SmallButton: Story = {
67+
args: {
68+
variant: 'button-primary',
69+
size: 'small',
70+
default: 'Small Button',
71+
},
72+
}
73+
74+
export const WithIconButton: Story = {
75+
args: {
76+
variant: 'button-primary',
77+
classicon: 'i-lucide:copy',
78+
default: 'Copy',
79+
},
80+
}
81+
82+
export const WithKeyboardShortcut: Story = {
83+
args: {
84+
variant: 'button-secondary',
85+
ariaKeyshortcuts: 's',
86+
default: 'Search',
87+
},
88+
}
89+
90+
export const BlockLink: Story = {
91+
args: {
92+
variant: 'button-primary',
93+
block: true,
94+
default: 'Full Width Button',
95+
},
96+
}
97+
98+
export const DisabledButton: Story = {
99+
args: {
100+
variant: 'button-primary',
101+
disabled: true,
102+
default: 'Disabled Button',
103+
},
104+
}
105+
106+
export const Snapshot: Story = {
107+
render: () => ({
108+
template: `
109+
<div style="display: flex; flex-direction: column; gap: 1rem; padding: 1rem;">
110+
<LinkBase to="/">Default Link</LinkBase>
111+
<LinkBase to="https://example.com">External Link</LinkBase>
112+
<LinkBase to="#section">Anchor Link</LinkBase>
113+
<LinkBase to="/" classicon="i-lucide:check">Link with icon</LinkBase>
114+
<LinkBase to="/" no-underline>Link without underline</LinkBase>
115+
<LinkBase to="/" disabled>Disabled Link</LinkBase>
116+
117+
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
118+
<LinkBase to="/" variant="button-primary">Primary</LinkBase>
119+
<LinkBase to="/" variant="button-secondary">Secondary</LinkBase>
120+
<LinkBase to="/" variant="button-primary" disabled>Disabled</LinkBase>
121+
<LinkBase to="/" variant="button-primary" classicon="i-lucide:copy">With Icon</LinkBase>
122+
</div>
123+
124+
<div style="display: flex; gap: 1rem;">
125+
<LinkBase to="/" variant="button-primary" size="small">Small Button</LinkBase>
126+
</div>
127+
<LinkBase to="/" variant="button-primary" block>Full Width Button</LinkBase>
128+
</div>
129+
`,
130+
components: { LinkBase },
131+
}),
132+
}

0 commit comments

Comments
 (0)