kmcom-nuxt-layers 1.1.9 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import type { GridConfig, GridPresetsItem } from '#layers/layout/app/types/layouts'
1
+ import type { GridConfig, GridLayers, GridMode, GridPresetsItem } from '#layers/layout/app/types/layouts'
2
2
 
3
3
  interface LayoutLayerConfig {
4
4
  layoutLayer?: {
@@ -13,15 +13,42 @@ export function useGridConfig() {
13
13
  const appConfig = useAppConfig() as LayoutLayerConfig
14
14
  const gridConfig = computed(() => appConfig.layoutLayer?.ui?.grid)
15
15
 
16
+ /**
17
+ * Resolved layout mode. Derives from `mode` field first, then falls back to
18
+ * the legacy `enabled` boolean for backwards compatibility.
19
+ */
20
+ const mode = computed<GridMode>(() => {
21
+ const cfg = gridConfig.value
22
+ if (!cfg) return 'swiss'
23
+ if (cfg.mode) return cfg.mode
24
+ return cfg.enabled === false ? 'disabled' : 'swiss'
25
+ })
26
+
27
+ /** True when the Swiss Grid system is active. */
28
+ const isEnabled = computed(() => mode.value !== 'disabled')
29
+
16
30
  const getPreset = (name: string): GridPresetsItem | undefined => {
17
31
  const presets = gridConfig.value?.presets
18
32
  if (!presets) return undefined
19
33
  return presets[name as keyof typeof presets]
20
34
  }
21
35
 
36
+ /**
37
+ * Returns the z-index value for a named stacking layer.
38
+ *
39
+ * @example
40
+ * const zModal = useZIndex('modal') // → 400
41
+ */
42
+ const useZIndex = (layer: keyof GridLayers): number => {
43
+ return gridConfig.value?.layers?.[layer] ?? 0
44
+ }
45
+
22
46
  return {
23
47
  config: gridConfig,
24
48
  getPreset,
25
49
  layers: computed(() => gridConfig.value?.layers),
50
+ isEnabled,
51
+ mode,
52
+ useZIndex,
26
53
  }
27
54
  }
@@ -7,28 +7,61 @@ export interface ResponsiveValue<T> {
7
7
  }
8
8
 
9
9
  export interface GridLayers {
10
+ /** Component/content at rest — z-index 0 */
10
11
  back: number
12
+ /** Standard interactive content — z-index 10 */
11
13
  mid: number
14
+ /** Elevated content, sticky elements — z-index 20 */
12
15
  front: number
16
+ /** Page-level overlays — z-index 30 */
13
17
  top: number
18
+ /** Site header / navigation bar — z-index 100 */
19
+ header: number
20
+ /** Dropdown menus, popovers — z-index 200 */
21
+ dropdown: number
22
+ /** Overlay backdrops — z-index 300 */
23
+ overlay: number
24
+ /** Modal dialogs — z-index 400 */
25
+ modal: number
26
+ /** Toast notifications — z-index 500 */
27
+ toast: number
14
28
  }
15
29
 
30
+ export type GridContainerSize = 'content' | 'wide' | 'fluid' | 'full'
31
+ export type GridDensity = 'compact' | 'normal' | 'relaxed'
32
+ export type GridMode = 'swiss' | 'fluid' | 'disabled'
33
+
16
34
  export interface GridPresetsItem {
17
- colStart: number | ResponsiveValue<number>
18
- colSpan: number | ResponsiveValue<number>
35
+ colStart?: number | ResponsiveValue<number>
36
+ colSpan: number | 'full' | ResponsiveValue<number>
19
37
  rowStart?: number | ResponsiveValue<number>
20
38
  rowSpan?: number
39
+ /** Container size applied to the item's content */
40
+ container?: GridContainerSize
41
+ /** Gap override for this preset */
42
+ gap?: string
43
+ /** Vertical rhythm density for this preset */
44
+ density?: GridDensity
45
+ /** Vertical alignment (align-self) */
46
+ align?: 'start' | 'center' | 'end' | 'stretch'
47
+ /** Horizontal alignment (justify-self) */
48
+ justify?: 'start' | 'center' | 'end' | 'stretch'
21
49
  }
22
50
 
23
- export interface GridPresets {
24
- hero: GridPresetsItem
25
- centered: GridPresetsItem
26
- fullWidth: GridPresetsItem
27
- sidebar: GridPresetsItem
28
- content: GridPresetsItem
29
- }
51
+ export type GridPresets = Record<string, GridPresetsItem>
30
52
 
31
53
  export interface GridConfig {
54
+ /**
55
+ * Layout mode.
56
+ * - `'swiss'` — Swiss Grid System (default)
57
+ * - `'fluid'` — Container-query based auto-fit grid
58
+ * - `'disabled'` — Falls back to standard Nuxt UI layout
59
+ *
60
+ * Setting `enabled: false` is equivalent to `mode: 'disabled'` (backwards compat).
61
+ */
62
+ mode?: GridMode
63
+ /** @deprecated Use `mode: 'disabled'` instead. Kept for backwards compatibility. */
64
+ enabled?: boolean
32
65
  columns: ResponsiveValue<number>
33
66
  rowsPerSection: number
34
67
  rhythm: string
@@ -1,8 +1,66 @@
1
1
  export default defineAppConfig({
2
+ /**
3
+ * Nuxt UI component theming — aligned to the Swiss Grid System.
4
+ *
5
+ * UHeader: removes the default max-width container so it spans the full
6
+ * viewport width, using clamp-based gutters that match the grid padding.
7
+ *
8
+ * UPage / UPage* components: participate as subgrid members so their
9
+ * columns align to the inherited mastmain grid lines.
10
+ *
11
+ * These overrides are additive and safe when the swiss grid is disabled —
12
+ * col-span-full / grid-cols-subgrid have no effect outside a grid context.
13
+ * The consuming app can override any of these via its own app.config.ts.
14
+ */
15
+ ui: {
16
+ header: {
17
+ container: 'max-w-full px-[clamp(1rem,2.5vw,2rem)]',
18
+ },
19
+
20
+ // UPage: transparent subgrid participant; left/center/right slots map
21
+ // to named column ranges on the 18-column grid (sidebar 4, content 10,
22
+ // right sidebar 4; all col-start values are explicit to avoid overlap)
23
+ page: {
24
+ root: 'col-span-full grid grid-cols-subgrid',
25
+ left: 'col-span-4',
26
+ center: 'col-start-5 col-span-10',
27
+ right: 'col-start-15 col-span-4',
28
+ },
29
+
30
+ // UPageBody: no opinionated padding — the grid and sections own spacing
31
+ pageBody: {
32
+ base: '',
33
+ },
34
+
35
+ // UPageGrid: inherits subgrid column lines; gap matches grid gap clamp
36
+ pageGrid: {
37
+ base: 'col-span-full grid grid-cols-subgrid gap-[clamp(0.75rem,1.5vw,1.5rem)]',
38
+ },
39
+
40
+ // UPageColumns: inherits subgrid column lines
41
+ pageColumns: {
42
+ base: 'col-span-full grid grid-cols-subgrid',
43
+ },
44
+ },
45
+
2
46
  layoutLayer: {
3
47
  ui: {
4
48
  // Swiss Grid System Configuration
5
49
  grid: {
50
+ /**
51
+ * Layout mode.
52
+ * - 'swiss' — Swiss Grid System (default)
53
+ * - 'fluid' — Container-query based auto-fit grid
54
+ * - 'disabled' — Falls back to standard UMain > UPage layout
55
+ */
56
+ mode: 'swiss',
57
+
58
+ /**
59
+ * @deprecated Use mode: 'disabled' instead. Kept for backwards compatibility.
60
+ * When false, acts as mode: 'disabled'.
61
+ */
62
+ enabled: true,
63
+
6
64
  // Core settings
7
65
  columns: { default: 6, md: 12, lg: 18 },
8
66
  rowsPerSection: 12,
@@ -10,28 +68,51 @@ export default defineAppConfig({
10
68
 
11
69
  // Z-index layers
12
70
  layers: {
71
+ // Base layers (swiss grid stacking)
13
72
  back: 0,
14
73
  mid: 10,
15
74
  front: 20,
16
75
  top: 30,
76
+ // UI stacking layers
77
+ header: 100,
78
+ dropdown: 200,
79
+ overlay: 300,
80
+ modal: 400,
81
+ toast: 500,
17
82
  },
18
83
 
19
84
  // Preset layouts for common patterns
20
85
  presets: {
21
- hero: {
86
+ // Full-viewport hero: full width, all 12 rows
87
+ hero: { colSpan: 'full', rowSpan: 12 },
88
+
89
+ // Centered content column (equal margins both sides)
90
+ // lg: 3 + 12 + 3 = 18 ✓ | md: 1 + 10 + 1 = 12 ✓
91
+ centered: {
22
92
  colStart: { default: 1, md: 2, lg: 4 },
23
93
  colSpan: { default: 6, md: 10, lg: 12 },
24
94
  rowSpan: 12,
25
95
  },
26
- centered: {
27
- colStart: { default: 1, md: 2, lg: 5 },
28
- colSpan: { default: 6, md: 10, lg: 10 },
29
- rowSpan: 12,
96
+
97
+ // Full width (all columns, no bleed)
98
+ fullWidth: { colSpan: 'full' },
99
+
100
+ // Narrow prose column (tighter reading width)
101
+ // lg: 4 + 10 + 4 = 18 ✓ | md: 2 + 8 + 2 = 12 ✓
102
+ prose: {
103
+ colStart: { default: 1, md: 3, lg: 5 },
104
+ colSpan: { default: 6, md: 8, lg: 10 },
30
105
  },
31
- fullWidth: {
32
- colStart: 1,
33
- colSpan: { default: 6, md: 12, lg: 18 },
106
+
107
+ // Wide content (generous width, minimal margins)
108
+ // lg: 1 + 16 + 1 = 18 ✓ | md: 1 + 10 + 1 = 12 ✓
109
+ wide: {
110
+ colStart: { default: 1, md: 2, lg: 2 },
111
+ colSpan: { default: 6, md: 10, lg: 16 },
34
112
  },
113
+
114
+ // Sidebar + content pair (use together)
115
+ // sidebar: cols 1-4 (lg) | content: cols 5-18 (14 cols) → 4+14=18 ✓
35
116
  sidebar: {
36
117
  colStart: { default: 1, md: 1, lg: 1 },
37
118
  colSpan: { default: 6, md: 4, lg: 4 },
@@ -41,7 +122,8 @@ export default defineAppConfig({
41
122
  colSpan: { default: 6, md: 8, lg: 14 },
42
123
  },
43
124
 
44
- // 50/50 vertical split (stacks on mobile)
125
+ // 50/50 vertical splits (stack on mobile)
126
+ // lg: 9+9=18 ✓ | md: 6+6=12 ✓
45
127
  splitLeft: {
46
128
  colStart: 1,
47
129
  colSpan: { default: 6, md: 6, lg: 9 },
@@ -53,7 +135,8 @@ export default defineAppConfig({
53
135
  rowSpan: 12,
54
136
  },
55
137
 
56
- // 25/75 split (stacks on mobile)
138
+ // 25/75 vertical splits (stack on mobile)
139
+ // lg: 5+13=18 ✓ | md: 3+9=12 ✓
57
140
  quarterLeft: {
58
141
  colStart: 1,
59
142
  colSpan: { default: 6, md: 3, lg: 5 },
@@ -65,18 +148,29 @@ export default defineAppConfig({
65
148
  rowSpan: 12,
66
149
  },
67
150
 
68
- // Horizontal 50/50 split (100vw × 50vh each)
69
- halfTop: {
151
+ // 75/25 vertical splits (stack on mobile)
152
+ // lg: 13+5=18 ✓ | md: 9+3=12 ✓
153
+ threeQuarterLeft: {
70
154
  colStart: 1,
71
- colSpan: { default: 6, md: 12, lg: 18 },
72
- rowStart: 1,
73
- rowSpan: 6,
155
+ colSpan: { default: 6, md: 9, lg: 13 },
156
+ rowSpan: 12,
74
157
  },
75
- halfBottom: {
76
- colStart: 1,
77
- colSpan: { default: 6, md: 12, lg: 18 },
78
- rowStart: 7,
79
- rowSpan: 6,
158
+ quarterRight: {
159
+ colStart: { default: 1, md: 10, lg: 14 },
160
+ colSpan: { default: 6, md: 3, lg: 5 },
161
+ rowSpan: 12,
162
+ },
163
+
164
+ // Horizontal 50/50 stacks (100vw × 50vh each)
165
+ halfTop: { colSpan: 'full', rowStart: 1, rowSpan: 6 },
166
+ halfBottom: { colSpan: 'full', rowStart: 7, rowSpan: 6 },
167
+
168
+ // Super centered: full-width, full-height, content perfectly centered
169
+ superCentered: {
170
+ colSpan: 'full',
171
+ rowSpan: 12,
172
+ align: 'center',
173
+ justify: 'center',
80
174
  },
81
175
  },
82
176
  },
@@ -10,7 +10,6 @@ export interface ScrollState {
10
10
  }
11
11
 
12
12
  export default defineNuxtPlugin(() => {
13
- // Reactive scroll state that will be updated via scrollCallback
14
13
  const scrollState = reactive<ScrollState>({
15
14
  scroll: 0,
16
15
  limit: 0,
@@ -19,30 +18,51 @@ export default defineNuxtPlugin(() => {
19
18
  progress: 0,
20
19
  })
21
20
 
22
- // Create Locomotive Scroll instance
23
- const locomotiveScroll = new LocomotiveScroll({
24
- // Lenis options passthrough (LS5 is built on Lenis)
25
- lenisOptions: {
26
- lerp: 0.1,
27
- smoothWheel: true,
28
- wheelMultiplier: 1,
29
- },
30
- // Scroll callback for reactive state updates and GSAP sync
31
- scrollCallback: ({ scroll, limit, velocity, direction, progress }) => {
32
- scrollState.scroll = scroll
33
- scrollState.limit = limit
34
- scrollState.velocity = velocity
35
- scrollState.direction = direction
36
- scrollState.progress = progress
37
-
38
- // Sync ScrollTrigger with Locomotive Scroll
39
- ScrollTrigger.update()
40
- },
21
+ let instance: LocomotiveScroll | null = null
22
+
23
+ function init() {
24
+ if (instance) return
25
+ instance = new LocomotiveScroll({
26
+ lenisOptions: {
27
+ lerp: 0.1,
28
+ smoothWheel: true,
29
+ wheelMultiplier: 1,
30
+ },
31
+ scrollCallback: ({ scroll, limit, velocity, direction, progress }) => {
32
+ scrollState.scroll = scroll
33
+ scrollState.limit = limit
34
+ scrollState.velocity = velocity
35
+ scrollState.direction = direction
36
+ scrollState.progress = progress
37
+ ScrollTrigger.update()
38
+ },
39
+ })
40
+ }
41
+
42
+ function destroy() {
43
+ instance?.destroy()
44
+ instance = null
45
+ }
46
+
47
+ const router = useRouter()
48
+
49
+ const SMOOTH_SCROLL_ROUTES = ['/locomotive-scroll', '/layout-stacking', '/layout-blind-reveal']
50
+
51
+ addRouteMiddleware((to, from) => {
52
+ if (SMOOTH_SCROLL_ROUTES.includes(to.path)) {
53
+ nextTick(init)
54
+ } else if (from?.path && SMOOTH_SCROLL_ROUTES.includes(from.path)) {
55
+ destroy()
56
+ }
41
57
  })
42
58
 
59
+ if (SMOOTH_SCROLL_ROUTES.includes(router.currentRoute.value.path)) {
60
+ init()
61
+ }
62
+
43
63
  return {
44
64
  provide: {
45
- locomotiveScroll,
65
+ locomotiveScroll: readonly(instance),
46
66
  scrollState,
47
67
  },
48
68
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "kmcom-nuxt-layers",
3
3
  "private": false,
4
- "version": "1.1.9",
4
+ "version": "1.3.0",
5
5
  "description": "Composable Nuxt 4 layers for building scalable Vue applications",
6
6
  "files": [
7
7
  "layers/*/nuxt.config.ts",