Skip to content

Commit be5b32e

Browse files
author
issayah
committed
Setupified Bavatar/...Group & Breadcrumb
1 parent 82d466b commit be5b32e

File tree

11 files changed

+280
-283
lines changed

11 files changed

+280
-283
lines changed

src/components/BAvatar/BAvatar.vue

Lines changed: 174 additions & 168 deletions
Original file line numberDiff line numberDiff line change
@@ -24,179 +24,185 @@
2424
</component>
2525
</template>
2626

27-
<script lang="ts">
28-
import {isEmptySlot} from '../../utils/dom'
29-
import {computed, defineComponent, inject, PropType, StyleValue} from 'vue'
30-
import {ColorVariant, InputSize} from '../../types'
31-
import {isNumber, isNumeric, isString} from '../../utils/inspect'
32-
import {toFloat} from '../../utils/number'
27+
<script setup lang="ts">
28+
// import type { BAvatarProps, BAvatarEmits } from '@/types/components'
29+
import {isEmptySlot} from '@/utils/dom'
30+
import type {BAvatarGroupParentData} from '@/types/components'
31+
import {computed, inject, StyleValue, useSlots} from 'vue'
32+
import type {ColorVariant, InputSize} from '@/types'
33+
import {isNumber, isNumeric, isString} from '@/utils/inspect'
34+
import {toFloat} from '@/utils/number'
3335
import {injectionKey} from './BAvatarGroup.vue'
3436
37+
interface Props {
38+
alt: string
39+
ariaLabel: string
40+
badge?: boolean | string
41+
badgeLeft?: boolean
42+
badgeOffset?: string
43+
badgeTop?: boolean
44+
badgeVariant?: ColorVariant
45+
button?: boolean
46+
buttonType?: string
47+
disabled?: boolean
48+
icon?: string
49+
rounded?: boolean | string
50+
size?: InputSize | string | number
51+
square?: boolean
52+
src?: string
53+
text?: string
54+
textVariant?: ColorVariant // not standard BootstrapVue props
55+
variant?: ColorVariant
56+
}
57+
58+
const props = withDefaults(defineProps<Props>(), {
59+
alt: 'avatar',
60+
badge: false,
61+
badgeLeft: false,
62+
badgeTop: false,
63+
badgeVariant: 'primary',
64+
button: false,
65+
buttonType: 'button',
66+
disabled: false,
67+
rounded: 'circle',
68+
square: false,
69+
textVariant: undefined,
70+
variant: 'secondary',
71+
})
72+
73+
interface Emits {
74+
(e: 'click', value: MouseEvent): void
75+
(e: 'img-error', value: Event): void
76+
}
77+
78+
const emit = defineEmits<Emits>()
79+
80+
const slots = useSlots()
81+
82+
const SIZES = ['sm', null, 'lg']
83+
const FONT_SIZE_SCALE = 0.4
84+
const BADGE_FONT_SIZE_SCALE = FONT_SIZE_SCALE * 0.7
85+
86+
const parentData = inject<BAvatarGroupParentData | null>(injectionKey, null)
87+
88+
const computeContrastVariant = (colorVariant: ColorVariant): ColorVariant => {
89+
const variant = colorVariant
90+
91+
if (variant === 'light') return 'dark'
92+
if (variant === 'warning') return 'dark'
93+
return 'light'
94+
}
95+
96+
const hasDefaultSlot = computed<boolean>(() => !isEmptySlot(slots.default))
97+
const hasBadgeSlot = computed<boolean>(() => !isEmptySlot(slots.badge))
98+
const showBadge = computed<string | boolean>(
99+
() => props.badge || props.badge === '' || hasBadgeSlot.value
100+
)
101+
102+
const computedSize = computed<string | null>(() =>
103+
parentData?.size ? parentData.size : computeSize(props.size)
104+
)
105+
106+
const computedVariant = computed<ColorVariant>(() =>
107+
parentData?.variant ? parentData.variant : props.variant
108+
)
109+
110+
const computedRounded = computed<string | boolean>(() =>
111+
parentData?.rounded ? parentData.rounded : props.rounded
112+
)
113+
114+
const attrs = computed(() => ({
115+
'aria-label': props.ariaLabel || null,
116+
'disabled': props.disabled || null,
117+
}))
118+
119+
const badgeClasses = computed(() => ({
120+
[`bg-${props.badgeVariant}`]: props.badgeVariant,
121+
}))
122+
123+
const badgeText = computed<string | false>(() => (props.badge === true ? '' : props.badge))
124+
const badgeTextClasses = computed<string>(() => {
125+
const textVariant = computeContrastVariant(props.badgeVariant)
126+
return `text-${textVariant}`
127+
})
128+
129+
const classes = computed(() => ({
130+
[`b-avatar-${props.size}`]: props.size && SIZES.indexOf(computeSize(props.size)) !== -1,
131+
[`bg-${computedVariant.value}`]: computedVariant.value,
132+
[`badge`]: !props.button && computedVariant.value && hasDefaultSlot.value,
133+
rounded: computedRounded.value === '' || computedRounded.value === true,
134+
[`rounded-circle`]: !props.square && computedRounded.value === 'circle',
135+
[`rounded-0`]: props.square || computedRounded.value === '0',
136+
[`rounded-1`]: !props.square && computedRounded.value === 'sm',
137+
[`rounded-3`]: !props.square && computedRounded.value === 'lg',
138+
[`rounded-top`]: !props.square && computedRounded.value === 'top',
139+
[`rounded-bottom`]: !props.square && computedRounded.value === 'bottom',
140+
[`rounded-start`]: !props.square && computedRounded.value === 'left',
141+
[`rounded-end`]: !props.square && computedRounded.value === 'right',
142+
btn: props.button,
143+
[`btn-${computedVariant.value}`]: props.button ? computedVariant.value : null,
144+
}))
145+
146+
const textClasses = computed<string>(() => {
147+
const textVariant = props.textVariant || computeContrastVariant(computedVariant.value)
148+
return `text-${textVariant}`
149+
})
150+
151+
const iconName = computed<string | undefined>(() => {
152+
// TODO this should work with any icon font, eg icon="fa fa-cogs"
153+
if (props.icon) return props.icon
154+
if (!props.text && !props.src) return 'person-fill'
155+
return undefined
156+
})
157+
158+
const badgeStyle = computed<StyleValue>(() => {
159+
const offset = props.badgeOffset || '0px'
160+
const fontSize =
161+
SIZES.indexOf(computedSize.value || null) === -1
162+
? `calc(${computedSize.value} * ${BADGE_FONT_SIZE_SCALE})`
163+
: ''
164+
return {
165+
fontSize: fontSize || '',
166+
top: props.badgeTop ? offset : '',
167+
bottom: props.badgeTop ? '' : offset,
168+
left: props.badgeLeft ? offset : '',
169+
right: props.badgeLeft ? '' : offset,
170+
}
171+
})
172+
173+
const fontStyle = computed<StyleValue>(() => {
174+
const fontSize =
175+
SIZES.indexOf(computedSize.value || null) === -1
176+
? `calc(${computedSize.value} * ${FONT_SIZE_SCALE})`
177+
: null
178+
return fontSize ? {fontSize} : {}
179+
})
180+
181+
const marginStyle = computed(() => {
182+
const overlapScale = parentData?.overlapScale?.value || 0
183+
184+
const value =
185+
computedSize.value && overlapScale ? `calc(${computedSize.value} * -${overlapScale})` : null
186+
return value ? {marginLeft: value, marginRight: value} : {}
187+
})
188+
189+
const tag = computed<string>(() => (props.button ? props.buttonType : 'span'))
190+
const tagStyle = computed(() => ({
191+
...marginStyle.value,
192+
width: computedSize.value,
193+
height: computedSize.value,
194+
}))
195+
196+
const clicked = (e: MouseEvent): void => {
197+
if (!props.disabled && props.button) emit('click', e)
198+
}
199+
const onImgError = (e: Event): void => emit('img-error', e)
200+
</script>
201+
202+
<script lang="ts">
35203
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
36204
export const computeSize = (value: any): string | null => {
37205
const calcValue = isString(value) && isNumeric(value) ? toFloat(value, 0) : value
38206
return isNumber(calcValue) ? `${calcValue}px` : calcValue || null
39207
}
40-
41-
export default defineComponent({
42-
name: 'BAvatar',
43-
props: {
44-
alt: {type: String, default: 'avatar'},
45-
ariaLabel: {type: String, required: false},
46-
badge: {type: [Boolean, String], default: false},
47-
badgeLeft: {type: Boolean, default: false},
48-
badgeOffset: {type: String, required: false},
49-
badgeTop: {type: Boolean, default: false},
50-
badgeVariant: {type: String as PropType<ColorVariant>, default: 'primary'},
51-
button: {type: Boolean, default: false},
52-
buttonType: {type: String, default: 'button'},
53-
disabled: {type: Boolean, default: false},
54-
icon: {type: String, required: false},
55-
rounded: {type: [Boolean, String], default: 'circle'},
56-
size: {type: [String, Number] as PropType<InputSize | string | number>, required: false},
57-
square: {type: Boolean, default: false},
58-
src: {type: String, required: false},
59-
text: {type: String, required: false},
60-
textVariant: {type: String as PropType<ColorVariant | undefined>, default: undefined}, // not standard BootstrapVue props
61-
variant: {type: String as PropType<ColorVariant>, default: 'secondary'},
62-
},
63-
emits: ['click', 'img-error'],
64-
setup(props, {emit, slots}) {
65-
const SIZES = ['sm', null, 'lg']
66-
const FONT_SIZE_SCALE = 0.4
67-
const BADGE_FONT_SIZE_SCALE = FONT_SIZE_SCALE * 0.7
68-
69-
const parentData = inject(injectionKey, null)
70-
71-
const computeContrastVariant = (colorVariant: ColorVariant): ColorVariant => {
72-
const variant = colorVariant
73-
74-
if (variant === 'light') return 'dark'
75-
if (variant === 'warning') return 'dark'
76-
return 'light'
77-
}
78-
79-
const hasDefaultSlot = computed(() => !isEmptySlot(slots.default))
80-
const hasBadgeSlot = computed(() => !isEmptySlot(slots.badge))
81-
const showBadge = computed(() => props.badge || props.badge === '' || hasBadgeSlot.value)
82-
83-
const computedSize = computed(() =>
84-
parentData?.size ? parentData.size : computeSize(props.size)
85-
)
86-
87-
const computedVariant = computed(() =>
88-
parentData?.variant ? parentData.variant : props.variant
89-
)
90-
91-
const computedRounded = computed(() =>
92-
parentData?.rounded ? parentData.rounded : props.rounded
93-
)
94-
95-
const attrs = computed(() => ({
96-
'aria-label': props.ariaLabel || null,
97-
'disabled': props.disabled || null,
98-
}))
99-
100-
const badgeClasses = computed(() => ({
101-
[`bg-${props.badgeVariant}`]: props.badgeVariant,
102-
}))
103-
104-
const badgeText = computed(() => (props.badge === true ? '' : props.badge))
105-
const badgeTextClasses = computed(() => {
106-
const textVariant = computeContrastVariant(props.badgeVariant)
107-
return `text-${textVariant}`
108-
})
109-
110-
const classes = computed(() => ({
111-
[`b-avatar-${props.size}`]: props.size && SIZES.indexOf(computeSize(props.size)) !== -1,
112-
[`bg-${computedVariant.value}`]: computedVariant.value,
113-
[`badge`]: !props.button && computedVariant.value && hasDefaultSlot.value,
114-
rounded: computedRounded.value === '' || computedRounded.value === true,
115-
[`rounded-circle`]: !props.square && computedRounded.value === 'circle',
116-
[`rounded-0`]: props.square || computedRounded.value === '0',
117-
[`rounded-1`]: !props.square && computedRounded.value === 'sm',
118-
[`rounded-3`]: !props.square && computedRounded.value === 'lg',
119-
[`rounded-top`]: !props.square && computedRounded.value === 'top',
120-
[`rounded-bottom`]: !props.square && computedRounded.value === 'bottom',
121-
[`rounded-start`]: !props.square && computedRounded.value === 'left',
122-
[`rounded-end`]: !props.square && computedRounded.value === 'right',
123-
btn: props.button,
124-
[`btn-${computedVariant.value}`]: props.button ? computedVariant.value : null,
125-
}))
126-
127-
const textClasses = computed(() => {
128-
const textVariant = props.textVariant || computeContrastVariant(computedVariant.value)
129-
return `text-${textVariant}`
130-
})
131-
132-
const iconName = computed(() => {
133-
// TODO this should work with any icon font, eg icon="fa fa-cogs"
134-
if (props.icon) return props.icon
135-
if (!props.text && !props.src) return 'person-fill'
136-
return undefined
137-
})
138-
139-
const badgeStyle = computed((): StyleValue => {
140-
const offset = props.badgeOffset || '0px'
141-
const fontSize =
142-
SIZES.indexOf(computedSize.value || null) === -1
143-
? `calc(${computedSize.value} * ${BADGE_FONT_SIZE_SCALE})`
144-
: ''
145-
return {
146-
fontSize: fontSize || '',
147-
top: props.badgeTop ? offset : '',
148-
bottom: props.badgeTop ? '' : offset,
149-
left: props.badgeLeft ? offset : '',
150-
right: props.badgeLeft ? '' : offset,
151-
}
152-
})
153-
154-
const fontStyle = computed((): StyleValue => {
155-
const fontSize =
156-
SIZES.indexOf(computedSize.value || null) === -1
157-
? `calc(${computedSize.value} * ${FONT_SIZE_SCALE})`
158-
: null
159-
return fontSize ? {fontSize} : {}
160-
})
161-
162-
const marginStyle = computed(() => {
163-
const overlapScale = parentData?.overlapScale?.value || 0
164-
165-
const value =
166-
computedSize.value && overlapScale ? `calc(${computedSize.value} * -${overlapScale})` : null
167-
return value ? {marginLeft: value, marginRight: value} : {}
168-
})
169-
170-
const tag = computed(() => (props.button ? props.buttonType : 'span'))
171-
const tagStyle = computed(() => ({
172-
...marginStyle.value,
173-
width: computedSize.value,
174-
height: computedSize.value,
175-
}))
176-
177-
const clicked = function (e: PointerEvent) {
178-
if (!props.disabled && props.button) emit('click', e)
179-
}
180-
const onImgError = (e: Event) => emit('img-error', e)
181-
182-
return {
183-
attrs,
184-
badgeClasses,
185-
badgeStyle,
186-
badgeText,
187-
badgeTextClasses,
188-
classes,
189-
clicked,
190-
fontStyle,
191-
hasBadgeSlot,
192-
hasDefaultSlot,
193-
iconName,
194-
onImgError,
195-
showBadge,
196-
tag,
197-
tagStyle,
198-
textClasses,
199-
}
200-
},
201-
})
202208
</script>

0 commit comments

Comments
 (0)