sprintify-ui 0.12.22 → 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.
- package/dist/sprintify-ui.es.js +5786 -5744
- package/dist/tailwindcss/theme.js +9 -0
- package/dist/types/composables/layoutHeaderHeight.d.ts +33 -0
- package/dist/types/index.d.ts +3 -0
- package/package.json +1 -1
- package/src/components/BaseBadge.vue +13 -18
- package/src/components/BaseButton.vue +9 -13
- package/src/components/BaseLayoutSidebar.vue +18 -2
- package/src/components/BaseLayoutStacked.vue +15 -2
- package/src/composables/layoutHeaderHeight.ts +87 -0
- package/src/index.ts +6 -0
- package/src/utils/colors.ts +19 -14
|
@@ -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>>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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
|
@@ -43,25 +43,20 @@ const props = withDefaults(
|
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
const colorStyle = computed((): Record<string, string> => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
styles.borderStyle = 'solid';
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return styles;
|
|
61
|
-
} catch {
|
|
62
|
-
console.warn(`[BaseBadge] "${props.color}" is not a valid color.`);
|
|
63
|
-
return {};
|
|
46
|
+
const config = getColorConfig(props.color, props.contrast == 'high');
|
|
47
|
+
|
|
48
|
+
const styles: Record<string, string> = {
|
|
49
|
+
backgroundColor: config.backgroundColor,
|
|
50
|
+
color: config.textColor,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
if (props.bordered) {
|
|
54
|
+
styles.borderColor = config.borderColor;
|
|
55
|
+
styles.borderWidth = '1px';
|
|
56
|
+
styles.borderStyle = 'solid';
|
|
64
57
|
}
|
|
58
|
+
|
|
59
|
+
return styles;
|
|
65
60
|
});
|
|
66
61
|
|
|
67
62
|
const sizeInternal = computed(() => {
|
|
@@ -207,19 +207,15 @@ const classes = computed(() => {
|
|
|
207
207
|
|
|
208
208
|
const styles = computed(() => {
|
|
209
209
|
if (props.color && !(props.color in colors)) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
};
|
|
220
|
-
} catch {
|
|
221
|
-
console.warn(`[BaseButton] "${props.color}" is not a valid color.`);
|
|
222
|
-
}
|
|
210
|
+
const config = getColorConfig(props.color);
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
backgroundColor: config.backgroundColor,
|
|
214
|
+
color: config.textColor,
|
|
215
|
+
borderColor: config.borderColor,
|
|
216
|
+
borderWidth: '1px',
|
|
217
|
+
borderStyle: 'solid',
|
|
218
|
+
};
|
|
223
219
|
}
|
|
224
220
|
|
|
225
221
|
return {};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
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
|
|
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
|
|
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 };
|
package/src/utils/colors.ts
CHANGED
|
@@ -211,7 +211,7 @@ const colorNames = {
|
|
|
211
211
|
} as Record<string, string>;
|
|
212
212
|
|
|
213
213
|
export function getColorConfig(color: string, contrast = false): ColorConfig {
|
|
214
|
-
|
|
214
|
+
const originalColor = color;
|
|
215
215
|
color = colorNames[color] || color;
|
|
216
216
|
|
|
217
217
|
const colorConfig = palette[color]?.[contrast ? 'high' : 'low'];
|
|
@@ -220,20 +220,25 @@ export function getColorConfig(color: string, contrast = false): ColorConfig {
|
|
|
220
220
|
return colorConfig;
|
|
221
221
|
}
|
|
222
222
|
|
|
223
|
-
|
|
223
|
+
try {
|
|
224
|
+
const c = getContrast(color, 'white');
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
let borderColor = 'transparent';
|
|
227
|
+
let textColor = 'white';
|
|
227
228
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
if (c < 2) {
|
|
230
|
+
textColor = darken(color, 0.5);
|
|
231
|
+
borderColor = darken(color, 0.1);
|
|
232
|
+
}
|
|
232
233
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
234
|
+
return {
|
|
235
|
+
backgroundColor: color,
|
|
236
|
+
textColor: textColor,
|
|
237
|
+
borderColor: borderColor,
|
|
238
|
+
color: color,
|
|
239
|
+
};
|
|
240
|
+
} catch {
|
|
241
|
+
console.warn(`[getColorConfig] "${originalColor}" is not a valid color. Falling back to gray.`);
|
|
242
|
+
return palette.gray[contrast ? 'high' : 'low'];
|
|
243
|
+
}
|
|
239
244
|
}
|