-
Notifications
You must be signed in to change notification settings - Fork 85
feat: add new Nuxt module for guidance banner #2424
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 11 commits
85dd874
a71b51d
64be640
fcc63c9
214cc24
ccffd72
151527b
d9801bc
f169c88
711e09e
16a5848
29a57e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| root = true | ||
|
|
||
| [*] | ||
| indent_size = 2 | ||
| indent_style = space | ||
| end_of_line = lf | ||
| charset = utf-8 | ||
| trim_trailing_whitespace = true | ||
| insert_final_newline = true | ||
|
|
||
| [*.md] | ||
| trim_trailing_whitespace = false | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| # Dependencies | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this also is probably too broad |
||
| node_modules | ||
|
|
||
| # Logs | ||
| *.log* | ||
|
|
||
| # Temp directories | ||
| .temp | ||
| .tmp | ||
| .cache | ||
|
|
||
| # Yarn | ||
| **/.yarn/cache | ||
| **/.yarn/*state* | ||
|
|
||
| # Generated dirs | ||
| dist | ||
|
|
||
| # Nuxt | ||
| .nuxt | ||
| .output | ||
| .data | ||
| .vercel_build_output | ||
| .build-* | ||
| .netlify | ||
|
|
||
| # Env | ||
| .env | ||
|
|
||
| # Testing | ||
| reports | ||
| coverage | ||
| *.lcov | ||
| .nyc_output | ||
|
|
||
| # VSCode | ||
| .vscode/* | ||
| !.vscode/settings.json | ||
| !.vscode/tasks.json | ||
| !.vscode/launch.json | ||
| !.vscode/extensions.json | ||
| !.vscode/*.code-snippets | ||
|
|
||
| # Intellij idea | ||
| *.iml | ||
| .idea | ||
|
|
||
| # OSX | ||
| .DS_Store | ||
| .AppleDouble | ||
| .LSOverride | ||
| .AppleDB | ||
| .AppleDesktop | ||
| Network Trash Folder | ||
| Temporary Items | ||
| .apdisk | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| // @ts-check | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for new package let's use oxlint right from the beginning, we'll migrate either way at some point so better now with new package |
||
| import { createConfigForNuxt } from "@nuxt/eslint-config/flat"; | ||
|
|
||
| // Run `npx @eslint/config-inspector` to inspect the resolved config interactively | ||
| export default createConfigForNuxt({ | ||
| features: { | ||
| // Rules for module authors | ||
| tooling: true, | ||
| // Rules for formatting | ||
| stylistic: true, | ||
| }, | ||
| dirs: { | ||
| src: ["./src"], | ||
| }, | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| { | ||
| "name": "@shopware/guidance-banner", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd suggest something like |
||
| "version": "1.0.0", | ||
| "description": "Shopware Guidance Banner Nuxt module", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/shopware/frontends.git" | ||
| }, | ||
| "license": "MIT", | ||
| "type": "module", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/types.d.mts", | ||
| "import": "./dist/module.mjs" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we need build step for nuxt module? Maybe just shipping raw TS would be enough? |
||
| } | ||
| }, | ||
| "main": "./dist/module.mjs", | ||
| "typesVersions": { | ||
| "*": { | ||
| ".": [ | ||
| "./dist/types.d.mts" | ||
| ] | ||
| } | ||
| }, | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "scripts": { | ||
| "prepack": "nuxt-module-build build", | ||
| "dev": "npm run dev:prepare && nuxt dev playground", | ||
| "dev:build": "nuxt build playground", | ||
| "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground", | ||
| "lint": "eslint src --ext .ts,.vue", | ||
| "lint:fix": "eslint src --ext .ts,.vue --fix", | ||
|
mdanilowicz marked this conversation as resolved.
|
||
| "release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we definetly do not release like this |
||
| }, | ||
| "dependencies": { | ||
| "@nuxt/kit": "4.4.2" | ||
| }, | ||
| "devDependencies": { | ||
| "@nuxt/devtools": "3.2.4", | ||
| "@nuxt/eslint-config": "1.15.2", | ||
| "@nuxt/module-builder": "1.0.2", | ||
| "@nuxt/schema": "4.4.2", | ||
| "@nuxt/test-utils": "4.0.2", | ||
| "@types/node": "22.13.14", | ||
| "changelogen": "0.6.2", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. review dependencies, probably many not needed like this one |
||
| "nuxt": "4.4.2", | ||
| "typescript": "5.9.3", | ||
| "vitest": "4.1.5", | ||
| "vue-tsc": "3.2.7" | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { | ||
| defineNuxtModule, | ||
| addPlugin, | ||
| createResolver, | ||
| } from '@nuxt/kit' | ||
|
|
||
| export type ModuleOptions = Record<string, never> | ||
|
|
||
| export default defineNuxtModule<ModuleOptions>({ | ||
| meta: { | ||
| name: 'shopware-guidance-banner', | ||
| configKey: 'shopwareGuidanceBanner', | ||
| }, | ||
| defaults: {}, | ||
| setup(_options, nuxt) { | ||
| if (!nuxt.options.dev) { | ||
| return | ||
| } | ||
|
|
||
| const resolver = createResolver(import.meta.url) | ||
|
|
||
| addPlugin(resolver.resolve('./runtime/plugin.client')) | ||
| }, | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,182 @@ | ||
| <script setup lang="ts"> | ||
| import { | ||
| computed, | ||
| nextTick, | ||
| onBeforeUnmount, | ||
| onMounted, | ||
| ref, | ||
| watch, | ||
| } from 'vue' | ||
|
|
||
| const storageKey = 'shopware-guidance-banner-dismissed' | ||
|
|
||
| const isMounted = ref(false) | ||
| const isDismissed = ref(false) | ||
| const bannerElement = ref<HTMLElement | null>(null) | ||
|
|
||
| const links = [ | ||
| { | ||
| label: 'View code', | ||
| href: 'https://github.com/shopware/frontends/tree/main/templates/vue-starter-template', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is that package only for this template? |
||
| variant: 'primary', | ||
| type: 'link', | ||
| }, | ||
| { | ||
| label: 'Docs', | ||
| href: 'https://frontends.shopware.com/', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. or is it |
||
| variant: 'secondary', | ||
| type: 'link', | ||
| }, | ||
| ] | ||
|
mdanilowicz marked this conversation as resolved.
|
||
|
|
||
| const isVisible = computed(() => isMounted.value && !isDismissed.value) | ||
|
|
||
| onMounted(() => { | ||
| isMounted.value = true | ||
| isDismissed.value = localStorage.getItem(storageKey) === 'true' | ||
| if (isDismissed.value) { | ||
| return | ||
| } | ||
| window.addEventListener('resize', syncBodyPadding) | ||
| void nextTick(syncBodyPadding) | ||
| }) | ||
|
mdanilowicz marked this conversation as resolved.
|
||
|
|
||
| onBeforeUnmount(() => { | ||
| window.removeEventListener('resize', syncBodyPadding) | ||
| document.body.style.paddingBottom = '' | ||
| }) | ||
|
|
||
| watch(isVisible, async () => { | ||
| await nextTick() | ||
| syncBodyPadding() | ||
| }) | ||
|
|
||
| function closeBanner() { | ||
| isDismissed.value = true | ||
| localStorage.setItem(storageKey, 'true') | ||
| } | ||
|
|
||
| function syncBodyPadding() { | ||
| if (!isVisible.value || !bannerElement.value) { | ||
| document.body.style.paddingBottom = '' | ||
| return | ||
| } | ||
|
|
||
| document.body.style.paddingBottom = `${bannerElement.value.offsetHeight + 12}px` | ||
| } | ||
|
|
||
| function isExternalLink(href: string) { | ||
| return href.startsWith('http') | ||
| } | ||
|
|
||
| function getLinkTarget(link: (typeof links)[number]) { | ||
| if (link.type !== 'link' || !link.href) { | ||
| return undefined | ||
| } | ||
|
|
||
| return isExternalLink(link.href) ? '_blank' : undefined | ||
| } | ||
|
|
||
| function getLinkRel(link: (typeof links)[number]) { | ||
| if (link.type !== 'link' || !link.href) { | ||
| return undefined | ||
| } | ||
|
|
||
| return isExternalLink(link.href) ? 'noopener noreferrer' : undefined | ||
| } | ||
| </script> | ||
|
|
||
| <template> | ||
| <aside | ||
| v-if="isVisible" | ||
| ref="bannerElement" | ||
| class="fixed inset-x-0 bottom-0 z-50 px-3 pb-3 sm:px-6 sm:pb-6" | ||
| > | ||
| <div class="mx-auto max-w-6xl"> | ||
| <div | ||
| class="relative overflow-hidden rounded-[28px] border border-white/70 bg-[linear-gradient(135deg,rgba(255,255,255,0.96)_0%,rgba(244,247,251,0.98)_45%,rgba(236,246,255,0.98)_100%)] shadow-[0_24px_80px_rgba(15,23,42,0.18)] ring-1 ring-slate-200/70 backdrop-blur-xl" | ||
| > | ||
| <div | ||
| aria-hidden="true" | ||
| class="pointer-events-none absolute -right-16 top-0 h-36 w-36 rounded-full bg-sky-200/40 blur-3xl" | ||
| /> | ||
| <div | ||
| aria-hidden="true" | ||
| class="pointer-events-none absolute -left-8 bottom-0 h-28 w-28 rounded-full bg-amber-200/50 blur-3xl" | ||
| /> | ||
|
|
||
| <div | ||
| class="relative flex flex-col gap-5 px-5 py-5 sm:px-6 sm:py-6 lg:flex-row lg:items-center lg:justify-between lg:gap-8" | ||
| > | ||
| <div class="max-w-2xl"> | ||
| <div | ||
| class="inline-flex items-center gap-2 rounded-full border border-sky-200/80 bg-white/75 px-3 py-1 text-[11px] font-semibold uppercase tracking-[0.22em] text-sky-700" | ||
| > | ||
| <span class="h-2 w-2 rounded-full bg-emerald-500" /> | ||
| Guided setup | ||
| </div> | ||
|
|
||
| <h2 class="mt-3 text-lg font-semibold tracking-tight text-slate-950 sm:text-xl"> | ||
| Your Frontends project is ready | ||
| </h2> | ||
| <p class="mt-2 max-w-xl text-sm leading-6 text-slate-600 sm:text-[15px]"> | ||
| Explore the starter, inspect the codebase, open the docs, or jump | ||
| straight into DevTools to continue building. | ||
| </p> | ||
| <p | ||
| class="mt-3 inline-flex flex-wrap items-center gap-2 rounded-full border border-slate-200/80 bg-white/70 px-3 py-1.5 text-xs font-medium text-slate-500" | ||
| > | ||
| <span>Press</span> | ||
| <kbd | ||
| class="rounded-md border border-slate-200 bg-white px-2 py-1 text-[11px] font-semibold text-slate-700 shadow-sm" | ||
| > | ||
| F12 | ||
| </kbd> | ||
| <span>or</span> | ||
| <kbd | ||
| class="rounded-md border border-slate-200 bg-white px-2 py-1 text-[11px] font-semibold text-slate-700 shadow-sm" | ||
| > | ||
| ⌘⌥I | ||
| </kbd> | ||
| <span>to open DevTools</span> | ||
| </p> | ||
| </div> | ||
|
|
||
| <div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between"> | ||
| <div class="flex flex-wrap gap-2.5"> | ||
| <component | ||
| :is="link.type === 'action' ? 'button' : 'a'" | ||
| v-for="link in links" | ||
| :key="link.label" | ||
| :href="link.type === 'link' ? link.href : undefined" | ||
| :type="link.type === 'action' ? 'button' : undefined" | ||
| class="inline-flex min-h-11 items-center justify-center rounded-full px-4 py-2.5 text-sm font-semibold transition duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 focus-visible:ring-offset-2" | ||
| :class=" | ||
| link.variant === 'primary' | ||
| ? 'bg-slate-950 text-white shadow-[0_10px_30px_rgba(15,23,42,0.22)] hover:bg-slate-800' | ||
| : 'border border-slate-200 bg-white/80 text-slate-700 hover:border-slate-300 hover:bg-white hover:text-slate-950' | ||
| " | ||
| :target="getLinkTarget(link)" | ||
| :rel="getLinkRel(link)" | ||
| > | ||
| {{ link.label }} | ||
| </component> | ||
| </div> | ||
|
|
||
| <button | ||
| type="button" | ||
| class="inline-flex h-11 w-11 shrink-0 items-center justify-center self-end rounded-full border border-slate-200/90 bg-white/80 text-slate-500 transition hover:border-slate-300 hover:bg-white hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-sky-500 focus-visible:ring-offset-2 sm:self-start" | ||
| aria-label="Close banner" | ||
| @click="closeBanner" | ||
| > | ||
| <span | ||
| aria-hidden="true" | ||
| class="text-xl leading-none" | ||
| >×</span> | ||
| </button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </aside> | ||
| </template> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { createApp, h } from 'vue' | ||
| import { defineNuxtPlugin } from '#app' | ||
| import ShopwareBanner from './components/ShopwareBanner.vue' | ||
|
|
||
| export default defineNuxtPlugin(() => { | ||
| const containerId = 'shopware-guidance-banner-root' | ||
|
|
||
| if (document.getElementById(containerId)) { | ||
| return | ||
| } | ||
|
|
||
| const container = document.createElement('div') | ||
| container.id = containerId | ||
| document.body.appendChild(container) | ||
|
|
||
| const app = createApp({ | ||
| render: () => h(ShopwareBanner), | ||
| }) | ||
|
|
||
| app.mount(container) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "extends": "./.nuxt/tsconfig.json", | ||
|
mdanilowicz marked this conversation as resolved.
|
||
| "exclude": [ | ||
| "dist", | ||
| "node_modules", | ||
| "playground", | ||
| ] | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should not have dedicaated editorconfig file for this package