Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions packages/shopware-guidance-banner/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true
Copy link
Copy Markdown
Contributor

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


[*]
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
56 changes: 56 additions & 0 deletions packages/shopware-guidance-banner/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Dependencies
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
15 changes: 15 additions & 0 deletions packages/shopware-guidance-banner/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// @ts-check
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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"],
},
});
53 changes: 53 additions & 0 deletions packages/shopware-guidance-banner/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@shopware/guidance-banner",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest something like @shopware/nuxt-guidance-banner as it's nuxt-specific, also directory change from shopware-guidance-banner to guidance-banner

"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"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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",
Comment thread
mdanilowicz marked this conversation as resolved.
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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"
}
}
24 changes: 24 additions & 0 deletions packages/shopware-guidance-banner/src/module.ts
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',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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/',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or is it https://developer.shopware.com/frontends/

variant: 'secondary',
type: 'link',
},
]
Comment thread
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)
})
Comment thread
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"
>&times;</span>
</button>
</div>
</div>
</div>
</div>
</aside>
</template>
21 changes: 21 additions & 0 deletions packages/shopware-guidance-banner/src/runtime/plugin.client.ts
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)
})
8 changes: 8 additions & 0 deletions packages/shopware-guidance-banner/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "./.nuxt/tsconfig.json",
Comment thread
mdanilowicz marked this conversation as resolved.
"exclude": [
"dist",
"node_modules",
"playground",
]
}
Loading