sprintify-ui 0.12.23 → 0.12.24

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,6 +1,15 @@
1
1
  export default {
2
2
  extend: {
3
3
  spacing: {
4
+ // Measured height of the layout's sticky top header (system
5
+ // alerts + navbar). Kept in sync by the BaseLayout* components
6
+ // through the --app-header-height CSS variable. Use for anything
7
+ // that must clear the header: sticky offsets, scroll margins,
8
+ // 100vh math.
9
+ header: 'var(--app-header-height, 0px)',
10
+ // Header plus the standard content gap, for sticky elements and
11
+ // scroll targets that should sit just below the header.
12
+ 'below-header': 'calc(var(--app-header-height, 0px) + 1.25rem)',
4
13
  'control-xs': '1.65rem',
5
14
  'control-sm': '2rem',
6
15
  'control-md': '2.5rem',
@@ -0,0 +1,33 @@
1
+ import type { Ref, StyleValue } from 'vue';
2
+ /**
3
+ * The layout's sticky top header (system alerts + navbar) overlaps page
4
+ * content when it scrolls, so anything sticky or scroll-anchored inside the
5
+ * layout needs to know its rendered height. That height cannot be hardcoded:
6
+ * the navbar row depends on the layout `size` prop, and system alerts stack
7
+ * above it dynamically.
8
+ *
9
+ * The layout components measure their own header element and publish the
10
+ * height two ways:
11
+ *
12
+ * - The `--app-header-height` CSS variable, bound on the layout's content
13
+ * wrapper — for Tailwind/CSS (`top-header`, `scroll-mt-below-header`, ...,
14
+ * see the tailwindcss plugin's spacing tokens).
15
+ * - A provided reactive ref, read with `useLayoutHeaderHeight()` — for
16
+ * scripts (scrollspy root margins, canvas math, ...).
17
+ */
18
+ export declare const LAYOUT_HEADER_HEIGHT_CSS_VARIABLE = "--app-header-height";
19
+ /**
20
+ * Internal — called once by each layout component (BaseLayoutSidebar,
21
+ * BaseLayoutStacked) with a template ref to its sticky header element.
22
+ * Returns the reactive height and a style binding carrying the CSS variable,
23
+ * to spread on the element that wraps the layout's page content.
24
+ */
25
+ export declare function provideLayoutHeaderHeight(headerElement: Ref<HTMLElement | null>): {
26
+ headerHeight: Readonly<Ref<number>>;
27
+ headerHeightStyle: Readonly<Ref<StyleValue>>;
28
+ };
29
+ /**
30
+ * The rendered height (px) of the layout's sticky top header. Zero when used
31
+ * outside a layout component.
32
+ */
33
+ export declare function useLayoutHeaderHeight(): Readonly<Ref<number>>;
@@ -4,6 +4,7 @@ import { useDialogsStore } from './stores/dialogs';
4
4
  import { useSnackbarsStore } from './stores/snackbars';
5
5
  import { useSystemAlertStore } from './stores/systemAlerts';
6
6
  import { useClickOutside } from './composables/clickOutside';
7
+ import { LAYOUT_HEADER_HEIGHT_CSS_VARIABLE, useLayoutHeaderHeight } from './composables/layoutHeaderHeight';
7
8
  import { useField } from './composables/field';
8
9
  declare const messages: {
9
10
  en: {
@@ -269,5 +270,7 @@ export { useDialogsStore };
269
270
  export { useSnackbarsStore };
270
271
  export { useSystemAlertStore };
271
272
  export { useClickOutside };
273
+ export { useLayoutHeaderHeight };
274
+ export { LAYOUT_HEADER_HEIGHT_CSS_VARIABLE };
272
275
  export { useField };
273
276
  export { SettingsStorage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.12.23",
3
+ "version": "0.12.24",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "generate-llm-txt": "node scripts/generate-llm-txt.js",
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <div class="min-h-full base-layout-sidebar">
2
+ <div
3
+ class="min-h-full base-layout-sidebar"
4
+ :style="headerHeightStyle"
5
+ >
3
6
  <TransitionRoot
4
7
  as="template"
5
8
  :show="showMobileMenu"
@@ -95,7 +98,10 @@
95
98
  class="flex flex-col min-h-full || xl:print:pl-0"
96
99
  :class="[collapsed ? 'xl:pl-14' : 'xl:pl-64']"
97
100
  >
98
- <div class="sticky top-0 left-0 z-10 shadow shrink-0 || print:hidden">
101
+ <div
102
+ ref="headerElement"
103
+ class="sticky top-0 left-0 z-10 shadow shrink-0 || print:hidden"
104
+ >
99
105
  <BaseSystemAlert
100
106
  v-for="systemAlert in systemAlerts"
101
107
  :key="systemAlert.id"
@@ -205,6 +211,7 @@ import {
205
211
  TransitionRoot,
206
212
  } from '@headlessui/vue';
207
213
  import { config } from '@/index';
214
+ import { provideLayoutHeaderHeight } from '../composables/layoutHeaderHeight';
208
215
  import { useSystemAlertStore } from '../stores/systemAlerts';
209
216
  import { Icon as BaseIcon } from '@iconify/vue';
210
217
  import BaseSystemAlert from './BaseSystemAlert.vue';
@@ -243,6 +250,15 @@ const systemAlerts = computed(() => {
243
250
  return systemAlertStore.systemAlerts;
244
251
  });
245
252
 
253
+ /**
254
+ * Header height (sticky system alerts + navbar), published as a CSS variable
255
+ * and via useLayoutHeaderHeight().
256
+ */
257
+
258
+ const headerElement = ref<HTMLElement | null>(null);
259
+
260
+ const { headerHeightStyle } = provideLayoutHeaderHeight(headerElement);
261
+
246
262
  /**
247
263
  * Show/Hide sidebar
248
264
  */
@@ -1,5 +1,8 @@
1
1
  <template>
2
- <div :class="classInternal">
2
+ <div
3
+ ref="headerElement"
4
+ :class="classInternal"
5
+ >
3
6
  <BaseSystemAlert
4
7
  v-for="systemAlert in systemAlerts"
5
8
  :key="systemAlert.id"
@@ -33,13 +36,14 @@
33
36
  </BaseNavbar>
34
37
  </div>
35
38
 
36
- <div>
39
+ <div :style="headerHeightStyle">
37
40
  <slot />
38
41
  </div>
39
42
  </template>
40
43
 
41
44
  <script lang="ts" setup>
42
45
  import { twMerge } from 'tailwind-merge';
46
+ import { provideLayoutHeaderHeight } from '../composables/layoutHeaderHeight';
43
47
  import { useSystemAlertStore } from '../stores/systemAlerts';
44
48
  import BaseNavbar from './BaseNavbar.vue';
45
49
  import BaseSystemAlert from './BaseSystemAlert.vue';
@@ -75,4 +79,13 @@ const systemAlertStore = useSystemAlertStore();
75
79
  const systemAlerts = computed(() => {
76
80
  return systemAlertStore.systemAlerts;
77
81
  });
82
+
83
+ /**
84
+ * Header height (sticky system alerts + navbar), published as a CSS variable
85
+ * and via useLayoutHeaderHeight().
86
+ */
87
+
88
+ const headerElement = ref<HTMLElement | null>(null);
89
+
90
+ const { headerHeightStyle } = provideLayoutHeaderHeight(headerElement);
78
91
  </script>
@@ -0,0 +1,87 @@
1
+ import type { InjectionKey, Ref, StyleValue } from 'vue';
2
+
3
+ /**
4
+ * The layout's sticky top header (system alerts + navbar) overlaps page
5
+ * content when it scrolls, so anything sticky or scroll-anchored inside the
6
+ * layout needs to know its rendered height. That height cannot be hardcoded:
7
+ * the navbar row depends on the layout `size` prop, and system alerts stack
8
+ * above it dynamically.
9
+ *
10
+ * The layout components measure their own header element and publish the
11
+ * height two ways:
12
+ *
13
+ * - The `--app-header-height` CSS variable, bound on the layout's content
14
+ * wrapper — for Tailwind/CSS (`top-header`, `scroll-mt-below-header`, ...,
15
+ * see the tailwindcss plugin's spacing tokens).
16
+ * - A provided reactive ref, read with `useLayoutHeaderHeight()` — for
17
+ * scripts (scrollspy root margins, canvas math, ...).
18
+ */
19
+
20
+ export const LAYOUT_HEADER_HEIGHT_CSS_VARIABLE = '--app-header-height';
21
+
22
+ const layoutHeaderHeightKey: InjectionKey<Readonly<Ref<number>>> = Symbol(
23
+ 'layoutHeaderHeight'
24
+ );
25
+
26
+ /**
27
+ * Internal — called once by each layout component (BaseLayoutSidebar,
28
+ * BaseLayoutStacked) with a template ref to its sticky header element.
29
+ * Returns the reactive height and a style binding carrying the CSS variable,
30
+ * to spread on the element that wraps the layout's page content.
31
+ */
32
+ export function provideLayoutHeaderHeight(
33
+ headerElement: Ref<HTMLElement | null>
34
+ ): {
35
+ headerHeight: Readonly<Ref<number>>;
36
+ headerHeightStyle: Readonly<Ref<StyleValue>>;
37
+ } {
38
+ const height = ref(0);
39
+
40
+ let observer: ResizeObserver | null = null;
41
+
42
+ watch(
43
+ headerElement,
44
+ (element) => {
45
+ observer?.disconnect();
46
+ observer = null;
47
+
48
+ if (!element) {
49
+ height.value = 0;
50
+
51
+ return;
52
+ }
53
+
54
+ observer = new ResizeObserver(() => {
55
+ height.value = element.getBoundingClientRect().height;
56
+ });
57
+
58
+ observer.observe(element);
59
+
60
+ height.value = element.getBoundingClientRect().height;
61
+ },
62
+ { immediate: true }
63
+ );
64
+
65
+ onBeforeUnmount(() => {
66
+ observer?.disconnect();
67
+ observer = null;
68
+ });
69
+
70
+ const headerHeight = readonly(height);
71
+
72
+ provide(layoutHeaderHeightKey, headerHeight);
73
+
74
+ const headerHeightStyle = computed<StyleValue>(() => ({
75
+ [LAYOUT_HEADER_HEIGHT_CSS_VARIABLE]: `${height.value}px`,
76
+ }));
77
+
78
+ return { headerHeight, headerHeightStyle };
79
+ }
80
+
81
+ /**
82
+ * The rendered height (px) of the layout's sticky top header. Zero when used
83
+ * outside a layout component.
84
+ */
85
+ export function useLayoutHeaderHeight(): Readonly<Ref<number>> {
86
+ return inject(layoutHeaderHeightKey, readonly(ref(0)));
87
+ }
package/src/index.ts CHANGED
@@ -8,6 +8,10 @@ import { useDialogsStore } from './stores/dialogs';
8
8
  import { useSnackbarsStore } from './stores/snackbars';
9
9
  import { useSystemAlertStore } from './stores/systemAlerts';
10
10
  import { useClickOutside } from './composables/clickOutside';
11
+ import {
12
+ LAYOUT_HEADER_HEIGHT_CSS_VARIABLE,
13
+ useLayoutHeaderHeight,
14
+ } from './composables/layoutHeaderHeight';
11
15
  import { useField } from './composables/field';
12
16
 
13
17
  const messages = { en, fr };
@@ -134,5 +138,7 @@ export { useDialogsStore };
134
138
  export { useSnackbarsStore };
135
139
  export { useSystemAlertStore };
136
140
  export { useClickOutside };
141
+ export { useLayoutHeaderHeight };
142
+ export { LAYOUT_HEADER_HEIGHT_CSS_VARIABLE };
137
143
  export { useField };
138
144
  export { SettingsStorage };