@saasmakers/ui 2.0.21 → 2.0.23
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/app/components/bases/BaseAction.vue +0 -1
- package/app/components/bases/BaseAvatar.vue +5 -3
- package/app/components/bases/BaseButton.vue +20 -16
- package/app/components/fields/FieldLabel.vue +2 -0
- package/app/components/layout/LayoutBottomSheet.vue +1 -1
- package/app/composables/useDialog.ts +7 -48
- package/app/types/bases.d.ts +6 -2
- package/app/types/global.d.ts +2 -0
- package/package.json +1 -1
|
@@ -5,10 +5,10 @@ import type { BaseAvatar } from '../../types/bases'
|
|
|
5
5
|
const props = withDefaults(defineProps<BaseAvatar>(), {
|
|
6
6
|
bordered: true,
|
|
7
7
|
borderWidth: 3,
|
|
8
|
-
circular: true,
|
|
9
8
|
editable: false,
|
|
10
9
|
maxSizeMb: undefined,
|
|
11
10
|
shadow: true,
|
|
11
|
+
shape: 'circle',
|
|
12
12
|
src: undefined,
|
|
13
13
|
to: undefined,
|
|
14
14
|
})
|
|
@@ -102,7 +102,8 @@ function onMouseLeave() {
|
|
|
102
102
|
:aria-label="editable ? t('edit') : undefined"
|
|
103
103
|
class="relative h-full w-full overflow-hidden"
|
|
104
104
|
:class="{
|
|
105
|
-
'rounded-full':
|
|
105
|
+
'rounded-full': shape === 'circle',
|
|
106
|
+
'rounded-lg': shape === 'square',
|
|
106
107
|
'shadow': shadow,
|
|
107
108
|
'cursor-pointer': isClickable,
|
|
108
109
|
}"
|
|
@@ -116,7 +117,8 @@ function onMouseLeave() {
|
|
|
116
117
|
<img
|
|
117
118
|
class="h-full w-full object-cover drag-none"
|
|
118
119
|
:class="{
|
|
119
|
-
'rounded-full':
|
|
120
|
+
'rounded-full': shape === 'circle',
|
|
121
|
+
'rounded-lg': shape === 'square',
|
|
120
122
|
'border-white dark:border-white': bordered,
|
|
121
123
|
'border-2': bordered && borderWidth === 2,
|
|
122
124
|
'border-3': bordered && borderWidth === 3,
|
|
@@ -4,7 +4,6 @@ import type { BaseButton } from '../../types/bases'
|
|
|
4
4
|
|
|
5
5
|
const props = withDefaults(defineProps<BaseButton>(), {
|
|
6
6
|
background: true,
|
|
7
|
-
circular: false,
|
|
8
7
|
color: 'black',
|
|
9
8
|
confirmation: false,
|
|
10
9
|
disabled: false,
|
|
@@ -15,6 +14,7 @@ const props = withDefaults(defineProps<BaseButton>(), {
|
|
|
15
14
|
light: false,
|
|
16
15
|
loading: false,
|
|
17
16
|
reverse: false,
|
|
17
|
+
shape: 'default',
|
|
18
18
|
size: 'base',
|
|
19
19
|
text: '',
|
|
20
20
|
to: undefined,
|
|
@@ -33,6 +33,10 @@ defineSlots<{
|
|
|
33
33
|
const confirming = ref(false)
|
|
34
34
|
const { t } = useI18n()
|
|
35
35
|
|
|
36
|
+
const hasFixedSize = computed(() => {
|
|
37
|
+
return props.shape === 'square' || props.shape === 'circle'
|
|
38
|
+
})
|
|
39
|
+
|
|
36
40
|
function onClick(event: MouseEvent) {
|
|
37
41
|
if (!props.loading) {
|
|
38
42
|
if (props.confirmation) {
|
|
@@ -56,21 +60,21 @@ function onClick(event: MouseEvent) {
|
|
|
56
60
|
:is="to ? NuxtLinkLocale : 'button'"
|
|
57
61
|
class="relative inline-block select-none appearance-none border uppercase focus-visible:outline-none"
|
|
58
62
|
:class="{
|
|
59
|
-
'flex items-center justify-center':
|
|
63
|
+
'flex items-center justify-center': hasFixedSize,
|
|
60
64
|
'cursor-not-allowed opacity-50': disabled,
|
|
61
65
|
'cursor-wait': loading,
|
|
62
66
|
'cursor-pointer': !loading,
|
|
63
67
|
'w-full': fullWidth,
|
|
64
68
|
'bg-transparent shadow-none': light,
|
|
65
69
|
'shadow-sm': !light,
|
|
66
|
-
'rounded-lg':
|
|
67
|
-
'rounded-full':
|
|
68
|
-
'h-6 w-6': size === '2xs' &&
|
|
69
|
-
'h-8 w-8': size === 'xs' &&
|
|
70
|
-
'h-10 w-10': size === 'sm' &&
|
|
71
|
-
'h-12 w-12': size === 'base' &&
|
|
72
|
-
'h-14 w-14': size === 'lg' &&
|
|
73
|
-
'h-16 w-16': size === 'xl' &&
|
|
70
|
+
'rounded-lg': shape !== 'circle',
|
|
71
|
+
'rounded-full': shape === 'circle',
|
|
72
|
+
'h-6 w-6': size === '2xs' && hasFixedSize,
|
|
73
|
+
'h-8 w-8': size === 'xs' && hasFixedSize && !confirming,
|
|
74
|
+
'h-10 w-10': size === 'sm' && hasFixedSize && !confirming,
|
|
75
|
+
'h-12 w-12': size === 'base' && hasFixedSize && !confirming,
|
|
76
|
+
'h-14 w-14': size === 'lg' && hasFixedSize && !confirming,
|
|
77
|
+
'h-16 w-16': size === 'xl' && hasFixedSize && !confirming,
|
|
74
78
|
'border-gray-900 dark:border-gray-100 bg-gray-900 dark:bg-gray-100 text-white dark:text-black': color === 'black' && !light,
|
|
75
79
|
'hover:bg-black dark:hover:bg-white hover:text-white dark:hover:text-black': color === 'black' && !light && !disabled && !loading,
|
|
76
80
|
'border-gray-900 dark:border-gray-100 text-gray-900 dark:text-gray-100': color === 'black' && light,
|
|
@@ -94,12 +98,12 @@ function onClick(event: MouseEvent) {
|
|
|
94
98
|
'border-white dark:border-gray-900 text-white dark:text-black': color === 'white' && light,
|
|
95
99
|
'hover:border-black dark:hover:border-white hover:text-black dark:hover:text-white': color !== 'white' && light && !disabled && !loading,
|
|
96
100
|
'hover:bg-white dark:hover:bg-gray-900 hover:text-gray-800 dark:hover:text-gray-200': color === 'white' && light && !disabled && !loading,
|
|
97
|
-
'h-6 px-3': size === '2xs' && (!
|
|
98
|
-
'h-8 px-4': size === 'xs' && (!
|
|
99
|
-
'h-10 px-5': size === 'sm' && (!
|
|
100
|
-
'h-12 px-6': size === 'base' && (!
|
|
101
|
-
'h-13 px-6.5': size === 'lg' && (!
|
|
102
|
-
'h-14 px-7': size === 'xl' && (!
|
|
101
|
+
'h-6 px-3': size === '2xs' && (!hasFixedSize || confirming),
|
|
102
|
+
'h-8 px-4': size === 'xs' && (!hasFixedSize || confirming),
|
|
103
|
+
'h-10 px-5': size === 'sm' && (!hasFixedSize || confirming),
|
|
104
|
+
'h-12 px-6': size === 'base' && (!hasFixedSize || confirming),
|
|
105
|
+
'h-13 px-6.5': size === 'lg' && (!hasFixedSize || confirming),
|
|
106
|
+
'h-14 px-7': size === 'xl' && (!hasFixedSize || confirming),
|
|
103
107
|
}"
|
|
104
108
|
:disabled="disabled"
|
|
105
109
|
:to="to"
|
|
@@ -14,7 +14,7 @@ const dragMoveThreshold = 8
|
|
|
14
14
|
const visible = defineModel<boolean>({ default: false })
|
|
15
15
|
const contentRef = ref<HTMLElement>()
|
|
16
16
|
const panelRef = ref<HTMLElement>()
|
|
17
|
-
const { release: onAfterLeave } = useDialog(visible, panelRef
|
|
17
|
+
const { release: onAfterLeave } = useDialog(visible, panelRef)
|
|
18
18
|
|
|
19
19
|
const drag = reactive({
|
|
20
20
|
closing: false,
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
interface UseDialogOptions {
|
|
2
|
-
ignoreClass: string
|
|
3
|
-
}
|
|
4
|
-
|
|
5
1
|
export default function useDialog(
|
|
6
2
|
visible: Ref<boolean>,
|
|
7
3
|
panelRef: Ref<HTMLElement | undefined>,
|
|
8
|
-
options: UseDialogOptions,
|
|
9
4
|
) {
|
|
10
5
|
const focusableSelector = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
|
|
11
|
-
const fixedElementPadding = new Map<HTMLElement, string>()
|
|
12
6
|
|
|
13
|
-
let
|
|
14
|
-
let savedBodyPaddingRight = ''
|
|
7
|
+
let savedRootOverflow = ''
|
|
15
8
|
|
|
16
9
|
function getFocusableElements(container: HTMLElement) {
|
|
17
10
|
return Array.from(container.querySelectorAll<HTMLElement>(focusableSelector))
|
|
@@ -20,39 +13,12 @@ export default function useDialog(
|
|
|
20
13
|
})
|
|
21
14
|
}
|
|
22
15
|
|
|
16
|
+
// Rely on the global `scrollbar-gutter: stable` (html/body) to keep the gutter
|
|
17
|
+
// reserved while the scroll container is locked, so no manual padding
|
|
18
|
+
// compensation is needed and no horizontal layout shift occurs.
|
|
23
19
|
function lockBodyScroll() {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
savedBodyOverflow = document.body.style.overflow
|
|
27
|
-
savedBodyPaddingRight = document.body.style.paddingRight
|
|
28
|
-
document.body.style.overflow = 'hidden'
|
|
29
|
-
|
|
30
|
-
if (scrollbarWidth > 0) {
|
|
31
|
-
document.body.style.paddingRight = `${scrollbarWidth}px`
|
|
32
|
-
|
|
33
|
-
for (const element of document.querySelectorAll('body *')) {
|
|
34
|
-
if (!(element instanceof HTMLElement)) {
|
|
35
|
-
continue
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Skip the dialog's own teleported elements so they are not padded.
|
|
39
|
-
if (element.classList.contains(options.ignoreClass)) {
|
|
40
|
-
continue
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const { position } = getComputedStyle(element)
|
|
44
|
-
|
|
45
|
-
if (position !== 'fixed' && position !== 'sticky') {
|
|
46
|
-
continue
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
fixedElementPadding.set(element, element.style.paddingRight)
|
|
50
|
-
|
|
51
|
-
const currentPadding = Number.parseFloat(getComputedStyle(element).paddingRight) || 0
|
|
52
|
-
|
|
53
|
-
element.style.paddingRight = `${currentPadding + scrollbarWidth}px`
|
|
54
|
-
}
|
|
55
|
-
}
|
|
20
|
+
savedRootOverflow = document.documentElement.style.overflow
|
|
21
|
+
document.documentElement.style.overflow = 'hidden'
|
|
56
22
|
}
|
|
57
23
|
|
|
58
24
|
function onFocusTrap(event: KeyboardEvent) {
|
|
@@ -90,14 +56,7 @@ export default function useDialog(
|
|
|
90
56
|
}
|
|
91
57
|
|
|
92
58
|
function unlockBodyScroll() {
|
|
93
|
-
document.
|
|
94
|
-
document.body.style.paddingRight = savedBodyPaddingRight
|
|
95
|
-
|
|
96
|
-
for (const [element, paddingRight] of fixedElementPadding) {
|
|
97
|
-
element.style.paddingRight = paddingRight
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
fixedElementPadding.clear()
|
|
59
|
+
document.documentElement.style.overflow = savedRootOverflow
|
|
101
60
|
}
|
|
102
61
|
|
|
103
62
|
// Restores scroll lock and focus trap once the leave transition has finished.
|
package/app/types/bases.d.ts
CHANGED
|
@@ -24,13 +24,17 @@ export interface BaseAlert {
|
|
|
24
24
|
// Global types for bases
|
|
25
25
|
export type BaseAlignment = 'center' | 'left' | 'right'
|
|
26
26
|
|
|
27
|
+
export type BaseButtonShape = 'circle' | 'default' | 'square'
|
|
28
|
+
|
|
29
|
+
export type BaseAvatarShape = 'circle' | 'square'
|
|
30
|
+
|
|
27
31
|
export interface BaseAvatar {
|
|
28
32
|
bordered?: boolean
|
|
29
33
|
borderWidth?: number
|
|
30
|
-
circular?: boolean
|
|
31
34
|
editable?: boolean
|
|
32
35
|
maxSizeMb?: number
|
|
33
36
|
shadow?: boolean
|
|
37
|
+
shape?: BaseAvatarShape
|
|
34
38
|
src?: string
|
|
35
39
|
to?: RouteLocationNamedI18n
|
|
36
40
|
}
|
|
@@ -49,7 +53,6 @@ export type BaseBorderedColor = 'black' | 'gray'
|
|
|
49
53
|
|
|
50
54
|
export interface BaseButton {
|
|
51
55
|
background?: boolean
|
|
52
|
-
circular?: boolean
|
|
53
56
|
color?: BaseColor
|
|
54
57
|
confirmation?: boolean
|
|
55
58
|
disabled?: boolean
|
|
@@ -60,6 +63,7 @@ export interface BaseButton {
|
|
|
60
63
|
light?: boolean
|
|
61
64
|
loading?: boolean
|
|
62
65
|
reverse?: boolean
|
|
66
|
+
shape?: BaseButtonShape
|
|
63
67
|
size?: BaseButtonSize
|
|
64
68
|
text?: BaseTextText
|
|
65
69
|
to?: RouteLocationNamedI18n
|
package/app/types/global.d.ts
CHANGED
|
@@ -66,6 +66,8 @@ declare global {
|
|
|
66
66
|
type BaseQuoteSize = import('./bases').BaseQuoteSize
|
|
67
67
|
type BaseShortcut = import('./bases').BaseShortcut
|
|
68
68
|
type BaseSize = import('./bases').BaseSize
|
|
69
|
+
type BaseButtonShape = import('./bases').BaseButtonShape
|
|
70
|
+
type BaseAvatarShape = import('./bases').BaseAvatarShape
|
|
69
71
|
type BaseSpinner = import('./bases').BaseSpinner
|
|
70
72
|
type BaseStatus = import('./bases').BaseStatus
|
|
71
73
|
type BaseTag = import('./bases').BaseTag
|