添加全局字号,动态调整大小

This commit is contained in:
icssoa
2025-08-13 15:21:16 +08:00
parent b04cfb0066
commit e7b17195dd
49 changed files with 682 additions and 292 deletions

View File

@@ -15,7 +15,12 @@
<slot name="prepend"></slot>
<view class="cl-action-sheet__description" v-if="config.description != ''">
<text class="text-surface-400 text-md">{{ config.description }}</text>
<cl-text
:pt="{
className: '!text-surface-400 !text-md text-center'
}"
>{{ config.description }}</cl-text
>
</view>
<view class="cl-action-sheet__list" :class="[pt.list?.className]">

View File

@@ -13,10 +13,16 @@
},
pt.className
]"
:style="badgeStyle"
>
<text class="cl-badge__text" v-if="!dot">
<cl-text
:pt="{
className: parseClass(['cl-badge__text', pt.text?.className])
}"
v-if="!dot"
>
{{ value }}
</text>
</cl-text>
<slot></slot>
</view>
@@ -25,8 +31,9 @@
<script setup lang="ts">
import { computed } from "vue";
import type { PropType } from "vue";
import type { Type } from "../../types";
import { parsePt } from "@/cool";
import type { PassThroughProps, Type } from "../../types";
import { parseClass, parsePt } from "@/cool";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-badge"
@@ -55,11 +62,39 @@ const props = defineProps({
}
});
const { getRpx } = useSize();
type PassThrough = {
className?: string;
text?: PassThroughProps;
};
const pt = computed(() => parsePt<PassThrough>(props.pt));
const badgeStyle = computed(() => {
const style = {};
if (props.dot) {
style["height"] = getRpx(10);
style["width"] = getRpx(10);
style["minWidth"] = getRpx(10);
style["padding"] = 0;
} else {
style["height"] = getRpx(30);
style["minWidth"] = getRpx(30);
style["padding"] = `0 ${getRpx(6)}`;
}
if (props.position) {
style["transform"] = "translate(50%, -50%)";
if (props.dot) {
style["transform"] = `translate(-${getRpx(5)}, ${getRpx(5)})`;
}
}
return style;
});
</script>
<style lang="scss" scoped>
@@ -67,29 +102,12 @@ const pt = computed(() => parsePt<PassThrough>(props.pt));
@apply flex flex-row items-center justify-center;
@apply rounded-full;
height: 30rpx;
min-width: 30rpx;
padding: 0 6rpx;
&__text {
@apply text-white;
font-size: 18rpx;
}
&--dot {
height: 10rpx;
width: 10rpx;
min-width: 10rpx;
padding: 0;
@apply text-white text-sm;
}
&--position {
@apply absolute z-10 right-0 top-0;
transform: translate(50%, -50%);
&.cl-badge--dot {
transform: translate(-5rpx, 5rpx);
}
}
}
</style>

View File

@@ -18,13 +18,24 @@
v-if="showIcon"
></image>
<text class="cl-empty__text dark:!text-surface-100" v-if="text">{{ text }}</text>
<cl-text
:pt="{
className: parseClass([
'cl-empty__text',
{
'!text-surface-100': isDark
}
])
}"
v-if="text"
>{{ text }}</cl-text
>
</view>
</template>
<script lang="ts" setup>
import { t } from "@/locale";
import { parsePt, parseRpx } from "@/cool";
import { isDark, parseClass, parsePt, parseRpx } from "@/cool";
import { computed } from "vue";
// 组件属性定义

View File

@@ -24,8 +24,9 @@
<script setup lang="ts">
import { computed, type PropType } from "vue";
import { forInObject, get, has, parseRpx, parsePt, useCache, isDark } from "@/cool";
import { forInObject, get, has, parsePt, useCache, isDark } from "@/cool";
import { icons } from "@/icons";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-icon"
@@ -68,6 +69,9 @@ const props = defineProps({
// 缓存
const { cache } = useCache(() => [props.color]);
// 字号
const { getRpx } = useSize();
// 透传样式类型定义
type PassThrough = {
className?: string;
@@ -126,11 +130,14 @@ const iconStyle = computed(() => {
style["color"] = props.color;
}
style["fontFamily"] = icon.value.font;
style["fontSize"] = parseRpx(props.size!);
style["height"] = parseRpx(props.height ?? props.size!);
style["width"] = parseRpx(props.width ?? props.size!);
style["lineHeight"] = parseRpx(props.size!);
if (icon.value.font != "") {
style["fontFamily"] = icon.value.font;
}
style["fontSize"] = getRpx(props.size!);
style["height"] = getRpx(props.height ?? props.size!);
style["width"] = getRpx(props.width ?? props.size!);
style["lineHeight"] = getRpx(props.size!);
return style;
});

View File

@@ -1,5 +1,13 @@
<template>
<view class="cl-input-otp" :class="[pt.className]">
<view
class="cl-input-otp"
:class="[
{
'cl-input-otp--disabled': disabled
},
pt.className
]"
>
<view class="cl-input-otp__inner">
<cl-input
v-model="value"
@@ -33,16 +41,11 @@
pt.item?.className
]"
>
<text
class="cl-input-otp__value"
:class="[
{
'is-disabled': disabled,
'is-dark': isDark
},
pt.value?.className
]"
>{{ item }}</text
<cl-text
:pt="{
className: pt.value?.className
}"
>{{ item }}</cl-text
>
<view
class="cl-input-otp__cursor"
@@ -268,14 +271,6 @@ function animateCursor(isIncreasing: boolean) {
}
}
&__value {
@apply text-surface-700 text-md;
&.is-dark {
@apply text-white;
}
}
&__cursor {
@apply absolute;
@apply bg-primary-500;
@@ -302,5 +297,9 @@ function animateCursor(isIncreasing: boolean) {
}
}
// #endif
&--disabled {
@apply opacity-50;
}
}
</style>

View File

@@ -30,8 +30,9 @@
'is-disabled': isDisabled,
'is-dark': isDark
},
pt.inner?.className
ptClassName
]"
:style="inputStyle"
:value="value"
:disabled="readonly ?? isDisabled"
:type="type"
@@ -86,7 +87,7 @@ import type { ClInputType, PassThroughProps } from "../../types";
import { isDark, parseClass, parsePt } from "@/cool";
import type { ClIconProps } from "../cl-icon/props";
import { t } from "@/locale";
import { useForm, useFormItem } from "../../hooks";
import { useForm, useFormItem, useSize } from "../../hooks";
defineOptions({
name: "cl-input"
@@ -225,6 +226,22 @@ type PassThrough = {
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 字号
const { ptClassName, getSize } = useSize(() => pt.value.inner?.className ?? "");
// 输入框样式
const inputStyle = computed(() => {
const style = {};
// 字号
const fontSize = getSize(null);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
return style;
});
// 绑定值
const value = ref<string>("");

View File

@@ -22,10 +22,21 @@
class="cl-keyboard-car__value"
:class="[pt.value?.className]"
>
<text class="cl-keyboard-car__value-text dark:!text-white" v-if="value != ''">{{
valueText
}}</text>
<text class="cl-keyboard-car__value-placeholder" v-else>{{ placeholder }}</text>
<cl-text
v-if="value != ''"
:pt="{
className: '!text-2xl'
}"
>{{ valueText }}</cl-text
>
<cl-text
v-else
:pt="{
className: '!text-md !text-surface-400'
}"
>{{ placeholder }}</cl-text
>
</view>
</slot>
@@ -70,9 +81,7 @@
color="white"
></cl-icon>
<text v-else class="cl-keyboard-car__item-text dark:!text-white">{{
item
}}</text>
<cl-text v-else>{{ item }}</cl-text>
</slot>
</view>
</view>
@@ -289,16 +298,8 @@ defineExpose({
&__value {
@apply flex flex-row items-center justify-center;
height: 60rpx;
margin-bottom: 28rpx;
&-text {
@apply text-2xl text-surface-700;
}
&-placeholder {
@apply text-md text-surface-400;
}
height: 80rpx;
margin-bottom: 20rpx;
}
&__list {
@@ -327,10 +328,6 @@ defineExpose({
@apply bg-surface-800;
}
&-text {
@apply text-lg;
}
&.is-fill {
flex: 1;
}
@@ -347,10 +344,6 @@ defineExpose({
&.is-keycode-confirm {
@apply bg-primary-500;
flex: 1;
.cl-keyboard-car__item-text {
@apply text-white;
}
}
&.is-empty {

View File

@@ -22,14 +22,21 @@
class="cl-keyboard-number__value"
:class="[pt.value?.className]"
>
<text
class="cl-keyboard-number__value-text dark:!text-white"
<cl-text
v-if="value != ''"
>{{ value }}</text
:pt="{
className: '!text-2xl'
}"
>{{ value }}</cl-text
>
<cl-text
v-else
:pt="{
className: '!text-md !text-surface-400'
}"
>{{ placeholder }}</cl-text
>
<text class="cl-keyboard-number__value-placeholder" v-else>{{
placeholder
}}</text>
</view>
</slot>
@@ -61,15 +68,21 @@
v-else-if="item == 'confirm'"
class="cl-keyboard-number__item-confirm"
>
<text class="cl-keyboard-number__item-text">{{
confirmText
}}</text>
<cl-text
color="white"
:pt="{
className: '!text-lg'
}"
>{{ confirmText }}</cl-text
>
</view>
<text
<cl-text
v-else
class="cl-keyboard-number__item-text dark:!text-white"
>{{ item }}</text
:pt="{
className: '!text-lg'
}"
>{{ item }}</cl-text
>
</slot>
</view>
@@ -346,16 +359,8 @@ defineExpose({
&__value {
@apply flex flex-row items-center justify-center;
height: 60rpx;
margin-bottom: 28rpx;
&-text {
@apply text-2xl text-surface-700;
}
&-placeholder {
@apply text-md text-surface-400;
}
height: 80rpx;
margin-bottom: 20rpx;
}
&__list {
@@ -372,10 +377,6 @@ defineExpose({
@apply bg-surface-800;
}
&-text {
@apply text-lg;
}
&.is-keycode-delete {
@apply bg-surface-200;
@@ -386,10 +387,6 @@ defineExpose({
&.is-keycode-confirm {
@apply bg-transparent relative;
.cl-keyboard-number__item-text {
@apply text-white;
}
}
&-confirm {

View File

@@ -22,14 +22,21 @@
class="cl-keyboard-password__value"
:class="[pt.value?.className]"
>
<text
class="cl-keyboard-password__value-text dark:!text-white"
<cl-text
v-if="value != ''"
>{{ valueText }}</text
:pt="{
className: '!text-2xl'
}"
>{{ valueText }}</cl-text
>
<cl-text
v-else
:pt="{
className: '!text-md !text-surface-400'
}"
>{{ placeholder }}</cl-text
>
<text class="cl-keyboard-password__value-placeholder" v-else>{{
placeholder
}}</text>
</view>
</slot>
@@ -66,22 +73,29 @@
:size="36"
></cl-icon>
<text
<cl-text
v-else-if="item == 'confirm'"
class="cl-keyboard-password__item-text"
>{{ confirmText }}</text
color="white"
:pt="{
className: '!text-lg'
}"
>{{ confirmText }}</cl-text
>
<text
<cl-text
v-else-if="item == 'letter'"
class="cl-keyboard-password__item-text dark:!text-white"
>ABC</text
:pt="{
className: '!text-lg'
}"
>ABC</cl-text
>
<text
<cl-text
v-else-if="item == 'number'"
class="cl-keyboard-password__item-text dark:!text-white"
>123</text
:pt="{
className: '!text-lg'
}"
>123</cl-text
>
<template v-else-if="item == 'caps'">
@@ -97,9 +111,13 @@
></cl-badge>
</template>
<text v-else class="cl-keyboard-password__item-text dark:!text-white">{{
item
}}</text>
<cl-text
v-else
:pt="{
className: '!text-lg'
}"
>{{ item }}</cl-text
>
</slot>
</view>
</view>
@@ -364,16 +382,8 @@ defineExpose({
&__value {
@apply flex flex-row items-center justify-center;
height: 60rpx;
margin-bottom: 28rpx;
&-text {
@apply text-2xl text-surface-700;
}
&-placeholder {
@apply text-md text-surface-400;
}
height: 80rpx;
margin-bottom: 20rpx;
}
&__list {
@@ -403,10 +413,6 @@ defineExpose({
@apply bg-surface-800;
}
&-text {
@apply text-lg bg-transparent;
}
&.is-keycode-number,
&.is-keycode-letter {
width: 150rpx;
@@ -434,10 +440,6 @@ defineExpose({
@apply bg-primary-500;
width: 150rpx !important;
flex: none;
.cl-keyboard-password__item-text {
@apply text-white;
}
}
&.is-empty {

View File

@@ -21,8 +21,9 @@
// #ifdef APP
transform: `rotate(${rotate}deg)`,
// #endif
height: parseRpx(size!),
width: parseRpx(size!),
height: getRpx(size!),
width: getRpx(size!),
borderWidth: getRpx(2),
borderTopColor: color,
borderRightColor: 'transparent',
borderBottomColor: color,
@@ -35,8 +36,9 @@
<script setup lang="ts">
import { computed, onMounted, ref, watch } from "vue";
import { isDark, parsePt, parseRpx } from "@/cool";
import { isDark, parsePt } from "@/cool";
import type { ClIconProps } from "../cl-icon/props";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-loading"
@@ -66,6 +68,8 @@ const props = defineProps({
}
});
const { getRpx } = useSize();
// 透传样式类型定义
type PassThrough = {
className?: string;
@@ -114,7 +118,6 @@ onMounted(() => {
.cl-loading {
@apply flex flex-row items-center justify-center rounded-full;
@apply border-surface-700 border-solid;
border-width: 2rpx;
&--dark {
border-color: white !important;

View File

@@ -16,10 +16,11 @@
}"
>
<slot name="text" :item="item">
<text
class="cl-noticebar__text dark:!text-white"
:class="[pt.text?.className]"
>{{ item }}</text
<cl-text
:pt="{
className: parseClass(['cl-noticebar__text', pt.text?.className])
}"
>{{ item }}</cl-text
>
</slot>
</view>
@@ -37,7 +38,7 @@ import {
type PropType,
watch
} from "vue";
import { parseRpx, parsePt } from "@/cool";
import { parseRpx, parsePt, parseClass } from "@/cool";
import type { PassThroughProps } from "../../types";
defineOptions({
@@ -248,7 +249,6 @@ onUnmounted(() => {
}
&__text {
@apply text-md text-surface-700;
white-space: nowrap;
}
}

View File

@@ -30,13 +30,17 @@
]"
@tap="toPage(item)"
>
<text
class="cl-pagination__item-text"
:class="{
'is-active': item == value,
'is-dark': isDark
<cl-text
:pt="{
className: parseClass([
'cl-pagination__item-text',
{
'is-active': item == value,
'is-dark': isDark
}
])
}"
>{{ item }}</text
>{{ item }}</cl-text
>
</view>
@@ -61,7 +65,7 @@
<script setup lang="ts">
import type { PassThroughProps } from "../../types";
import { isDark, parsePt } from "@/cool";
import { isDark, parseClass, parsePt } from "@/cool";
import { computed, ref, watch } from "vue";
defineOptions({

View File

@@ -1,11 +1,13 @@
<template>
<view class="cl-picker-view">
<view class="cl-picker-view__header" v-if="headers.length > 0">
<text
class="cl-picker-view__header-item dark:!text-white"
<cl-text
:pt="{
className: 'flex-1 !text-sm text-center'
}"
v-for="(label, index) in headers"
:key="index"
>{{ label }}</text
>{{ label }}</cl-text
>
</view>
@@ -168,11 +170,6 @@ function onChange(e: UniPickerViewChangeEvent) {
&__header {
@apply flex flex-row items-center py-4;
&-item {
@apply text-center text-sm text-surface-700;
flex: 1;
}
}
&__item {

View File

@@ -252,7 +252,7 @@ const paddingBottom = computed(() => {
let h = 0;
if (props.direction == "bottom") {
h += hasCustomTabBar() ? getTabBarHeight() : getSafeAreaHeight("bottom");
h += getSafeAreaHeight("bottom");
}
return h + "px";

View File

@@ -28,13 +28,14 @@
{{ text }}
</cl-text>
<text
class="cl-select-trigger__placeholder"
:class="[pt.placeholder?.className]"
<cl-text
:pt="{
className: parseClass(['!text-surface-400', pt.placeholder?.className])
}"
v-else
>
{{ placeholder }}
</text>
</cl-text>
</view>
<view v-if="showText && !isDisabled" class="cl-select-trigger__icon" @tap.stop="clear">
@@ -152,10 +153,6 @@ function open() {
height: 66rpx;
padding: 0 20rpx;
&__placeholder {
@apply text-surface-400 text-md;
}
&__content {
flex: 1;
}

View File

@@ -26,6 +26,8 @@
}"
>
<view class="cl-select-popup" @touchmove.stop>
<slot name="prepend"></slot>
<view class="cl-select-popup__picker">
<cl-picker-view
:value="indexes"
@@ -34,6 +36,8 @@
></cl-picker-view>
</view>
<slot name="append"></slot>
<view class="cl-select-popup__op">
<cl-button
v-if="showCancel"
@@ -73,6 +77,11 @@ defineOptions({
name: "cl-select"
});
defineSlots<{
prepend(): any;
append(): any;
}>();
// 值类型
type Value = string[] | number[] | number | string | null;
@@ -146,7 +155,7 @@ const props = defineProps({
});
// 定义事件
const emit = defineEmits(["update:modelValue", "change"]);
const emit = defineEmits(["update:modelValue", "change", "changing"]);
// 弹出层引用
const popupRef = ref<ClPopupComponentPublicInstance | null>(null);
@@ -230,6 +239,11 @@ const text = computed(() => {
.join(props.splitor);
});
// 获取当前选中值
function getValue() {
return props.columnCount == 1 ? value.value[0] : value.value;
}
// 选择器值改变事件
function onChange(a: number[]) {
// 复制当前组件内部维护的索引数组
@@ -258,6 +272,9 @@ function onChange(a: number[]) {
indexes.value = b;
// 根据最新的索引数组,更新选中的值数组
value.value = b.map((e, i) => columns.value[i][e].value);
// 触发changing事件
emit("changing", getValue());
}
// 选择器显示状态
@@ -291,7 +308,7 @@ function clear() {
// 确认选择
function confirm() {
// 根据列数返回单个值或数组
const val = props.columnCount == 1 ? value.value[0] : value.value;
const val = getValue();
// 触发更新事件
emit("update:modelValue", val);

View File

@@ -35,19 +35,21 @@
@tap="change(index)"
>
<slot name="item" :item="item" :active="item.isActive">
<text
class="cl-tabs__item-label"
:class="[
{
'is-active': item.isActive,
'is-disabled': item.disabled,
'is-dark': isDark,
'is-fill': fill
},
pt.text?.className
]"
<cl-text
:pt="{
className: parseClass([
'cl-tabs__item-label',
{
'is-active': item.isActive,
'is-disabled': item.disabled,
'is-dark': isDark,
'is-fill': fill
},
pt.text?.className
])
}"
:style="getTextStyle(item)"
>{{ item.label }}</text
>{{ item.label }}</cl-text
>
</slot>
</view>
@@ -73,7 +75,7 @@
<script lang="ts" setup>
import { type PropType, computed, getCurrentInstance, nextTick, onMounted, ref, watch } from "vue";
import { isDark, isEmpty, isHarmony, isNull, parsePt, parseRpx, rpx2px } from "@/cool";
import { isDark, isEmpty, isHarmony, isNull, parseClass, parsePt, parseRpx, rpx2px } from "@/cool";
import type { ClTabsItem, PassThroughProps } from "../../types";
// 定义标签类型

View File

@@ -18,7 +18,7 @@
'!text-surface-50': color == 'light',
'!text-surface-400': color == 'disabled'
},
pt.className
ptClassName
]"
:style="textStyle"
:selectable="selectable"
@@ -49,7 +49,7 @@
'!text-surface-50': color == 'light',
'!text-surface-400': color == 'disabled'
},
pt.className
ptClassName
]"
:style="textStyle"
:selectable="selectable"
@@ -66,6 +66,7 @@
import { computed, type PropType } from "vue";
import { isDark, parsePt, useCache } from "@/cool";
import type { ClTextType } from "../../types";
import { useSize } from "../../hooks";
defineOptions({
name: "cl-text"
@@ -88,6 +89,11 @@ const props = defineProps({
type: String,
default: ""
},
// 字体大小
size: {
type: [Number, String] as PropType<number | string | null>,
default: null
},
// 文本类型
type: {
type: String as PropType<ClTextType>,
@@ -161,6 +167,9 @@ type PassThrough = {
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 文本大小
const { getSize, getLineHeight, ptClassName } = useSize(() => pt.value.className ?? "");
// 文本样式
const textStyle = computed(() => {
const style = {};
@@ -169,6 +178,18 @@ const textStyle = computed(() => {
style["color"] = props.color;
}
// 字号
const fontSize = getSize(props.size);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
// 行高
const lineHeight = getLineHeight();
if (lineHeight != null) {
style["lineHeight"] = lineHeight;
}
return style;
});

View File

@@ -20,11 +20,9 @@
'is-disabled': isDisabled,
'is-dark': isDark
},
pt.inner?.className
ptClassName
]"
:style="{
height: parseRpx(height)
}"
:style="textareaStyle"
:value="value"
:name="name"
:disabled="readonly ?? isDisabled"
@@ -48,12 +46,14 @@
@input="onInput"
@linechange="onLineChange"
@blur="onBlur"
@keyboardheightchange="onKeyboardheightchange"
@focus="onFocus"
@keyboardheightchange="onKeyboardheightchange"
/>
<text class="cl-textarea__count" v-if="showWordLimit"
>{{ value.length }}/{{ maxlength }}</text
<cl-text
:pt="{ className: 'absolute right-2 bottom-2 !text-xs !text-surface-400' }"
v-if="showWordLimit"
>{{ value.length }} / {{ maxlength }}</cl-text
>
</view>
</template>
@@ -64,7 +64,7 @@ import { parsePt, parseRpx } from "@/cool";
import type { PassThroughProps } from "../../types";
import { isDark } from "@/cool";
import { t } from "@/locale";
import { useForm, useFormItem } from "../../hooks";
import { useForm, useFormItem, useSize } from "../../hooks";
defineOptions({
name: "cl-textarea"
@@ -243,6 +243,24 @@ type PassThrough = {
// 解析透传样式
const pt = computed(() => parsePt<PassThrough>(props.pt));
// 字号
const { ptClassName, getSize } = useSize(() => pt.value.inner?.className ?? "");
// 文本框样式
const textareaStyle = computed(() => {
const style = {
height: parseRpx(props.height)
};
// 字号
const fontSize = getSize(null);
if (fontSize != null) {
style["fontSize"] = fontSize;
}
return style;
});
// 绑定值
const value = ref(props.modelValue);
@@ -348,10 +366,6 @@ defineExpose({
padding-left: 20rpx;
}
&__count {
@apply text-surface-400 text-sm absolute right-2 bottom-2;
}
&--border {
@apply border border-solid border-surface-200;
}

View File

@@ -37,7 +37,13 @@
></cl-icon>
</view>
<text class="cl-toast__text">{{ item.message }}</text>
<cl-text
color="white"
:pt="{
className: 'text-center'
}"
>{{ item.message }}</cl-text
>
</view>
</view>
</cl-popup>
@@ -157,10 +163,6 @@ defineExpose({
max-width: 600rpx;
opacity: 0;
&__text {
@apply text-md text-center font-bold text-white;
}
&.is-open {
opacity: 1;
}

View File

@@ -31,7 +31,7 @@
<cl-text
:color="color"
:pt="{
className: parseClass(['!text-[16px]', pt.title?.className])
className: parseClass(['!text-md', pt.title?.className])
}"
>
{{ title }}

View File

@@ -43,7 +43,7 @@
<view
v-if="isAdd"
class="cl-upload"
class="cl-upload is-add"
:class="[
{
'is-dark': isDark,
@@ -109,7 +109,7 @@ const props = defineProps({
// 上传按钮显示的文本
text: {
type: String,
default: () => t("上传/拍摄")
default: () => t("上传 / 拍摄")
},
// 图片压缩方式original原图compressed压缩图
sizeType: {
@@ -421,6 +421,10 @@ watch(
@apply opacity-50;
}
&.is-add {
@apply p-1;
}
&__image {
@apply w-full h-full absolute top-0 left-0;
transition-property: opacity;