-
-
Notifications
You must be signed in to change notification settings - Fork 365
feat: add brand page #2197
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
Open
Adebesin-Cell
wants to merge
35
commits into
npmx-dev:main
Choose a base branch
from
Adebesin-Cell:feat/brand-page
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+963
−17
Open
feat: add brand page #2197
Changes from all commits
Commits
Show all changes
35 commits
Select commit
Hold shift + click to select a range
9fde292
feat: add brand page with logos, colors, typography, and usage guidel…
Adebesin-Cell eeef5e5
[autofix.ci] apply automated fixes
autofix-ci[bot] fb287bf
fix: prevent LogoContextMenu wrapper from breaking mobile nav layout
Adebesin-Cell c58e7e7
fix: resolve failing unit and i18n tests
Adebesin-Cell c956570
[autofix.ci] apply automated fixes
autofix-ci[bot] b4b5630
fix: regenerate i18n schema for brand page keys
Adebesin-Cell 32900dc
fix: address CodeRabbit review comments
Adebesin-Cell 284f70a
fix: replace remaining bare buttons with ButtonBase in Brand/Customize
Adebesin-Cell 35f9f07
fix: await toBlob before clearing loading state in custom PNG download
Adebesin-Cell 3639ee2
[autofix.ci] apply automated fixes
autofix-ci[bot] f22cec2
refactor: simplify brand page — remove colors, app icon, and strict g…
Adebesin-Cell ba79696
refactor: per-variant downloads, better spacing, friendlier guidelines
Adebesin-Cell 33410bf
[autofix.ci] apply automated fixes
autofix-ci[bot] 508ecca
fix: align logo cards with flex-col and consistent min-height
Adebesin-Cell 9edd0ee
fix: increase spacing between logo cards
Adebesin-Cell e5ab1bb
fix: use space-y-16 for logo card spacing
Adebesin-Cell 8c71173
fix: use mt-12 on figures instead of space-y for logo spacing
Adebesin-Cell 8cf0210
fix: replace space-y-16 with flex col gap-16 for section spacing
Adebesin-Cell ab9d274
fix: reduce context menu size — remove min-w, use sm buttons
Adebesin-Cell a848281
fix: remove w-full from context menu buttons to shrink width
Adebesin-Cell 22c3bb1
fix: add flex-col to context menu so items stack vertically
Adebesin-Cell 7f07fcb
fix: use contrast-appropriate accent colors based on preview background
Adebesin-Cell f4c6916
fix: sync color picker swatches with preview background palette
Adebesin-Cell 78df9f3
[autofix.ci] apply automated fixes
autofix-ci[bot] d1cf5be
fix: delay context menu close after copy so confirmation is readable
Adebesin-Cell d63a73b
style: Update setTimeout duration in copySvg function
Adebesin-Cell 7abe2cd
Merge branch 'main' into feat/brand-page
Adebesin-Cell 07e9928
chore: use generics
ghostdevv a97cf16
chore: ignore some lint issues
ghostdevv c3671ab
refactor: create downloadFile and downloadFileLink utils
ghostdevv 7c04e99
refactor: remove composable
ghostdevv 540ed93
fix: address PR review feedback for brand page
Adebesin-Cell 7d1025a
[autofix.ci] apply automated fixes
autofix-ci[bot] 1e0788f
fix: use correct light-mode colors for logo mark and add PNG spinners
Adebesin-Cell dc799b9
[autofix.ci] apply automated fixes
autofix-ci[bot] File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,228 @@ | ||
| <script setup lang="ts"> | ||
| import { ACCENT_COLORS, type AccentColorId } from '#shared/utils/constants' | ||
|
|
||
| const { selectedAccentColor } = useAccentColor() | ||
| const { t } = useI18n() | ||
|
|
||
| const customAccent = ref<string | null>(null) | ||
| const customBgDark = ref(true) | ||
| const customLogoRef = useTemplateRef('customLogoRef') | ||
|
|
||
| const activeAccentId = computed(() => customAccent.value ?? selectedAccentColor.value ?? 'sky') | ||
|
|
||
| // Use the palette matching the preview background, not the site theme | ||
| const previewPalette = computed(() => | ||
| customBgDark.value ? ACCENT_COLORS.dark : ACCENT_COLORS.light, | ||
| ) | ||
|
|
||
| const activeAccentColor = computed(() => { | ||
| return previewPalette.value[activeAccentId.value as AccentColorId] ?? previewPalette.value.sky | ||
| }) | ||
|
|
||
| const accentColorLabels = computed<Record<AccentColorId, string>>(() => ({ | ||
| sky: t('settings.accent_colors.sky'), | ||
| coral: t('settings.accent_colors.coral'), | ||
| amber: t('settings.accent_colors.amber'), | ||
| emerald: t('settings.accent_colors.emerald'), | ||
| violet: t('settings.accent_colors.violet'), | ||
| magenta: t('settings.accent_colors.magenta'), | ||
| neutral: t('settings.clear_accent'), | ||
| })) | ||
|
|
||
| // Color swatches match the preview background palette so the circles reflect what the logo will render | ||
| const pickerColors = computed(() => | ||
| Object.entries(previewPalette.value).map(([id, value]) => ({ | ||
| id: id as AccentColorId, | ||
| label: accentColorLabels.value[id as AccentColorId], | ||
| value, | ||
| })), | ||
| ) | ||
|
|
||
| function getCustomSvgString(): string { | ||
| const el = customLogoRef.value?.$el as SVGElement | undefined | ||
| if (!el) return '' | ||
| const clone = el.cloneNode(true) as SVGElement | ||
| clone.querySelectorAll<SVGElement>('[fill="currentColor"]').forEach(path => { | ||
| path.setAttribute('fill', customBgDark.value ? '#fafafa' : '#0a0a0a') | ||
| }) | ||
| clone.querySelectorAll<SVGElement>('[fill="var(--accent)"]').forEach(path => { | ||
| const style = getComputedStyle(path as SVGElement) | ||
| path.setAttribute('fill', style.fill || activeAccentColor.value) | ||
| }) | ||
| clone.removeAttribute('aria-hidden') | ||
| clone.removeAttribute('class') | ||
| return new XMLSerializer().serializeToString(clone) | ||
| } | ||
|
|
||
| function downloadCustomSvg() { | ||
| const svg = getCustomSvgString() | ||
| if (!svg) return | ||
|
|
||
| const blob = new Blob([svg], { type: 'image/svg+xml' }) | ||
| downloadFile(blob, `npmx-logo-${activeAccentId.value}.svg`) | ||
| } | ||
|
|
||
| const pngLoading = ref(false) | ||
|
|
||
| async function downloadCustomPng() { | ||
| const svg = getCustomSvgString() | ||
| if (!svg) return | ||
| pngLoading.value = true | ||
|
|
||
| const blob = new Blob([svg], { type: 'image/svg+xml' }) | ||
| const url = URL.createObjectURL(blob) | ||
|
|
||
| try { | ||
| await document.fonts.ready | ||
|
|
||
| const img = new Image() | ||
| const loaded = new Promise<void>((resolve, reject) => { | ||
| // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener) | ||
| img.onload = () => resolve() | ||
| // oxlint-disable-next-line eslint-plugin-unicorn(prefer-add-event-listener) | ||
| img.onerror = () => reject(new Error('Failed to load custom SVG')) | ||
| }) | ||
| img.src = url | ||
| await loaded | ||
|
|
||
| const scale = 2 | ||
| const canvas = document.createElement('canvas') | ||
| canvas.width = 602 * scale | ||
| canvas.height = 170 * scale | ||
| const ctx = canvas.getContext('2d')! | ||
ghostdevv marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ctx.fillStyle = customBgDark.value ? '#0a0a0a' : '#ffffff' | ||
| ctx.fillRect(0, 0, canvas.width, canvas.height) | ||
| ctx.scale(scale, scale) | ||
| ctx.drawImage(img, 0, 0, 602, 170) | ||
|
|
||
| await new Promise<void>(resolve => { | ||
| canvas.toBlob(pngBlob => { | ||
| if (pngBlob) downloadFile(pngBlob, `npmx-logo-${activeAccentId.value}.png`) | ||
| resolve() | ||
| }, 'image/png') | ||
| }) | ||
| } finally { | ||
| URL.revokeObjectURL(url) | ||
| pngLoading.value = false | ||
| } | ||
| } | ||
| </script> | ||
|
|
||
| <template> | ||
| <section aria-labelledby="brand-customize-heading"> | ||
| <h2 id="brand-customize-heading" class="text-lg text-fg uppercase tracking-wider mb-4"> | ||
| {{ $t('brand.customize.title') }} | ||
| </h2> | ||
| <p class="text-fg-muted leading-relaxed mb-8"> | ||
| {{ $t('brand.customize.description') }} | ||
| </p> | ||
|
|
||
| <div class="border border-border rounded-lg overflow-hidden"> | ||
| <!-- Live preview --> | ||
| <div | ||
| class="flex items-center justify-center p-10 sm:p-16 transition-colors duration-300 motion-reduce:transition-none" | ||
| :class="customBgDark ? 'bg-[#0a0a0a]' : 'bg-white'" | ||
| > | ||
| <AppLogo | ||
| ref="customLogoRef" | ||
| class="h-10 sm:h-14 w-auto max-w-full transition-colors duration-300 motion-reduce:transition-none" | ||
| :class="customBgDark ? 'text-[#fafafa]' : 'text-[#0a0a0a]'" | ||
| :style="{ '--accent': activeAccentColor }" | ||
| /> | ||
| </div> | ||
|
|
||
| <!-- Controls --> | ||
| <div | ||
| class="border-t border-border p-4 sm:p-6 flex flex-col sm:flex-row sm:items-center gap-4" | ||
| > | ||
| <!-- Accent color picker --> | ||
| <fieldset class="flex items-center gap-3 flex-1 border-none p-0 m-0"> | ||
| <legend class="sr-only">{{ $t('brand.customize.accent_label') }}</legend> | ||
| <span class="text-xs font-mono text-fg-muted shrink-0">{{ | ||
| $t('brand.customize.accent_label') | ||
| }}</span> | ||
| <div class="flex items-center gap-1.5" role="radiogroup"> | ||
| <ButtonBase | ||
| v-for="color in pickerColors" | ||
| :key="color.id" | ||
| role="radio" | ||
| :aria-checked="activeAccentId === color.id" | ||
| :aria-label="color.label" | ||
| class="!w-6 !h-6 !rounded-full !border-2 !p-0 !min-w-0 transition-all duration-150 motion-reduce:transition-none" | ||
| :class=" | ||
| activeAccentId === color.id | ||
| ? '!border-fg scale-110' | ||
| : color.id === 'neutral' | ||
| ? '!border-border hover:!border-border-hover' | ||
| : '!border-transparent hover:!border-border-hover' | ||
| " | ||
| :style="{ backgroundColor: color.value }" | ||
| @click="customAccent = color.id" | ||
| /> | ||
| </div> | ||
| </fieldset> | ||
|
|
||
| <!-- Background toggle --> | ||
| <div class="flex items-center gap-3"> | ||
| <span class="text-xs font-mono text-fg-muted">{{ $t('brand.customize.bg_label') }}</span> | ||
| <div | ||
| class="flex items-center border border-border rounded-md overflow-hidden" | ||
| role="radiogroup" | ||
| > | ||
| <ButtonBase | ||
| size="sm" | ||
| role="radio" | ||
| :aria-checked="customBgDark" | ||
| :aria-label="$t('brand.logos.on_dark')" | ||
| class="!border-none !rounded-none motion-reduce:transition-none" | ||
| :class=" | ||
| customBgDark ? 'bg-bg-muted text-fg' : 'bg-transparent text-fg-muted hover:text-fg' | ||
| " | ||
| @click="customBgDark = true" | ||
| > | ||
| {{ $t('brand.logos.on_dark') }} | ||
| </ButtonBase> | ||
| <ButtonBase | ||
| size="sm" | ||
| role="radio" | ||
| :aria-checked="!customBgDark" | ||
| :aria-label="$t('brand.logos.on_light')" | ||
| class="!border-none !rounded-none border-is border-is-border motion-reduce:transition-none" | ||
| :class=" | ||
| !customBgDark ? 'bg-bg-muted text-fg' : 'bg-transparent text-fg-muted hover:text-fg' | ||
| " | ||
| @click="customBgDark = false" | ||
| > | ||
| {{ $t('brand.logos.on_light') }} | ||
| </ButtonBase> | ||
|
Comment on lines
+172
to
+197
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. It'd be cool if it had a little spinner while it's downloading (e.g.), but it's kinda extra so don't do it if you don't want to xD
Contributor
Author
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. On the mode toggles? |
||
| </div> | ||
| </div> | ||
|
|
||
| <!-- Download buttons --> | ||
| <div class="flex items-center gap-2"> | ||
| <ButtonBase | ||
| size="sm" | ||
| classicon="i-lucide:download" | ||
| :aria-label="$t('brand.customize.download_svg_aria')" | ||
| @click="downloadCustomSvg" | ||
| > | ||
| {{ $t('brand.logos.download_svg') }} | ||
| </ButtonBase> | ||
| <ButtonBase | ||
| size="sm" | ||
| :aria-label="$t('brand.customize.download_png_aria')" | ||
| :disabled="pngLoading" | ||
| @click="downloadCustomPng" | ||
| > | ||
| <span | ||
| class="size-[1em]" | ||
| aria-hidden="true" | ||
| :class="pngLoading ? 'i-lucide:loader-circle animate-spin' : 'i-lucide:download'" | ||
| /> | ||
| {{ $t('brand.logos.download_png') }} | ||
| </ButtonBase> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </section> | ||
| </template> | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.