Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
24 changes: 20 additions & 4 deletions apps/docs/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@
<div
class="app-shell min-h-screen text-on-background"
:class="{ 'dot-grid': settings.showDotGrid.value }"
:style="{ '--line-opacity': `${settings.dotGridIntensity.value}%` }"
>
<a
class="sr-only focus:not-sr-only focus:absolute focus:top-2 focus:start-2 focus:z-50 focus:px-4 focus:py-2 focus:bg-primary focus:text-on-primary focus:rounded"
Expand Down Expand Up @@ -199,8 +200,21 @@
position: relative;
background: color-mix(in srgb, var(--v0-background) 85%, transparent);

/*
* App-shell dot grid (toggled via Settings → "Dot grid pattern").
*
* Intentionally a separate inline implementation from the AppDotGrid
* component: this is a full-viewport shell background with a diagonal fade
* and a user toggle, whereas AppDotGrid is a per-section accent with a
* radial fade from a corner — different roles, so neither is forced onto
* the other. The dot weight, 20px density, and connecting-line construction
* (lines offset half a cell so the dots sit on the intersections) are kept
* deliberately in sync with the GnDotGrid primitive in @paper/genesis;
* change the look there and mirror it here.
*/
&.dot-grid::before {
--dot-opacity: 24%;
--dot-opacity: 12%;
/* --line-opacity is set inline from the "Line intensity" setting (defaults to 0.85%). */
content: '';
position: absolute;
top: 0;
Expand All @@ -210,9 +224,11 @@
z-index: 0;
pointer-events: none;
background:
radial-gradient(circle, color-mix(in srgb, var(--v0-on-background) var(--dot-opacity), transparent) 1px, transparent 1px);
background-size: 24px 24px;
background-position: 18px 0;
radial-gradient(circle, color-mix(in srgb, var(--v0-on-background) var(--dot-opacity), transparent) 1px, transparent 1px),
linear-gradient(to right, color-mix(in srgb, var(--v0-on-background) var(--line-opacity, 0.85%), transparent) 1px, transparent 1px),
linear-gradient(to bottom, color-mix(in srgb, var(--v0-on-background) var(--line-opacity, 0.85%), transparent) 1px, transparent 1px);
background-size: 20px 20px;
background-position: 0 0, 10px 10px, 10px 10px;
mask-image: linear-gradient(
225deg,
black 0%,
Expand Down
1 change: 1 addition & 0 deletions apps/docs/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ declare module 'vue' {
AppSettingsSheet: typeof import('./components/app/AppSettingsSheet.vue')['default']
AppSettingsSingleSelect: typeof import('./components/app/settings/AppSettingsSingleSelect.vue')['default']
AppSettingsSkillLevel: typeof import('./components/app/settings/AppSettingsSkillLevel.vue')['default']
AppSettingsSlider: typeof import('./components/app/settings/AppSettingsSlider.vue')['default']
AppSettingsTheme: typeof import('./components/app/settings/AppSettingsTheme.vue')['default']
AppSettingsThemeCarousel: typeof import('./components/app/settings/AppSettingsThemeCarousel.vue')['default']
AppSettingsThemeEditor: typeof import('./components/app/settings/AppSettingsThemeEditor.vue')['default']
Expand Down
54 changes: 12 additions & 42 deletions apps/docs/src/components/app/AppDotGrid.vue
Original file line number Diff line number Diff line change
@@ -1,50 +1,20 @@
<script setup lang="ts">
// Composables
import { useThemeToggle } from '@/composables/useThemeToggle'

// Utilities
import { computed } from 'vue'
// Framework
import { GnDotGrid, type GnDotGridProps } from '@paper/genesis'

const {
coverage = 15,
density = 20,
origin = 'bottom left',
} = defineProps<{
coverage?: number
density?: number
origin?: string
}>()
// Composables
import { useSettings } from '@/composables/useSettings'

const { isDark } = useThemeToggle()
// Docs adapter for the canonical GnDotGrid primitive in @paper/genesis.
// Kept as a thin local component so the docs auto-import keeps resolving
// <AppDotGrid>. The connecting-line intensity defaults to the user's
// "Line intensity" setting (shared with the app-shell grid); the genesis
// primitive itself stays neutral (lines=0). Any usage can override :lines.
const { lines, ...props } = defineProps<GnDotGridProps>()

const maskStyle = computed(() => {
const fadeEnd = coverage + 20
const gradient = `radial-gradient(ellipse at ${origin}, transparent 0%, transparent ${coverage}%, black ${fadeEnd}%)`
return {
maskImage: gradient,
WebkitMaskImage: gradient,
backgroundSize: `${density}px ${density}px`,
}
})
const settings = useSettings()
</script>

<template>
<div
aria-hidden="true"
class="app-dot-grid absolute inset-0 pointer-events-none z-0"
:class="isDark ? 'dot-dark' : 'dot-light'"
:style="maskStyle"
/>
<GnDotGrid v-bind="props" :lines="lines ?? settings.dotGridIntensity.value" />
</template>

<style scoped>
.app-dot-grid.dot-dark {
background:
radial-gradient(circle, color-mix(in srgb, var(--v0-on-background) 10%, transparent) 1px, transparent 1px);
}

.app-dot-grid.dot-light {
background:
radial-gradient(circle, color-mix(in srgb, var(--v0-on-background) 12%, transparent) 1px, transparent 1px);
}
</style>
82 changes: 82 additions & 0 deletions apps/docs/src/components/app/settings/AppSettingsSlider.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script setup lang="ts">
// Framework
import { Checkbox, Collapsible, Slider } from '@vuetify/v0'

// Utilities
import { computed, shallowRef } from 'vue'

const {
label,
description = undefined,
min = 0,
max = 2,
step = 0.05,
} = defineProps<{
label: string
description?: string
min?: number
max?: number
step?: number
}>()

const model = defineModel<number>({ required: true })

// The slider + preview stay collapsed until the user checks the box to edit.
const expanded = shallowRef(false)

// v0 Slider works on an array model; adapt the scalar setting to/from it.
const value = computed({
get: () => [model.value],
set: ([next]) => {
model.value = next
},
})
</script>

<template>
<div class="px-3 py-2 rounded-lg bg-surface-variant">
<label class="flex items-center justify-between gap-3 cursor-pointer">
<div>
<span class="text-sm">{{ label }}</span>
<p v-if="description" class="text-xs text-on-surface-variant/60">{{ description }}</p>
</div>

<div class="flex items-center gap-2">
<span class="text-xs text-on-surface-variant tabular-nums">{{ model.toFixed(2) }}</span>

<Checkbox.Root
v-model="expanded"
:aria-label="`Edit ${label.toLowerCase()}`"
class="size-4 shrink-0 inline-flex items-center justify-center rounded-sm border border-on-surface-variant/40"
>
<Checkbox.Indicator class="text-primary text-[11px] leading-none">✓</Checkbox.Indicator>
</Checkbox.Root>
</div>
</label>

<Collapsible.Root v-model="expanded">
<Collapsible.Content>
<div class="pt-3">
<slot name="preview" />

<Slider.Root
v-model="value"
class="relative flex items-center w-full h-5 mt-3"
:max
:min
:step
>
<Slider.Track class="relative h-1 w-full rounded-full bg-surface">
<Slider.Range class="absolute h-full rounded-full bg-primary" />
</Slider.Track>

<Slider.Thumb
:aria-label="label"
class="absolute size-4 rounded-full bg-primary -translate-x-1/2 focus:outline-2 focus:outline-primary"
/>
</Slider.Root>
</div>
</Collapsible.Content>
</Collapsible.Root>
</div>
</template>
19 changes: 19 additions & 0 deletions apps/docs/src/components/app/settings/AppSettingsTheme.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,25 @@
label="Dot grid pattern"
/>

<AppSettingsSlider
v-if="settings.showDotGrid.value"
v-model="settings.dotGridIntensity.value"
class="ml-4"
description="Connecting-line strength"
label="Line intensity"
:max="2"
:min="0"
:step="0.05"
>
<template #preview>
<div class="relative h-20 rounded-md overflow-hidden border bg-background">
<div class="absolute inset-0" style="transform: scale(2.2); transform-origin: center">
<AppDotGrid :coverage="0" />
</div>
</div>
</template>
</AppSettingsSlider>

<AppSettingsToggle
v-model="settings.showMeshGrid.value"
description="Colorful gradient background"
Expand Down
10 changes: 9 additions & 1 deletion apps/docs/src/composables/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export interface DocSettings {
showSocialLinks: boolean
collapsibleNav: boolean
showDotGrid: boolean
dotGridIntensity: number

showMeshGrid: boolean
showMeshTransition: boolean
Expand All @@ -44,6 +45,7 @@ export interface SettingsContext {
showSocialLinks: ShallowRef<boolean>
collapsibleNav: ShallowRef<boolean>
showDotGrid: ShallowRef<boolean>
dotGridIntensity: ShallowRef<number>

showMeshGrid: ShallowRef<boolean>
showMeshTransition: ShallowRef<boolean>
Expand All @@ -65,6 +67,7 @@ const DEFAULTS: DocSettings = {
showSocialLinks: true,
collapsibleNav: true,
showDotGrid: true,
dotGridIntensity: 0.85,

showMeshGrid: true,
showMeshTransition: true,
Expand Down Expand Up @@ -123,6 +126,7 @@ export function createSettingsContext (): SettingsContext {
const showSocialLinks = shallowRef(DEFAULTS.showSocialLinks)
const collapsibleNav = shallowRef(DEFAULTS.collapsibleNav)
const showDotGrid = shallowRef(DEFAULTS.showDotGrid)
const dotGridIntensity = shallowRef(DEFAULTS.dotGridIntensity)

const showMeshGrid = shallowRef(DEFAULTS.showMeshGrid)
const showMeshTransition = shallowRef(DEFAULTS.showMeshTransition)
Expand All @@ -138,13 +142,14 @@ export function createSettingsContext (): SettingsContext {
loadSetting(storage, 'showSocialLinks', showSocialLinks)
loadSetting(storage, 'collapsibleNav', collapsibleNav)
loadSetting(storage, 'showDotGrid', showDotGrid)
loadSetting(storage, 'dotGridIntensity', dotGridIntensity)

loadSetting(storage, 'showMeshGrid', showMeshGrid)
loadSetting(storage, 'showMeshTransition', showMeshTransition)
loadSetting(storage, 'showBgGlass', showBgGlass)

// Persist on change
const settings = { lineWrap, reduceMotion, packageManager, showInlineApi, showSkillFilter, showThemeToggle, showSocialLinks, collapsibleNav, showDotGrid, showMeshGrid, showMeshTransition, showBgGlass }
const settings = { lineWrap, reduceMotion, packageManager, showInlineApi, showSkillFilter, showThemeToggle, showSocialLinks, collapsibleNav, showDotGrid, dotGridIntensity, showMeshGrid, showMeshTransition, showBgGlass }
for (const [key, ref] of Object.entries(settings)) {
watch(ref, val => storage.set(key, val))
}
Expand Down Expand Up @@ -172,6 +177,7 @@ export function createSettingsContext (): SettingsContext {
showSocialLinks.value !== DEFAULTS.showSocialLinks ||
collapsibleNav.value !== DEFAULTS.collapsibleNav ||
showDotGrid.value !== DEFAULTS.showDotGrid ||
dotGridIntensity.value !== DEFAULTS.dotGridIntensity ||
showMeshGrid.value !== DEFAULTS.showMeshGrid ||
showMeshTransition.value !== DEFAULTS.showMeshTransition ||
showBgGlass.value !== DEFAULTS.showBgGlass
Expand Down Expand Up @@ -213,6 +219,7 @@ export function createSettingsContext (): SettingsContext {
showSocialLinks.value = DEFAULTS.showSocialLinks
collapsibleNav.value = DEFAULTS.collapsibleNav
showDotGrid.value = DEFAULTS.showDotGrid
dotGridIntensity.value = DEFAULTS.dotGridIntensity
showMeshGrid.value = DEFAULTS.showMeshGrid
showMeshTransition.value = DEFAULTS.showMeshTransition
showBgGlass.value = DEFAULTS.showBgGlass
Expand All @@ -232,6 +239,7 @@ export function createSettingsContext (): SettingsContext {
showSocialLinks,
collapsibleNav,
showDotGrid,
dotGridIntensity,
showMeshGrid,
showMeshTransition,
showBgGlass,
Expand Down
74 changes: 74 additions & 0 deletions packages/genesis/src/components/GnDotGrid/GnDotGrid.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script lang="ts">
// Utilities
import { computed } from 'vue'

export interface GnDotGridProps {
/** Base color for the dots and lines; any CSS color. Defaults to the theme foreground. */
color?: string
/** Percentage of the radial fade kept fully transparent before it ramps to opaque. */
coverage?: number
/** Grid cell size, in pixels. */
density?: number
/** Connecting-line intensity as an alpha percentage; `0` disables the lines (dots only). */
lines?: number
/** Origin of the radial fade mask, e.g. `'bottom left'`. */
origin?: string
}
</script>

<script setup lang="ts">
const {
color = 'var(--v0-on-background)',
coverage = 15,
density = 20,
lines = 0,
origin = 'bottom left',
} = defineProps<GnDotGridProps>()

const maskStyle = computed(() => {
const fadeEnd = coverage + 20
const mask = `radial-gradient(ellipse at ${origin}, transparent 0%, transparent ${coverage}%, black ${fadeEnd}%)`

const dot = `radial-gradient(circle, color-mix(in srgb, ${color} 12%, transparent) 1px, transparent 1px)`

const layers = [dot]
// Dots are centered in each cell; offset the lines by half a cell so they
// cross through the dot centers, seating each dot on a grid intersection.
const positions = ['0 0']

if (lines > 0) {
const line = `color-mix(in srgb, ${color} ${lines}%, transparent)`
const half = density / 2
layers.push(
`linear-gradient(to right, ${line} 1px, transparent 1px)`,
`linear-gradient(to bottom, ${line} 1px, transparent 1px)`,
)
positions.push(`${half}px ${half}px`, `${half}px ${half}px`)
}

return {
maskImage: mask,
WebkitMaskImage: mask,
backgroundImage: layers.join(', '),
backgroundPosition: positions.join(', '),
backgroundSize: `${density}px ${density}px`,
}
})
</script>

<template>
<div
aria-hidden="true"
class="genesis-dot-grid"
:style="maskStyle"
/>
</template>

<style scoped>
.genesis-dot-grid {
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
}
</style>
2 changes: 2 additions & 0 deletions packages/genesis/src/components/GnDotGrid/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type { GnDotGridProps } from './GnDotGrid.vue'
export { default as GnDotGrid } from './GnDotGrid.vue'
1 change: 1 addition & 0 deletions packages/genesis/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './GnDocsExample'
export * from './GnDotGrid'
Loading