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.
- package/docs/LAYOUT.md +517 -0
- package/layers/core/app/composables/useScrollGuard.ts +2 -2
- package/layers/layout/app/assets/css/layout/grids.css +18 -18
- package/layers/layout/app/assets/css/layout/modes/fluid.css +100 -0
- package/layers/layout/app/assets/css/main.css +1 -0
- package/layers/layout/app/components/Layout/Container.vue +49 -0
- package/layers/layout/app/components/Layout/Grid/Debug.vue +18 -19
- package/layers/layout/app/components/Layout/Grid/Item.vue +43 -19
- package/layers/layout/app/components/Layout/Main.vue +36 -0
- package/layers/layout/app/components/Layout/Page/index.vue +69 -0
- package/layers/layout/app/components/Layout/Section/Grid.vue +49 -0
- package/layers/layout/app/components/Layout/Section/Sidebar.vue +72 -0
- package/layers/layout/app/components/Layout/Section/Stack.vue +63 -0
- package/layers/layout/app/composables/useGridConfig.ts +28 -1
- package/layers/layout/app/types/layouts.ts +42 -9
- package/layers/layout/app.config.ts +114 -20
- package/layers/motion/app/plugins/locomotive-scroll.client.ts +41 -21
- package/package.json +1 -1
|
@@ -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
|
|
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
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
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
|
|
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
|
-
//
|
|
69
|
-
|
|
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:
|
|
72
|
-
|
|
73
|
-
rowSpan: 6,
|
|
155
|
+
colSpan: { default: 6, md: 9, lg: 13 },
|
|
156
|
+
rowSpan: 12,
|
|
74
157
|
},
|
|
75
|
-
|
|
76
|
-
colStart: 1,
|
|
77
|
-
colSpan: { default: 6, md:
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
}
|