2025/2/8第一次更新

This commit is contained in:
爱吃咸鱼小猫咪
2025-02-08 18:50:38 +08:00
commit d7af560866
26519 changed files with 5046029 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
# 图鸟 UI icon 字体图标
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
{
"name": "@tuniao/tn-icon",
"version": "1.9.0",
"description": "Tuniao Icon Font",
"keywords": [
"tn",
"tuniao",
"图鸟",
"icon"
],
"license": "ISC",
"author": "Tuniao Technology Co., Ltd.",
"style": "dist/index.css",
"publishConfig": {
"access": "public"
},
"browerslist": [
"> 1%",
"not dead"
],
"gitHead": ""
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+144
View File
@@ -0,0 +1,144 @@
### 1.0.1
_2023-03-21_
#### Fixed
- 优化box-shadow样式
### 1.0.2
_2023-03-25_
#### Fixed
- 修复默认内置颜色背景颜色时设置字体颜色失效的问题
### 1.0.3
_2023-03-25_
#### Fixed
- 优化颜色值
### 1.0.4
_2023-03-26_
#### Fixed
- 修改tn-cool-bg-image类名为tn-bg-image
### 1.0.5
_2023-03-28_
#### Fixed
- 修复边框设置无效的问题
### 1.0.6
_2023-03-28_
#### Fixed
- 修复flex布局下同时设置多个对齐方向的时候会导致对齐失效的问题
### 1.0.7
_2023-03-29_
#### Fixed
- 修复shadow--blur类名修改为shadow-blur
- 修复shadow-blur样式不生效的问题
### 1.0.8
_2023-04-03_
#### Fixed
- 修复/在uniapp-微信小程序中无法解析的问题,将/改为-
- 修复:在uniapp-微信小程序中无法解析的问题,将:改为_
#### Add
- 新增uniapp专有样式
### 1.0.9
_2023-04-03_
#### Fixed
- 修复uniapp中没有:root的问题,将变量卸载body中
### 1.0.10
_2023-04-05_
#### Add
- 新增src目录,将源码放入src目录中
- 新增uniapp中隐藏滚动条样式
#### Fixed
- 修复uniapp中没有:root的问题,将变量卸载body中
### 1.0.11
### 1.0.12
_2023-04-10_
#### Add
- 新增uniapp样式中H5中隐藏滚动条样式
#### Fixed
- 修复uniapp中没有:root的问题,将变量卸载body\page中
### 1.0.13
_2023-04-10_
#### Fixed
- 修复uniapp中样式生效的顺序
### 1.0.14
_2023-04-24_
#### Fixed
- 修复tn-border样式下去修改style时不生效的问题
### 1.0.15
### 1.0.16
### 1.0.17
_2023-07-13_
#### Fixed
- 修复在`h5`中图鸟弹框会遮挡原生弹框的问题
### 1.0.18
_2023-07-26_
- 更新cool-bg的背景地址
### 1.0.19
_2023-08-02_
- uniapp中新增`tn-image`设置图片默认样式
+1
View File
@@ -0,0 +1 @@
# 图鸟 UI 样式
+1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+24
View File
@@ -0,0 +1,24 @@
{
"name": "@tuniao/tn-style",
"version": "1.0.19",
"description": "Tuniao UI Style",
"keywords": [
"tn",
"tuniao",
"tuniao-ui",
"图鸟",
"tn-style",
"style css"
],
"license": "ISC",
"author": "Tuniao Technology Co., Ltd.",
"style": "dist/index.css",
"publishConfig": {
"access": "public"
},
"browerslist": [
"> 1%",
"not dead"
],
"gitHead": ""
}
+1
View File
@@ -0,0 +1 @@
@use 'var.scss';
+53
View File
@@ -0,0 +1,53 @@
// 边框样式
@use 'mixins/function' as *;
@use 'mixins/mixins' as *;
[class*=#{bem('border')}] {
border-radius: inherit;
box-sizing: border-box;
border-width: getCssVar('border-width', '');
border-style: solid;
}
/* 圆角尺寸 start */
.#{bem('radius')} {
border-radius: getCssVar('border-radius', '');
}
.#{bem('round')} {
border-radius: getCssVar('border-radius', 'round');
}
/* 圆角尺寸 end */
@include b('border') {
// 加粗边框
&-bold {
border-width: getCssVar('border-width', 'bold');
}
// 虚线边框
&-dashed {
border-style: dashed;
}
// 设置单边
@each $direction in ('top', 'right', 'bottom', 'left') {
&-#{$direction} {
border-width: 0px;
border-#{$direction}-width: getCssVar('border-width', '');
&.tn-border-bold {
border-#{$direction}-width: getCssVar('border-width', 'bold');
}
}
}
// 移除边框
&-none {
border-width: 0px;
@each $direction in ('top', 'right', 'bottom', 'left') {
&-#{$direction} {
border-#{$direction}-width: 0px;
}
}
}
}
+183
View File
@@ -0,0 +1,183 @@
// 内置颜色类
@use 'sass:map';
@use 'sass:list';
@use 'common/var' as *;
@use 'mixins/mixins' as *;
@use 'mixins/color' as *;
@use 'mixins/var' as *;
@use 'mixins/function' as *;
// 背景颜色
@each $type in $types {
@include b('type-' + $type) {
@include generate-attr-color($type, 'bg', 'background-color') {
@include get-inner-color('color', 'white');
}
}
}
@include b('white') {
@include generate-attr-color('white', 'bg', 'background-color') {
@include get-inner-color('color', 'black');
}
}
@include b('black') {
@include generate-attr-color('black', 'bg', 'background-color') {
@include get-inner-color('color', 'white');
}
}
@each $color in $inner-color-names {
@include b($color) {
@include generate-attr-bg-color($color);
@each $type in ('light', 'dark', 'disabled') {
&-#{$type} {
@include generate-attr-bg-color($color, $type);
}
}
}
}
// 没有渐变色的主题色
$no-gradient-bg-colors: ('orangered', 'brown', 'gray');
// 渐变背景
[class*=#{bem('gradient-bg')}] {
background-image: repeating-linear-gradient(
45deg,
getCssVar('cool-bg-start-color'),
getCssVar('cool-bg-end-color')
);
}
@include b('gradient-bg') {
// 主题色渐变
$i: 0;
@each $color in $inner-color-names {
$i: $i + 1;
@include e($color) {
@if not index($no-gradient-bg-colors, $color) {
$next-color: '';
@if $i >= length($inner-color-names) {
$next-color: nth($inner-color-names, 1);
} @else {
$next-color: nth($inner-color-names, $i + 1);
}
#{getCssVarName('cool-bg-start-color')}: getCssVar('color', $color);
#{getCssVarName('cool-bg-end-color')}: getCssVar('color', $next-color);
&-light {
#{getCssVarName('cool-bg-start-color')}: getCssVar(
'color',
$color,
'light'
);
#{getCssVarName('cool-bg-end-color')}: getCssVar(
'color',
$next-color,
'light'
);
}
}
&-single {
#{getCssVarName('cool-bg-start-color')}: getCssVar('color', $color);
#{getCssVarName('cool-bg-end-color')}: getCssVar(
'color',
$color,
'light'
);
}
}
}
// 炫酷渐变
@include e('cool') {
@each $key, $color in $cool-colors {
&-#{$key} {
#{getCssVarName('cool-bg-start-color')}: map.get($color, 'begin');
#{getCssVarName('cool-bg-end-color')}: map.get($color, 'end');
}
}
}
}
// 酷炫背景颜色图片
@include b('bg-image') {
position: relative;
&::after {
content: ' ';
position: absolute;
z-index: inherit;
width: 100%;
height: 100%;
left: 0;
bottom: 0;
border-radius: getCssVar('border-radius', 'default');
opacity: 1;
transform: scale(1, 1);
background-size: 100% 100%;
background-image: inherit;
}
@for $num from 1 through 6 {
&:nth-of-type(#{$num}n)::after {
background-image: url(https://resource.tuniaokj.com/images/cool_bg_image/#{$num}.png);
}
}
}
// 字体颜色
@each $type in $types {
@include b('type-' + $type) {
@include generate-attr-color($type, 'text', 'color');
}
}
@each $color in join(('white', 'black'), $inner-color-names) {
@include b($color) {
@include generate-attr-color($color, 'text', 'color');
@if not index(('white', 'black'), $color) {
@each $type in ('light', 'dark', 'disabled') {
&-#{$type} {
@include generate-attr-color($color, 'text', 'color', $type);
}
}
}
}
}
// 边框颜色
@each $type in $types {
@include b('type-' + $type) {
@include generate-attr-color($type, 'border', 'border-color');
}
}
@each $color in join(('white', 'black'), $inner-color-names) {
@include b($color) {
@include generate-attr-color($color, 'border', 'border-color');
@if not index(('white', 'black'), $color) {
@each $type in ('light', 'dark', 'disabled') {
&-#{$type} {
@include generate-attr-color($color, 'border', 'border-color', $type);
}
}
}
}
}
// 阴影颜色
@each $type in $types {
@include b('type-' + $type) {
@include generate-attr-shadow-color($type);
}
}
@each $color in join(('white', 'black'), $inner-color-names) {
@include b($color) {
@include generate-attr-shadow-color($color);
@if not index(('white', 'black'), $color) {
@each $type in ('light', 'dark', 'disabled') {
&-#{$type} {
@include generate-attr-shadow-color($color, $type);
}
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
@use 'sass:color';
@use 'sass:string';
@function rgb2hex($color) {
@return unquote('#' + #{string.slice(color.ie-hex-str($color), 4)});
}
// rgba color above solid color
@function mix-overlay-color($upper, $lower) {
$opacity: color.aplha($upper);
$red: color.red($supper) * $opacity + color.red($lower) * (1 - $opacity);
$green: color.green($supper) * $opacity + color.green($lower) * (1 - $opacity);
$blue: color.blue($supper) * $opacity + color.blue($lower) * (1 - $opacity);
@return rgb2hex(rgb($red, $green, $blue));
}
+370
View File
@@ -0,0 +1,370 @@
/** 图鸟变量 */
@use 'sass:map';
@use 'sass:math';
@use '../mixins/function.scss' as *;
// 类型
$types: primary, success, warning, danger, error, info;
// 内置颜色的值
$inner-color-names: 'red', 'purplered', 'purple', 'bluepurple', 'aquablue',
'blue', 'indigo', 'cyan', 'teal', 'green', 'yellowgreen', 'lime', 'yellow',
'orangeyellow', 'orange', 'orangered', 'brown', 'grey', 'gray';
$inner-colors: (
'red': (
'base': #e83a30,
'dark': #ba2e26,
'disabled': #f39c97,
'light': #fad8d6,
),
'purplered': (
'base': #e72f8c,
'dark': #b9266f,
'disabled': #f397c5,
'light': #fad5e8,
),
'purple': (
'base': #892fe8,
'dark': #6e26ba,
'disabled': #c497f3,
'light': #e7d5fa,
),
'bluepurple': (
'base': #5f4fd9,
'dark': #4c3fae,
'disabled': #afa7ec,
'light': #dfdcf7,
),
'aquablue': (
'base': #3646ff,
'dark': #2b38cc,
'disabled': #9aa2ff,
'light': #d7daff,
),
'blue': (
'base': #3d7eff,
'dark': #3165cc,
'disabled': #9ebeff,
'light': #d8e5ff,
),
'indigo': (
'base': #31c9e8,
'dark': #27a1ba,
'disabled': #98e4f3,
'light': #d6f4fa,
),
'cyan': (
'base': #2de8bd,
'dark': #24ba97,
'disabled': #96f3de,
'light': #d5faf2,
),
'teal': (
'base': #24f083,
'dark': #1dc069,
'disabled': #91f7c1,
'light': #d3fce6,
),
'green': (
'base': #31e749,
'dark': #27b93a,
'disabled': #98f3a4,
'light': #d6fadb,
),
'yellowgreen': (
'base': #a4e82f,
'dark': #82ba26,
'disabled': #d1f397,
'light': #edfad5,
),
'lime': (
'base': #d5eb00,
'dark': #aabc00,
'disabled': #e9f57f,
'light': #f7fbcc,
),
'yellow': (
'base': #fff420,
'dark': #ccc21a,
'disabled': #fff88f,
'light': #fffdd2,
),
'orangeyellow': (
'base': #ffca28,
'dark': #cca220,
'disabled': #ffe493,
'light': #fff4d4,
),
'orange': (
'base': #ffa726,
'dark': #cc851e,
'disabled': #ffd392,
'light': #ffedd4,
),
'orangered': (
'base': #ff7043,
'dark': #cc5a36,
'disabled': #ffb7a1,
'light': #ffe2d9,
),
'brown': (
'base': #914f2c,
'dark': #743f23,
'disabled': #c8a795,
'light': #e9dcd5,
),
'grey': (
'base': #78909c,
'dark': #5f7e8b,
'disabled': #c6d1d8,
'light': #e4e9ec,
),
'gray': (
'base': #aaaaaa,
'dark': #838383,
'disabled': #e6e6e6,
'light': #f8f7f8,
),
) !default;
// 炫酷颜色值
$cool-colors: (
'1': (
'begin': #f5317f,
'end': #ff7c6e,
),
'2': (
'begin': #ca26ff,
'end': #f360a7,
),
'3': (
'begin': #a26ffc,
'end': #9d12ff,
),
'4': (
'begin': #aa77f0,
'end': #e871e5,
),
'5': (
'begin': #40a0f7,
'end': #4866e6,
),
'6': (
'begin': #209cff,
'end': #68e0cf,
),
'7': (
'begin': #00c3ff,
'end': #58fff5,
),
'8': (
'begin': #00d1ff,
'end': #69ff97,
),
'9': (
'begin': #0fd893,
'end': #29ecbf,
),
'10': (
'begin': #0fd850,
'end': #f9f047,
),
'11': (
'begin': #24fe41,
'end': #f7fd47,
),
'12': (
'begin': #d6ff7f,
'end': #00f657,
),
'13': (
'begin': #fa709a,
'end': #fee140,
),
'14': (
'begin': #fe5e9c,
'end': #f1aa76,
),
'15': (
'begin': #ff3181,
'end': #ff8331,
),
'16': (
'begin': #ed1c24,
'end': #fece12,
),
);
// 颜色值
$colors: () !default;
$colors: map.deep-merge(
(
'white': #ffffff,
'black': #000000,
'primary': (
'base': #01beff,
),
'success': (
'base': #00d886,
),
'warning': (
'base': #fbbd12,
),
'danger': (
'base': #ff444f,
),
'error': (
'base': #ff444f,
),
'info': (
'base': #9c9c9c,
),
),
$colors
);
$color-white: map.get($colors, 'white') !default;
$color-black: map.get($colors, 'black') !default;
$color-primary: map.get($colors, 'primary', 'base') !default;
$color-success: map.get($colors, 'success', 'base') !default;
$color-warning: map.get($colors, 'warning', 'base') !default;
$color-danger: map.get($colors, 'danger', 'base') !default;
$color-error: map.get($colors, 'error', 'base') !default;
$color-info: map.get($colors, 'info', 'base') !default;
// https://sass-lang.com/documentation/values/maps#immutability
// mix colors with white/black to generate light/dark level
@mixin set-color-mix-level(
$type,
$number,
$mode: 'light',
$mix-color: $color-white
) {
$colors: map.deep-merge(
(
$type: (
'#{$mode}-#{$number}':
mix(
$mix-color,
map.get($colors, $type, 'base'),
math.percentage(math.div($number, 10))
),
),
),
$colors
) !global;
}
// $colors.primary.light-i
@each $type in $types {
@for $i from 1 through 9 {
@include set-color-mix-level($type, $i, 'light', $color-white);
}
}
// --tn-color-primary-dark-2
@each $type in $types {
@include set-color-mix-level($type, 2, 'dark', $color-black);
}
$text-color: () !default;
$text-color: map.merge(
(
'primary': #080808,
'secondary': #9c9c9c,
'placeholder': #838383,
'disabled': #e6e6e6,
),
$text-color
);
$bg-color: () !default;
$bg-color: map.merge(
(
'': #ffffff,
'page': #f4f4f4,
'overlay': #ffffff,
'mask': rgba(0, 0, 0, 0.5),
),
$bg-color
);
$font-size: () !default;
$font-size: map.merge(
(
'4xl': 32px,
'3xl': 26px,
'2xl': 20px,
'xl': 18px,
'lg': 16px,
'default': 14px,
'sm': 13px,
'xs': 12px,
),
$font-size
);
$padding-margin-size: () !default;
$padding-margin-size: map.merge(
(
'xl': 25px,
'lg': 20px,
'default': 15px,
'sm': 10px,
'xs': 5px,
'0': 0px,
),
$padding-margin-size
);
$border-width: () !default;
$border-width: map.merge(
(
'bold': 3px,
'default': 1px,
),
$border-width
);
$border-radius: () !default;
$border-radius: map.merge(
(
'default': 7.5px,
'round': 2500px,
),
$border-radius
);
$box-shadow: () !default;
$box-shadow: map.merge(
(
'default': (
0px 5px 30px -9.5px,
),
'sm': (
0px 5px 30px -20px,
),
'md': (
0px 5px 10px -1px,
0px 0px 5px -2px,
),
'lg': (
0px 10px 15px -2px,
0px 5px 10px -2px,
),
),
$box-shadow
);
$box-shadow-colors: () !default;
$box-shadow-colors: map.merge(
(
'default': rgba(0, 0, 0, 0.1),
'sm': rgba(0, 0, 0, 0.1),
'md': rgba(0, 0, 0, 0.1),
'lg': rgba(0, 0, 0, 0.1),
),
$box-shadow-colors
);
+154
View File
@@ -0,0 +1,154 @@
// flex布局
@use 'mixins/function' as *;
@use 'mixins/mixins' as *;
[class*=#{bem('flex')}] {
display: -webkit-flex;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
@include b(flex) {
// 方向
@each $direction in (row, row-reverse, column, column-reverse) {
&-#{$direction} {
flex-direction: $direction;
}
}
// 换行
@each $wrap in (nowrap, wrap, wrap-reverse) {
&-#{$wrap} {
flex-wrap: $wrap;
}
}
// 空间分配
&-1 {
flex: 1 1 0%;
}
&-auto {
flex: 1 1 auto;
}
&-inital {
flex: 0 1 auto;
}
&-none {
flex: none;
}
// grow
&-grow {
flex-grow: 1;
&-0 {
flex-grow: 0;
}
}
// shrink
&-shrink {
flex-shrink: 1;
&-0 {
flex-shrink: 0;
}
}
// 组合对其方式
@each $align in (start, end, center, baseline, stretch) {
@each $justitfy in (start, end, center, between, around) {
&-#{$align}-#{$justitfy} {
@if $align == 'start' {
align-items: flex-start;
} @else if $align == 'end' {
align-items: flex-end;
} @else {
align-items: $align;
}
@if $justitfy == 'between' {
justify-content: space-between;
} @else if $justitfy == 'around' {
justify-content: space-around;
} @else if $justitfy == 'start' {
justify-content: flex-start;
} @else if $justitfy == 'end' {
justify-content: flex-end;
} @else {
justify-content: $justitfy;
}
}
}
}
&-center {
align-items: center;
justify-content: center;
}
// justify-content
@each $item
in (
(start, flex-start),
(end, flex-end),
(center, center),
(between, space-between),
(around, space-around),
(evenly, space-evenly)
)
{
&.#{'justify-' + nth($item, 1)} {
justify-content: nth($item, 2);
}
}
// justify-item
@each $item
in ((start, start), (end, end), (center, center), (stretch, stretch))
{
&.#{'justify-item-' + nth($item, 1)} {
justify-items: nth($item, 2);
}
}
// justify-self
@each $item
in (
(auto, auto),
(start, start),
(end, end),
(center, center),
(stretch, stretch)
)
{
&.#{'justify-self-' + nth($item, 1)} {
justify-self: nth($item, 2);
}
}
// align content
@each $item
in (
(start, flex-start),
(end, flex-end),
(center, center),
(between, space-between),
(around, space-around),
(evenly, evenly)
)
{
&.#{'content-' + nth($item, 1)} {
align-content: nth($item, 2);
}
}
// align-items
@each $item
in (
(start, flex-start),
(end, flex-end),
(center, center),
(baseline, baseline),
(stretch, stretch)
)
{
&.#{'items-' + nth($item, 1)} {
align-items: nth($item, 2);
}
}
}
+8
View File
@@ -0,0 +1,8 @@
@use 'base.scss';
@use 'size.scss';
@use 'flex.scss';
@use 'shadow.scss';
@use 'color.scss';
@use 'text.scss';
@use 'border.scss';
+84
View File
@@ -0,0 +1,84 @@
@use 'sass:map';
@use '../common/var.scss' as *;
@use 'function' as *;
// 获取图鸟官方内置颜色
@mixin get-inner-color($attr, $name, $type: '', $important: false) {
$attr-value: '';
@if $name == 'white' {
$attr-value: #{getCssVar('color', 'white')};
// #{$attr}: #{getCssVar('color', 'white') if($important, '!important')};
} @else if $name == 'black' {
$attr-value: #{getCssVar('text', 'color', 'primary')};
// #{$attr}: #{getCssVar('text', 'color', 'primary') if($important, '!important')};
} @else if index($types, $name) {
$attr-value: getCssVar('color', $name, $type);
// #{$attr}: #{getCssVar('color', $name, $type) if($important, '!important')};
} @else {
$attr-value: getCssVar('color', $name, if($type == 'base', '', $type));
// @if $type == 'base' {
// #{$attr}: #{getCssVar('color', $name) if($important, '!important')};
// } @else {
// #{$attr}: #{getCssVar('color', $name, $type) if($important, '!important')};
// }
}
$attr-value: $attr-value + if($important, '!important', '');
#{$attr}: #{$attr-value};
}
// 背景颜色设置的特殊字体颜色
$special-bg-text-colors: ('yellow', 'lime');
// 设置背景颜色
@mixin get-bg-inner-color($name, $type) {
@include get-inner-color('background-color', $name, $type);
@if ($type == 'base' or $type == '') {
@if index($special-bg-text-colors, $name) {
@include get-inner-color('color', $name, 'dark');
} @else {
@include get-inner-color('color', 'white');
}
} @else if $type == 'light' {
@if index($special-bg-text-colors, $name) {
@include get-inner-color('color', $name, 'dark');
} @else {
@include get-inner-color('color', $name, 'base');
}
} @else if $type == 'dark' {
@include get-inner-color('color', 'white');
} @else if $type == 'disabled' {
@include get-inner-color('color', $name, 'dark');
}
}
// 生成渐变背景颜色
@mixin generate-gradient-color($start, $end, $reverse: false) {
$rotate: if($reverse, -45deg, 45deg);
background-image: repeating-linear-gradient($rotate, $start, $end);
}
// 根据不同颜色类型设置不同属性的颜色值
@mixin generate-attr-color(
$name,
$attr-type,
$attr,
$type: '',
$important: false
) {
&#{'_' + $attr-type} {
@include get-inner-color($attr, $name, $type, $important);
@content;
}
}
@mixin generate-attr-bg-color($name, $type: '') {
&#{'_bg'} {
@include get-bg-inner-color($name, $type);
@content;
}
}
@mixin generate-attr-shadow-color($name, $type: '') {
&#{'_shadow'} {
@include get-inner-color(getCssVarName('shadow-color'), $name, $type);
#{getCssVarName('shadow')}: #{getCssVar('shadow-colored')};
}
}
+84
View File
@@ -0,0 +1,84 @@
@use 'sass:map';
@use 'config';
@use 'function' as *;
@use '../common/var' as *;
// set css var value
// @include set-css-var-value(('color', 'primary'), red)
// --tn-color-primary: red
@mixin set-css-var-value($name, $value) {
#{joinVarName($name)}: #{$value};
}
// @include set-css-var-type('color', 'primary', $map)
// --tn-color-primary: #{map.get($map, 'primary')}
@mixin set-css-var-type($name, $type, $variables) {
#{getCssVarName($name, $type)}: #{map.get($variables, $type)};
}
@mixin set-css-color-type($colors, $type) {
@include set-css-var-value(('color', $type), map.get($colors, $type, 'base'));
@each $i in (3, 5, 7, 8, 9) {
@include set-css-var-value(
('color', $type, 'light', $i),
map.get($colors, $type, 'light-#{$i}')
);
}
@include set-css-var-value(
('color', $type, 'dark-2'),
map.get($colors, $type, 'dark-2')
);
}
@mixin set-css-var-inner-color($name, $type, $variables) {
@include set-css-color-type($name, $type, $variables);
}
@mixin set-css-color-inner($colors, $name) {
@include set-css-var-value(('color', $name), map.get($colors, $name, 'base'));
@each $type in (dark, light, disabled) {
@include set-css-var-value(
('color', $name, $type),
map.get($colors, $name, $type)
);
}
}
// set all css var for coponent by map
@mixin set-component-css-var($name, $variables) {
@each $attribute, $value in $variables {
@if $attribute == 'default' {
#{getCssVarName($name)}: #{$value};
} @else {
#{getCssVarName($name, $attribute)}: #{$value};
}
}
}
// set component default var value
@mixin set-component-default-css-var($name, $value) {
#{getCssVarName($name)}: #{$value};
}
@mixin set-css-color-rgb($colors, $type) {
$color: map.get($colors, $type, 'base');
@include set-css-var-value(
('color', $type, 'rgb'),
#{red($color),
green($color),
blue($color)}
);
}
// generate css var from existing css var
// @include css-var-from-global(('button', 'text-color'), ('color', $type))
// --tn-button-text-color: var(--tn-color-#{$type})
@mixin css-var-from-global($var, $gVar) {
$varName: joinVarName($var);
$gVarName: joinVarName($gVar);
#{$varName}: var(#{$gVarName});
}
+5
View File
@@ -0,0 +1,5 @@
$namespace: 'tn' !default;
$common-separator: '-' !default;
$element-separator: '__' !default;
$modifier-separator: '--' !default;
$state-prefix: 'is-' !default;
+105
View File
@@ -0,0 +1,105 @@
@use 'config';
// BEM support Func
// 块(Block):代表一个独立的组件或页面中的一个大型部分。块可以看作是一个命名空间,用于包含相关元素和修饰符。例如,一个导航栏可以被视为一个块。
// 元素(Element):代表块的一部分,但不能独立存在。元素总是属于一个块,并且与该块紧密相关。元素由块名称和元素名称组成,中间由双下划线(__)连接。例如,一个导航栏可以包含多个链接,链接可以被视为导航栏块的元素。
// 修饰符(Modifier):代表块或元素的变体或状态。修饰符用于修改块或元素的外观或行为。修饰符由块名称或元素名称,连字符(-)和修饰符名称组成。例如,一个导航栏可以具有活动状态或浅色主题,这些状态可以通过添加修饰符类进行实现。
@function selectorToString($selector) {
$selector: inspect($selector);
$selector: str-slice($selector, 2, -2);
@return $selector;
}
@function containsModifier($selector) {
$selector: selectorToString($selector);
@if str_index($selector, config.$modifier-separator) {
@return true;
} @else {
@return false;
}
}
@function containWhenFlag($selector) {
$selector: selectorToString($selector);
@if str-index($selector, '.' + config.$state-prefix) {
@return true;
} @else {
@return false;
}
}
@function containPseudoClass($selector) {
$selector: selectorToString($selector);
@if str-index($selector, ':') {
@return true;
} @else {
@return false;
}
}
@function hitAllSpecialNestRule($selector) {
@return containsModifier($selector) or containWhenFlag($selector) or
containPseudoClass($selector);
}
// join var name
// joinVarName(('button', 'text-color')) => '--tn-button-text-color'
@function joinVarName($list) {
$name: '--' + config.$namespace;
@each $item in $list {
@if $item != '' {
$name: $name + '-' + $item;
}
}
@return $name;
}
// getCssVarName('button', 'text-color') => '--tn-button-text-color'
@function getCssVarName($args...) {
@return joinVarName($args);
}
// getCssVar('button', 'text-color') => var(--tn-button-text-color)
@function getCssVar($args...) {
@return var(#{joinVarName($args)});
}
// getCssVarWithDefault('button', 'text-color', 'red') => var(--tn-button-text-color, red)
@function getCssVarWithDefault($args, $default) {
@return var(#{joinVarName($args)}, #{$default});
}
// bem('block', 'element', ''modifier) => 'tn-block__element--modifier'
@function bem($block, $element: '', $modifier: '') {
$name: config.$namespace + config.$common-separator + $block;
@if $element != '' {
$name: $name + config.$element-separator + $element;
}
@if $modifier != '' {
$name: $name + config.$modifier-separator + $modifier;
}
@return $name;
}
// 字符串替换
@function str-replace($string, $search, $replace) {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace +
str-replace(
str-slice($string, $index + str-length($search)),
$search,
$replace
);
}
@return $string;
}
+135
View File
@@ -0,0 +1,135 @@
@use 'function' as *;
@use '../common/var' as *;
// forward mixins
@forward 'config';
@forward 'function';
@forward '_var';
@use 'config' as *;
// BEM
@mixin b($block) {
$B: $namespace + '-' + $block !global;
.#{$B} {
@content;
}
}
@mixin e($element) {
$E: $element !global;
$selector: &;
$currentSelector: '';
@each $unit in $element {
$currentSelector: #{$currentSelector +
'.' +
$B +
$element-separator +
$unit +
','};
}
@if hitAllSpecialNestRule($selector) {
@at-root {
#{$selector} {
#{$currentSelector} {
@content;
}
}
}
} @else {
@at-root {
#{$currentSelector} {
@content;
}
}
}
}
@mixin m($modifier) {
$selector: &;
$currentSelector: '';
@each $unit in $modifier {
$currentSelector: #{$currentSelector +
$selector +
$modifier-separator +
$unit +
','};
}
@at-root {
#{$currentSelector} {
@content;
}
}
}
@mixin configurable-m($modifier, $E-flag: false) {
$selector: &;
$interpolation: '';
@if $E-flag {
$interpolation: $element-separator + $E-flag;
}
@at-root {
#{$selector} {
.#{$B + $interpolation + $modifier-separator + $modifier} {
@content;
}
}
}
}
@mixin spec-selector(
$specSelector: '',
$element: $E,
$modifier: false,
$block: $B
) {
$modifierCombo: '';
@if $modifier {
$modifierCombo: $modifier-separator + $modifier;
}
@at-root {
#{&}#{$specSelector}.#{$block
+ $element-separator
+ $element
+ $modifierCombo} {
@content;
}
}
}
@mixin meb($modifier: false, $element: $E, $block: $B) {
$selector: &;
$modifierCombo: '';
@if $modifier {
$modifierCombo: $modifier-separator + $modifier;
}
@at-root {
#{$selector} {
.#{$block + $element-separator + $element + $modifierCombo} {
@content;
}
}
}
}
@mixin when($state) {
@at-root {
&.#{$state-prefix + $state} {
@content;
}
}
}
@mixin pseudo($pseudo) {
@at-root #{&}#{':#{$pseudo}'} {
@content;
}
}
+24
View File
@@ -0,0 +1,24 @@
@mixin utils-vertical-center {
$selector: &;
@at-root {
#{$selector}::after {
display: inline-block;
content: '';
height: 100%;
vertical-align: middle;
}
}
}
@mixin utils-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin utils-inline-flex-center {
display: inline-flex;
align-items: center;
justify-content: center;
}
+61
View File
@@ -0,0 +1,61 @@
/** Box Shadow */
@use 'sass:map';
@use 'common/var' as *;
@use 'mixins/mixins' as *;
@use 'mixins/function' as *;
@include b('shadow') {
@each $size in ('default', 'sm', 'md', lg) {
$box-shadow-sizes: map.get($box-shadow, $size);
$box-shadow-color: map.get($box-shadow-colors, $size);
$color-attr-value: '';
@each $box-shadow-size in $box-shadow-sizes {
$color-attr-value: $color-attr-value +
$box-shadow-size +
' var(--tn-shadow-color),';
}
// 移除最后一个逗号
$color-attr-value: str-slice(
$color-attr-value,
1,
str-length($color-attr-value) - 1
);
$default-attr-value: str-replace(
$color-attr-value,
'var(--tn-shadow-color)',
$box-shadow-color
);
$prefix: if($size == 'default', '', '-' + $size);
&#{$prefix} {
#{getCssVarName('shadow')}: #{$default-attr-value};
#{getCssVarName('shadow-colored')}: #{$color-attr-value};
}
&#{$prefix} {
box-shadow: #{getCssVar('shadow')};
}
}
&-blur {
position: relative;
&::before {
content: ' ';
display: block;
background: inherit;
background-color: inherit;
background-image: inherit;
filter: blur(5px);
position: absolute;
width: 100%;
height: 100%;
top: 5px;
left: 5px;
z-index: inherit;
opacity: 0.4;
transform-origin: 0 0;
border-radius: inherit;
transform: scale(1, 1);
}
}
}
+67
View File
@@ -0,0 +1,67 @@
// 尺寸信息
@use 'sass:math';
@use 'mixins/function' as *;
@use 'mixins/mixins' as *;
/* 宽高尺寸 start */
@each $type in ('w', 'h') {
$attr: if($type == 'w', 'width', 'height');
@include b($type) {
&-0 {
#{$attr}: 0px;
}
&-px {
#{$attr}: 1px;
}
@for $i from 2 through 12 {
@if index((2, 3, 4, 5, 6, 12), $i) {
@for $j from 1 to $i {
$classname: '#{$j}-#{$i}';
&-#{$classname} {
#{$attr}: percentage(math.div($j, $i));
}
}
}
}
&-auto {
#{$attr}: auto;
}
&-full {
#{$attr}: 100%;
}
&-screen {
$value: if($type == 'w', 100vw, 100vh);
#{$attr}: $value;
}
@if ($type == 'w') {
&-min {
#{$attr}: min-content;
}
&-max {
#{$attr}: max-content;
}
}
}
}
/* 宽高尺寸 end */
/* 内外边距 start */
@each $type in ('p', 'm') {
$attr: if($type == 'p', 'padding', 'margin');
$sub-attrs: (('', ''), (t, top), (r, right), (b, bottom), (l, left));
@each $sub-attr in $sub-attrs {
@include b($type + nth($sub-attr, 1)) {
@each $size in ('default', 'xs', 'sm', 'lg', 'xl', '0') {
$classname: if($size == 'default', '', '-' + $size);
&#{$classname} {
#{$attr + if(nth($sub-attr, 2) == '', '', '-' + nth($sub-attr, 2))}: getCssVar(
'pm-size',
if($size == 'default', '', $size)
);
}
}
}
}
}
/* 内外边距 end */
+76
View File
@@ -0,0 +1,76 @@
// 文本样式
@use 'mixins/function' as *;
@use 'mixins/mixins' as *;
/* 文本尺寸 start */
@include b('text') {
@each $size in ('default', 'xs', 'sm', 'lg', 'xl', '2xl', '3xl', '4xl') {
$classname: if($size == 'default', '', '-' + $size);
&#{$classname} {
font-size: getCssVar('font-size', if($size == 'default', '', $size));
}
}
}
/* 文本尺寸 end */
// 字体变成大写
.#{bem('text-uppercase')} {
text-transform: uppercase;
}
// 字体变成小写
.#{bem('text-lowercase')} {
text-transform: lowercase;
}
// 字体变成首字母大写
.#{bem('text-capitalize')} {
text-transform: capitalize;
}
// 字体加粗
.#{bem('text-bold')} {
font-weight: bold;
}
// 字体居中
.#{bem('text-center')} {
text-align: center;
}
// 字体居左
.#{bem('text-left')} {
text-align: left;
}
// 字体居右
.#{bem('text-right')} {
text-align: right;
}
// 如果需要使用用省略号隐藏更多的内容,对应的容器需要设置宽度
[class*=#{bem('text-ellipsis')}] {
width: 100%;
}
@for $i from 1 through 5 {
.#{bem('text-ellipsis-' + $i)} {
@if ($i == '1') {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} @else {
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
word-wrap: break-word;
-webkit-line-clamp: $i;
-webkit-box-orient: vertical !important;
}
}
}
// 透明字体
.#{bem('text-transparent')} {
-webkit-background-clip: text;
background-clip: text;
color: transparent !important;
}
// 换行
.#{bem('break-word')} {
word-wrap: break-word;
}
+34
View File
@@ -0,0 +1,34 @@
// 全局样式
@use '../mixins/mixins.scss' as *;
@use '../mixins/function.scss' as *;
page,
body {
// 修复点击view标签的时候会有蓝色遮罩
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
background-color: #{getCssVar('bg-color')};
font-size: #{getCssVar('font-size')};
color: #{getCssVar('text-color')};
}
// 隐藏滚动条
::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
// 设置ios设备底部安全区域高度设置
@include b('u-safe-area') {
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom) !important;
padding-bottom: env(safe-area-inset-bottom) !important;
@include m('more') {
padding-bottom: 20rpx;
padding-bottom: calc(constant(safe-area-inset-bottom) + 20rpx) !important;
padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx) !important;
}
}
+25
View File
@@ -0,0 +1,25 @@
// 按钮样式
@use '../mixins/mixins.scss' as *;
@include b('u-btn') {
// 去除原生button样式
&-clear {
background-color: transparent;
padding: 0;
margin: 0;
font-size: inherit;
line-height: inherit;
border-radius: inherit;
color: inherit;
border: none;
&::after {
border: none;
}
}
// 按钮点击时的效果
&-hover {
opacity: 0.8;
box-shadow: inset 10rpx 2rpx 40rpx 0rpx rgba(0, 0, 0, 0.05);
}
}
+29
View File
@@ -0,0 +1,29 @@
/*
h5特有的style样式
*/
/* 提升H5端uni.toast()的层级,避免被tn-modal等遮盖 start */
uni-toast {
z-index: 30090;
}
uni-toast .uni-toast {
z-index: inherit;
}
uni-modal {
z-index: 30090;
}
uni-modal .uni-mask {
z-index: inherit;
}
uni-modal .uni-modal {
z-index: inherit;
}
/* 提升H5端uni.toast()的层级,避免被tn-modal等遮盖 end */
// 通过样式穿透,隐藏H5下,scroll-view下的滚动条
scroll-view ::v-deep ::-webkit-scrollbar {
display: none;
width: 0 !important;
height: 0 !important;
-webkit-appearance: none;
background: transparent;
}
+10
View File
@@ -0,0 +1,10 @@
// image容器样式
@use '../mixins/mixins.scss' as *;
@include b('image') {
display: block;
width: 100%;
height: 100%;
border-radius: inherit;
will-change: transform;
}
+18
View File
@@ -0,0 +1,18 @@
@use './var.scss';
@use './base.scss';
@use './reset.scss';
@use './button.scss';
@use './scrollbar.scss';
@use './image.scss';
@use '../size.scss';
@use '../flex.scss';
@use '../shadow.scss';
@use '../color.scss';
@use '../text.scss';
@use '../border.scss';
/* #ifdef H5 */
@use './h5.style.scss';
/* #endif */
+23
View File
@@ -0,0 +1,23 @@
// 重置样式操作
view,
cover-view,
scroll-view,
swiper,
button,
input,
textarea,
label,
navigator,
image {
box-sizing: border-box;
}
button::after {
border: none;
}
image {
width: 100%;
height: 100%;
border-radius: inherit;
will-change: transform;
}
+13
View File
@@ -0,0 +1,13 @@
// scroll-view容器样式
@use '../mixins/mixins.scss' as *;
@include b('scroll-view') {
width: 100%;
height: 100%;
&::-webkit-scrollbar {
display: none;
width: 0;
height: 0;
color: transparent;
}
}
+60
View File
@@ -0,0 +1,60 @@
@use 'sass:map';
// CSS3 var
@use '../common/var' as *;
@use '../mixins/var' as *;
@use '../mixins/mixins' as *;
// for better performance do not dynamically change the root variable if you really
// do not need that, since this could introduce recalculation overhead for rendering.
// common
page,
body {
@include set-css-var-value('color-white', $color-white);
@include set-css-var-value('color-black', $color-black);
// get rgb
@each $type in $types {
@include set-css-color-rgb($colors, $type);
}
@each $color in $inner-color-names {
@include set-css-color-rgb($inner-colors, $color);
}
// Typography
@include set-component-css-var('font-size', $font-size);
// padding-margin-size
@include set-component-css-var('pm-size', $padding-margin-size);
// radius
@include set-component-css-var('border-radius', $border-radius);
// border-width
@include set-component-css-var('border-width', $border-width);
}
// for light
page,
body {
color-scheme: light;
@include set-css-var-value('color-white', $color-white);
@include set-css-var-value('color-black', $color-black);
// --tn-color-#{$type}
// --tn-color-#{$type}-light-{$i}
@each $type in $types {
@include set-css-color-type($colors, $type);
}
@each $color in $inner-color-names {
@include set-css-color-inner($inner-colors, $color);
}
// color-scheme
// Background --tn-bg-color-#{$type}
@include set-component-css-var('bg-color', $bg-color);
// --tn-text-color-#{$type}
@include set-component-css-var('text-color', $text-color);
}
+58
View File
@@ -0,0 +1,58 @@
@use 'sass:map';
// CSS3 var
@use 'common/var' as *;
@use 'mixins/var' as *;
@use 'mixins/mixins' as *;
// for better performance do not dynamically change the root variable if you really
// do not need that, since this could introduce recalculation overhead for rendering.
// common
:root {
@include set-css-var-value('color-white', $color-white);
@include set-css-var-value('color-black', $color-black);
// get rgb
@each $type in $types {
@include set-css-color-rgb($colors, $type);
}
@each $color in $inner-color-names {
@include set-css-color-rgb($inner-colors, $color);
}
// Typography
@include set-component-css-var('font-size', $font-size);
// padding-margin-size
@include set-component-css-var('pm-size', $padding-margin-size);
// radius
@include set-component-css-var('border-radius', $border-radius);
// border-width
@include set-component-css-var('border-width', $border-width);
}
// for light
:root {
color-scheme: light;
@include set-css-var-value('color-white', $color-white);
@include set-css-var-value('color-black', $color-black);
// --tn-color-#{$type}
// --tn-color-#{$type}-light-{$i}
@each $type in $types {
@include set-css-color-type($colors, $type);
}
@each $color in $inner-color-names {
@include set-css-color-inner($inner-colors, $color);
}
// color-scheme
// Background --tn-bg-color-#{$type}
@include set-component-css-var('bg-color', $bg-color);
// --tn-text-color-#{$type}
@include set-component-css-var('text-color', $text-color);
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";[class*=tn-border]{border-radius:inherit;box-sizing:border-box;border-width:var(--tn-border-width);border-style:solid}.tn-radius{border-radius:var(--tn-border-radius)}.tn-round{border-radius:var(--tn-border-radius-round)}.tn-border-bold{border-width:var(--tn-border-width-bold)}.tn-border-dashed{border-style:dashed}.tn-border-top{border-width:0;border-top-width:var(--tn-border-width)}.tn-border-top.tn-border-bold{border-top-width:var(--tn-border-width-bold)}.tn-border-right{border-width:0;border-right-width:var(--tn-border-width)}.tn-border-right.tn-border-bold{border-right-width:var(--tn-border-width-bold)}.tn-border-bottom{border-width:0;border-bottom-width:var(--tn-border-width)}.tn-border-bottom.tn-border-bold{border-bottom-width:var(--tn-border-width-bold)}.tn-border-left{border-width:0;border-left-width:var(--tn-border-width)}.tn-border-left.tn-border-bold{border-left-width:var(--tn-border-width-bold)}.tn-border-none{border-width:0}.tn-border-none-top{border-top-width:0}.tn-border-none-right{border-right-width:0}.tn-border-none-bottom{border-bottom-width:0}.tn-border-none-left{border-left-width:0}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";[class*=tn-flex]{display:flex;flex-direction:row;flex-wrap:nowrap}.tn-flex-row{flex-direction:row}.tn-flex-row-reverse{flex-direction:row-reverse}.tn-flex-column{flex-direction:column}.tn-flex-column-reverse{flex-direction:column-reverse}.tn-flex-nowrap{flex-wrap:nowrap}.tn-flex-wrap{flex-wrap:wrap}.tn-flex-wrap-reverse{flex-wrap:wrap-reverse}.tn-flex-1{flex:1 1 0%}.tn-flex-auto{flex:1 1 auto}.tn-flex-inital{flex:0 1 auto}.tn-flex-none{flex:none}.tn-flex-grow{flex-grow:1}.tn-flex-grow-0{flex-grow:0}.tn-flex-shrink{flex-shrink:1}.tn-flex-shrink-0{flex-shrink:0}.tn-flex-start-start{align-items:flex-start;justify-content:flex-start}.tn-flex-start-end{align-items:flex-start;justify-content:flex-end}.tn-flex-start-center{align-items:flex-start;justify-content:center}.tn-flex-start-between{align-items:flex-start;justify-content:space-between}.tn-flex-start-around{align-items:flex-start;justify-content:space-around}.tn-flex-end-start{align-items:flex-end;justify-content:flex-start}.tn-flex-end-end{align-items:flex-end;justify-content:flex-end}.tn-flex-end-center{align-items:flex-end;justify-content:center}.tn-flex-end-between{align-items:flex-end;justify-content:space-between}.tn-flex-end-around{align-items:flex-end;justify-content:space-around}.tn-flex-center-start{align-items:center;justify-content:flex-start}.tn-flex-center-end{align-items:center;justify-content:flex-end}.tn-flex-center-center{align-items:center;justify-content:center}.tn-flex-center-between{align-items:center;justify-content:space-between}.tn-flex-center-around{align-items:center;justify-content:space-around}.tn-flex-baseline-start{align-items:baseline;justify-content:flex-start}.tn-flex-baseline-end{align-items:baseline;justify-content:flex-end}.tn-flex-baseline-center{align-items:baseline;justify-content:center}.tn-flex-baseline-between{align-items:baseline;justify-content:space-between}.tn-flex-baseline-around{align-items:baseline;justify-content:space-around}.tn-flex-stretch-start{align-items:stretch;justify-content:flex-start}.tn-flex-stretch-end{align-items:stretch;justify-content:flex-end}.tn-flex-stretch-center{align-items:stretch;justify-content:center}.tn-flex-stretch-between{align-items:stretch;justify-content:space-between}.tn-flex-stretch-around{align-items:stretch;justify-content:space-around}.tn-flex-center{align-items:center;justify-content:center}.tn-flex.justify-start{justify-content:flex-start}.tn-flex.justify-end{justify-content:flex-end}.tn-flex.justify-center{justify-content:center}.tn-flex.justify-between{justify-content:space-between}.tn-flex.justify-around{justify-content:space-around}.tn-flex.justify-evenly{justify-content:space-evenly}.tn-flex.justify-item-start{justify-items:start}.tn-flex.justify-item-end{justify-items:end}.tn-flex.justify-item-center{justify-items:center}.tn-flex.justify-item-stretch{justify-items:stretch}.tn-flex.justify-self-auto{justify-self:auto}.tn-flex.justify-self-start{justify-self:start}.tn-flex.justify-self-end{justify-self:end}.tn-flex.justify-self-center{justify-self:center}.tn-flex.justify-self-stretch{justify-self:stretch}.tn-flex.content-start{align-content:flex-start}.tn-flex.content-end{align-content:flex-end}.tn-flex.content-center{align-content:center}.tn-flex.content-between{align-content:space-between}.tn-flex.content-around{align-content:space-around}.tn-flex.content-evenly{align-content:evenly}.tn-flex.items-start{align-items:flex-start}.tn-flex.items-end{align-items:flex-end}.tn-flex.items-center{align-items:center}.tn-flex.items-baseline{align-items:baseline}.tn-flex.items-stretch{align-items:stretch}
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";.tn-shadow{--tn-shadow:0px 5px 30px -9.5px rgba(0, 0, 0, 0.1);--tn-shadow-colored:0px 5px 30px -9.5px var(--tn-shadow-color)}.tn-shadow{box-shadow:var(--tn-shadow)}.tn-shadow-sm{--tn-shadow:0px 5px 30px -20px rgba(0, 0, 0, 0.1);--tn-shadow-colored:0px 5px 30px -20px var(--tn-shadow-color)}.tn-shadow-sm{box-shadow:var(--tn-shadow)}.tn-shadow-md{--tn-shadow:0px 5px 10px -1px rgba(0, 0, 0, 0.1),0px 0px 5px -2px rgba(0, 0, 0, 0.1);--tn-shadow-colored:0px 5px 10px -1px var(--tn-shadow-color),0px 0px 5px -2px var(--tn-shadow-color)}.tn-shadow-md{box-shadow:var(--tn-shadow)}.tn-shadow-lg{--tn-shadow:0px 10px 15px -2px rgba(0, 0, 0, 0.1),0px 5px 10px -2px rgba(0, 0, 0, 0.1);--tn-shadow-colored:0px 10px 15px -2px var(--tn-shadow-color),0px 5px 10px -2px var(--tn-shadow-color)}.tn-shadow-lg{box-shadow:var(--tn-shadow)}.tn-shadow-blur{position:relative}.tn-shadow-blur::before{content:" ";display:block;background:inherit;background-color:inherit;background-image:inherit;filter:blur(5px);position:absolute;width:100%;height:100%;top:5px;left:5px;z-index:inherit;opacity:.4;transform-origin:0 0;border-radius:inherit;transform:scale(1,1)}
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";.tn-w-0{width:0}.tn-w-px{width:1px}.tn-w-1-2{width:50%}.tn-w-1-3{width:33.3333333333%}.tn-w-2-3{width:66.6666666667%}.tn-w-1-4{width:25%}.tn-w-2-4{width:50%}.tn-w-3-4{width:75%}.tn-w-1-5{width:20%}.tn-w-2-5{width:40%}.tn-w-3-5{width:60%}.tn-w-4-5{width:80%}.tn-w-1-6{width:16.6666666667%}.tn-w-2-6{width:33.3333333333%}.tn-w-3-6{width:50%}.tn-w-4-6{width:66.6666666667%}.tn-w-5-6{width:83.3333333333%}.tn-w-1-12{width:8.3333333333%}.tn-w-2-12{width:16.6666666667%}.tn-w-3-12{width:25%}.tn-w-4-12{width:33.3333333333%}.tn-w-5-12{width:41.6666666667%}.tn-w-6-12{width:50%}.tn-w-7-12{width:58.3333333333%}.tn-w-8-12{width:66.6666666667%}.tn-w-9-12{width:75%}.tn-w-10-12{width:83.3333333333%}.tn-w-11-12{width:91.6666666667%}.tn-w-auto{width:auto}.tn-w-full{width:100%}.tn-w-screen{width:100vw}.tn-w-min{width:-moz-min-content;width:min-content}.tn-w-max{width:-moz-max-content;width:max-content}.tn-h-0{height:0}.tn-h-px{height:1px}.tn-h-1-2{height:50%}.tn-h-1-3{height:33.3333333333%}.tn-h-2-3{height:66.6666666667%}.tn-h-1-4{height:25%}.tn-h-2-4{height:50%}.tn-h-3-4{height:75%}.tn-h-1-5{height:20%}.tn-h-2-5{height:40%}.tn-h-3-5{height:60%}.tn-h-4-5{height:80%}.tn-h-1-6{height:16.6666666667%}.tn-h-2-6{height:33.3333333333%}.tn-h-3-6{height:50%}.tn-h-4-6{height:66.6666666667%}.tn-h-5-6{height:83.3333333333%}.tn-h-1-12{height:8.3333333333%}.tn-h-2-12{height:16.6666666667%}.tn-h-3-12{height:25%}.tn-h-4-12{height:33.3333333333%}.tn-h-5-12{height:41.6666666667%}.tn-h-6-12{height:50%}.tn-h-7-12{height:58.3333333333%}.tn-h-8-12{height:66.6666666667%}.tn-h-9-12{height:75%}.tn-h-10-12{height:83.3333333333%}.tn-h-11-12{height:91.6666666667%}.tn-h-auto{height:auto}.tn-h-full{height:100%}.tn-h-screen{height:100vh}.tn-p{padding:var(--tn-pm-size)}.tn-p-xs{padding:var(--tn-pm-size-xs)}.tn-p-sm{padding:var(--tn-pm-size-sm)}.tn-p-lg{padding:var(--tn-pm-size-lg)}.tn-p-xl{padding:var(--tn-pm-size-xl)}.tn-p-0{padding:var(--tn-pm-size-0)}.tn-pt{padding-top:var(--tn-pm-size)}.tn-pt-xs{padding-top:var(--tn-pm-size-xs)}.tn-pt-sm{padding-top:var(--tn-pm-size-sm)}.tn-pt-lg{padding-top:var(--tn-pm-size-lg)}.tn-pt-xl{padding-top:var(--tn-pm-size-xl)}.tn-pt-0{padding-top:var(--tn-pm-size-0)}.tn-pr{padding-right:var(--tn-pm-size)}.tn-pr-xs{padding-right:var(--tn-pm-size-xs)}.tn-pr-sm{padding-right:var(--tn-pm-size-sm)}.tn-pr-lg{padding-right:var(--tn-pm-size-lg)}.tn-pr-xl{padding-right:var(--tn-pm-size-xl)}.tn-pr-0{padding-right:var(--tn-pm-size-0)}.tn-pb{padding-bottom:var(--tn-pm-size)}.tn-pb-xs{padding-bottom:var(--tn-pm-size-xs)}.tn-pb-sm{padding-bottom:var(--tn-pm-size-sm)}.tn-pb-lg{padding-bottom:var(--tn-pm-size-lg)}.tn-pb-xl{padding-bottom:var(--tn-pm-size-xl)}.tn-pb-0{padding-bottom:var(--tn-pm-size-0)}.tn-pl{padding-left:var(--tn-pm-size)}.tn-pl-xs{padding-left:var(--tn-pm-size-xs)}.tn-pl-sm{padding-left:var(--tn-pm-size-sm)}.tn-pl-lg{padding-left:var(--tn-pm-size-lg)}.tn-pl-xl{padding-left:var(--tn-pm-size-xl)}.tn-pl-0{padding-left:var(--tn-pm-size-0)}.tn-m{margin:var(--tn-pm-size)}.tn-m-xs{margin:var(--tn-pm-size-xs)}.tn-m-sm{margin:var(--tn-pm-size-sm)}.tn-m-lg{margin:var(--tn-pm-size-lg)}.tn-m-xl{margin:var(--tn-pm-size-xl)}.tn-m-0{margin:var(--tn-pm-size-0)}.tn-mt{margin-top:var(--tn-pm-size)}.tn-mt-xs{margin-top:var(--tn-pm-size-xs)}.tn-mt-sm{margin-top:var(--tn-pm-size-sm)}.tn-mt-lg{margin-top:var(--tn-pm-size-lg)}.tn-mt-xl{margin-top:var(--tn-pm-size-xl)}.tn-mt-0{margin-top:var(--tn-pm-size-0)}.tn-mr{margin-right:var(--tn-pm-size)}.tn-mr-xs{margin-right:var(--tn-pm-size-xs)}.tn-mr-sm{margin-right:var(--tn-pm-size-sm)}.tn-mr-lg{margin-right:var(--tn-pm-size-lg)}.tn-mr-xl{margin-right:var(--tn-pm-size-xl)}.tn-mr-0{margin-right:var(--tn-pm-size-0)}.tn-mb{margin-bottom:var(--tn-pm-size)}.tn-mb-xs{margin-bottom:var(--tn-pm-size-xs)}.tn-mb-sm{margin-bottom:var(--tn-pm-size-sm)}.tn-mb-lg{margin-bottom:var(--tn-pm-size-lg)}.tn-mb-xl{margin-bottom:var(--tn-pm-size-xl)}.tn-mb-0{margin-bottom:var(--tn-pm-size-0)}.tn-ml{margin-left:var(--tn-pm-size)}.tn-ml-xs{margin-left:var(--tn-pm-size-xs)}.tn-ml-sm{margin-left:var(--tn-pm-size-sm)}.tn-ml-lg{margin-left:var(--tn-pm-size-lg)}.tn-ml-xl{margin-left:var(--tn-pm-size-xl)}.tn-ml-0{margin-left:var(--tn-pm-size-0)}
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";.tn-text{font-size:var(--tn-font-size)}.tn-text-xs{font-size:var(--tn-font-size-xs)}.tn-text-sm{font-size:var(--tn-font-size-sm)}.tn-text-lg{font-size:var(--tn-font-size-lg)}.tn-text-xl{font-size:var(--tn-font-size-xl)}.tn-text-2xl{font-size:var(--tn-font-size-2xl)}.tn-text-3xl{font-size:var(--tn-font-size-3xl)}.tn-text-4xl{font-size:var(--tn-font-size-4xl)}.tn-text-uppercase{text-transform:uppercase}.tn-text-lowercase{text-transform:lowercase}.tn-text-capitalize{text-transform:capitalize}.tn-text-bold{font-weight:700}.tn-text-center{text-align:center}.tn-text-left{text-align:left}.tn-text-right{text-align:right}[class*=tn-text-ellipsis]{width:100%}.tn-text-ellipsis-1{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;word-wrap:break-word;-webkit-line-clamp:1;-webkit-box-orient:vertical!important}.tn-text-ellipsis-2{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;word-wrap:break-word;-webkit-line-clamp:2;-webkit-box-orient:vertical!important}.tn-text-ellipsis-3{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;word-wrap:break-word;-webkit-line-clamp:3;-webkit-box-orient:vertical!important}.tn-text-ellipsis-4{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;word-wrap:break-word;-webkit-line-clamp:4;-webkit-box-orient:vertical!important}.tn-text-ellipsis-5{display:-webkit-box;overflow:hidden;text-overflow:ellipsis;word-wrap:break-word;-webkit-line-clamp:5;-webkit-box-orient:vertical!important}.tn-text-transparent{-webkit-background-clip:text;background-clip:text;color:transparent!important}.tn-break-word{word-wrap:break-word}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";body,page{-webkit-tap-highlight-color:transparent;background-color:var(--tn-bg-color);font-size:var(--tn-font-size);color:var(--tn-text-color)}::-webkit-scrollbar{display:none;width:0!important;height:0!important;-webkit-appearance:none;background:0 0}.tn-u-safe-area{padding-bottom:0;padding-bottom:constant(safe-area-inset-bottom)!important;padding-bottom:env(safe-area-inset-bottom)!important}.tn-u-safe-area--more{padding-bottom:20rpx;padding-bottom:calc(constant(safe-area-inset-bottom) + 20rpx)!important;padding-bottom:calc(env(safe-area-inset-bottom) + 20rpx)!important}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";.tn-u-btn-clear{background-color:transparent;padding:0;margin:0;font-size:inherit;line-height:inherit;border-radius:inherit;color:inherit;border:none}.tn-u-btn-clear::after{border:none}.tn-u-btn-hover{opacity:.8;box-shadow:inset 10rpx 2rpx 40rpx 0rpx rgba(0,0,0,.05)}
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";uni-toast{z-index:30090}uni-toast .uni-toast{z-index:inherit}uni-modal{z-index:30090}uni-modal .uni-mask{z-index:inherit}uni-modal .uni-modal{z-index:inherit}scroll-view ::v-deep ::-webkit-scrollbar{display:none;width:0!important;height:0!important;-webkit-appearance:none;background:0 0}
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";.tn-image{display:block;width:100%;height:100%;border-radius:inherit;will-change:transform}
+1
View File
@@ -0,0 +1 @@
button,cover-view,image,input,label,navigator,scroll-view,swiper,textarea,view{box-sizing:border-box}button::after{border:none}image{width:100%;height:100%;border-radius:inherit;will-change:transform}
+1
View File
@@ -0,0 +1 @@
@charset "UTF-8";.tn-scroll-view{width:100%;height:100%}.tn-scroll-view::-webkit-scrollbar{display:none;width:0;height:0;color:transparent}
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
Copyright (c) 2023-present Tuniao Technology
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+56
View File
@@ -0,0 +1,56 @@
# 图鸟 UI vue3 uniapp 版本
![TuniaoUI vue3 uniapp](https://resource.tuniaokj.com/images/vue3/market/vue3-banner-min.jpg 'TuniaoUI vue3 uniapp')
![TuniaoUI vue3 uniapp](https://resource.tuniaokj.com/images/vue3/market/vue3-code-min.jpg 'TuniaoUI vue3 uniapp')
<p align="center">
<a href="https://www.npmjs.com/package/@tuniao/tnui-vue3-uniapp">
<img src="https://img.shields.io/npm/v/%40tuniao%2Ftnui-vue3-uniapp.svg" alt="Version" />
</a>
<a href="https://www.npmjs.com/package/@tuniao/tnui-vue3-uniapp">
<img src="https://img.shields.io/npm/l/%40tuniao%2Ftnui-vue3-uniapp.svg" alt="Version" />
</a>
<a href="https://github.com/tuniaoTech/tuniaoui-rc-vue3-uniapp">
<img src="https://img.shields.io/github/forks/tuniaoTech/tuniaoui-rc-vue3-uniapp.svg" alt="GitHub Forks" />
</a>
<a href="https://github.com/tuniaoTech/tuniaoui-rc-vue3-uniapp">
<img src="https://img.shields.io/github/stars/tuniaoTech/tuniaoui-rc-vue3-uniapp.svg" alt="GitHub Star" />
</a>
<a href="https://github.com/tuniaoTech/tuniaoui-rc-vue3-uniapp">
<img src="https://img.shields.io/badge/Platform-uniapp-%2301beff.svg" alt="Platform" />
</a>
<a href="https://github.com/tuniaoTech/tuniaoui-rc-vue3-uniapp">
<img src="https://img.shields.io/badge/Language-vue3-%2301beff.svg" alt="Language" />
</a>
<a href="https://github.com/tuniaoTech/tuniaoui-rc-vue3-uniapp">
<img src="https://img.shields.io/badge/Language-Typescript-%2301beff.svg" alt="Language" />
</a>
</p>
## 介绍
`TuniaoUI vue3 uniapp`是一款基于`uniapp``vue3``Typescript`进行开发的 UI 组件库,提供丰富的组件进行快速开发,支持`微信小程序``APP``H5`,包含常用表单组件、信息展示组件等,并提供丰富的酷炫页面模板。
## 特性
- 包含基础常用的布局元素,flex、grid、浮动
- 完整一体的配色体系,包含 4 种色深模式,同时包含 4 套渐变配色
- 700+风格统一的图标 icon,60+精选组件,让开发者可以快速进行开发,icon 提供了单独的 npm 包,让开发者更加方便的更新图鸟 UI 的 icon
- 酷炫常用的页面模板,更有让你眼前一亮的界面效果
- 图片素材语雀便捷下载,图鸟生态共同成长
- 使用文档详尽说明,让你一文读懂图鸟 UI
## 链接
- [github 地址](https://github.com/tuniaoTech/tuniaoui-rc-vue3-uniapp)
- [图鸟 UI vue3 uniapp 演示代码](https://github.com/tuniaoTech/tuniaoui-uniapp-v3-demo)
- [图鸟 UI vue3 uniapp 使用文档](https://vue3.tuniaokj.com)
- [图鸟社区 - 资源下载](https://www.yuque.com/tuniao)
## 联系作者
联系微信: `tnkewo`
<p style="color: #01beff;">群内气氛挺不错的,应该或许可能大概,算是为数不多的,专搞技术的前端群,偶尔聊天摸鱼</p>
![扫码进群](https://resource.tuniaokj.com/images/about_tuniao/tn_author_qrcode.jpg '扫码进群')
@@ -0,0 +1,8 @@
import { withNoopInstall } from '../../utils'
import ActionSheet from './src/action-sheet.vue'
export const TnActionSheet = withNoopInstall(ActionSheet)
export default TnActionSheet
export * from './src/action-sheet'
export type { TnActionSheetInstance } from './src/instance'
@@ -0,0 +1,68 @@
import { ZIndex } from '../../../constants'
import { buildProps } from '../../../utils'
import type { ExtractPropTypes } from 'vue'
export interface ActionSheetAction {
/**
* @description 选项文字
*/
text: string
/**
* @description 选项备注
*/
desc?: string
/**
* @description 选项的值
*/
value?: string | number
}
/**
* @description ActionSheet options配置项
*/
export interface ActionSheetOptions {
/**
* @description 选项列表
*/
actions: ActionSheetAction[]
/**
* @description 标题
*/
title?: string
/**
* @description 取消按钮文字,如果为空则不显示取消按钮
*/
cancelText?: string
/**
* @description 是否显示遮罩
*/
mask?: boolean
/**
* @description 点击蒙层是否允许关闭
*/
maskClosable?: boolean
/**
* @description 点击取消按钮触发的回调函数,返回 false 或者返回 Promise 且被 reject 则取消关闭
*/
cancel?: () => (Promise<boolean> | void) | boolean
/**
* @description 点击选项触发的回调函数,返回 false 或者返回 Promise 且被 reject 则取消关闭
*/
select?: (
index: number,
value: string | number
) => (Promise<boolean> | void) | boolean
}
export const actionSheetProps = buildProps({
/**
* @description ZIndex
*/
zIndex: {
type: Number,
default: ZIndex.popup,
},
})
export type ActionSheetProps = ExtractPropTypes<typeof actionSheetProps>
@@ -0,0 +1,87 @@
<script lang="ts" setup>
import { useNamespace } from '../../../hooks'
import TnPopup from '../../popup/src/popup.vue'
import { actionSheetProps } from './action-sheet'
import { useActionSheet } from './composables'
defineProps(actionSheetProps)
const ns = useNamespace('action-sheet')
const {
data,
showTitle,
title,
showCancel,
cancelText,
overlay,
overlayClosable,
openPopup,
showActionSheet,
popupCloseEvent,
actionClickEvent,
} = useActionSheet()
defineExpose({
/**
* @description: 打开/显示 actionSheet 操作菜单
*/
show: showActionSheet,
})
</script>
<template>
<TnPopup
:model-value="openPopup"
open-direction="bottom"
:overlay="overlay"
:z-index="zIndex"
bg-color="transparent"
:safe-area-inset-bottom="false"
:overlay-closeable="overlayClosable"
@overlay-click="popupCloseEvent"
>
<view class="tn-u-safe-area" :class="[ns.b(), ns.is('shadow', !overlay)]">
<!-- 标题 -->
<view v-if="showTitle" :class="[ns.e('title')]">
<slot name="title">
{{ title }}
</slot>
</view>
<!-- 选项 -->
<view :class="[ns.e('actions')]">
<view
v-for="(item, index) in data"
:key="index"
:class="[ns.e('action')]"
hover-class="tn-u-btn-hover"
:hover-stay-time="150"
@tap.stop="actionClickEvent(index)"
>
<!-- 选项显示内容 -->
<view class="text">{{ item.text }}</view>
<!-- 选项描述 -->
<view v-if="item.desc" class="desc">{{ item.desc }}</view>
</view>
</view>
<!-- 取消按钮 -->
<view
v-if="showCancel"
:class="[ns.e('cancel')]"
hover-class="tn-u-btn-hover"
:hover-stay-time="150"
@tap.stop="popupCloseEvent"
>
<slot name="cancel">
{{ cancelText }}
</slot>
</view>
</view>
</TnPopup>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/action-sheet.scss';
</style>
@@ -0,0 +1 @@
export * from './use-action-sheet'
@@ -0,0 +1,141 @@
import { computed, getCurrentInstance, reactive, ref } from 'vue'
import {
debugWarn,
isBoolean,
isEmptyVariableInDefault,
isPromise,
} from '../../../../utils'
import type { ActionSheetOptions } from '../action-sheet'
export const useActionSheet = () => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnActionSheet', '请在 setup 中使用 useActionSheet')
}
const { slots } = instance!
// 默认配置项
const defaultOptions: ActionSheetOptions = {
actions: [],
title: '',
cancelText: '取 消',
mask: true,
maskClosable: false,
cancel: undefined,
select: undefined,
}
// 配置项
const options = reactive<ActionSheetOptions>({
actions: [],
title: '',
cancelText: '取 消',
mask: true,
cancel: undefined,
select: undefined,
})
// 操作菜单数据
const data = computed(() => options.actions)
// 是否显示标题
const showTitle = computed(() => !!slots?.title || !!options.title)
const title = computed(() => options.title)
// 是否显示取消按钮
const showCancel = computed(() => !!slots?.cancel || !!options.cancelText)
const cancelText = computed(() => options.cancelText)
// 是否显示遮罩
const overlay = computed(() => isEmptyVariableInDefault(options.mask, true))
// 点击遮罩是否允许关闭
const overlayClosable = computed(() =>
isEmptyVariableInDefault(options.maskClosable, false)
)
// 弹出popup弹框
const openPopup = ref<boolean>(false)
// popup弹框关闭事件
const popupCloseEvent = () => {
if (!options.cancel || overlayClosable.value) {
openPopup.value = false
return
}
const shouldCancel = options.cancel()
const isPromiseOrBoolean = [
isPromise(shouldCancel),
isBoolean(shouldCancel),
].includes(true)
if (!isPromiseOrBoolean) {
debugWarn(
'TnActionSheet',
'cancel 函数返回值必须是 Promise 或者 Boolean 类型'
)
return
}
if (isPromise(shouldCancel)) {
shouldCancel.then((res) => {
if (res) {
openPopup.value = false
}
})
} else {
if (shouldCancel) {
openPopup.value = false
}
}
}
// 选项点击事件
const actionClickEvent = (index: number) => {
if (!options.select) {
openPopup.value = false
return
}
const shouldSelect = options.select(index, options.actions[index].value!)
const isPromiseOrBoolean = [
isPromise(shouldSelect),
isBoolean(shouldSelect),
].includes(true)
if (!isPromiseOrBoolean) {
debugWarn(
'TnActionSheet',
'select 函数返回值必须是 Promise 或者 Boolean 类型'
)
return
}
if (isPromise(shouldSelect)) {
shouldSelect.then((res) => {
if (res) {
openPopup.value = false
}
})
} else {
if (shouldSelect) {
openPopup.value = false
}
}
}
const showActionSheet = (_options: ActionSheetOptions) => {
Object.assign(options, defaultOptions, _options)
openPopup.value = true
}
return {
data,
showTitle,
title,
showCancel,
cancelText,
overlay,
overlayClosable,
openPopup,
showActionSheet,
popupCloseEvent,
actionClickEvent,
}
}
@@ -0,0 +1,3 @@
import type ActionSheet from './action-sheet.vue'
export type TnActionSheetInstance = InstanceType<typeof ActionSheet>
+14
View File
@@ -0,0 +1,14 @@
import { withInstall, withNoopInstall } from '../../utils'
import Avatar from './src/avatar.vue'
import AvatarGroup from './src/avatar-group.vue'
export const TnAvatar = withInstall(Avatar, {
AvatarGroup,
})
export const TnAvatarGroup = withNoopInstall(AvatarGroup)
export default TnAvatar
export * from './src/avatar'
export * from './src/avatar-group'
export type { AvatarInstance, AvatarGroupInstance } from './src/instance'
@@ -0,0 +1,77 @@
import { buildProps } from '../../../utils'
import { avatarProps } from './avatar'
import type { ExtractPropTypes } from 'vue'
export const avatarGroupProps = buildProps({
/**
* @description
*/
iconConfig: avatarProps.iconConfig,
/**
* @description
*/
type: avatarProps.type,
/**
* @description
*/
size: avatarProps.size,
/**
* @description
*/
shape: avatarProps.shape,
/**
* @description
*/
imgMode: avatarProps.imgMode,
/**
* @description
*/
bgColor: avatarProps.bgColor,
/**
* @description
*/
border: {
type: Boolean,
default: true,
},
/**
* @description
*/
borderColor: {
type: String,
default: 'tn-white',
},
/**
* @description
*/
borderBold: avatarProps.borderBold,
/**
* @description
*/
shadow: avatarProps.shadow,
/**
* @description
*/
shadowColor: avatarProps.shadowColor,
/**
* @description
*/
badgeConfig: avatarProps.badgeConfig,
/**
* @description
*/
gap: {
type: [String, Number],
default: 0.4,
},
})
export const avatarGroupEmits = {
/**
* @description
*/
click: (index: number) => typeof index === 'number',
}
export type AvatarGroupProps = ExtractPropTypes<typeof avatarGroupProps>
export type AvatarGroupEmits = typeof avatarGroupEmits
@@ -0,0 +1,23 @@
<script lang="ts" setup>
import { useNamespace } from '../../../hooks'
import { avatarGroupEmits, avatarGroupProps } from './avatar-group'
import { useAvatarGroup } from './composables'
const props = defineProps(avatarGroupProps)
const emits = defineEmits(avatarGroupEmits)
const ns = useNamespace('avatar')
useAvatarGroup(props, emits)
</script>
<template>
<view :class="`${ns.b('group')}`">
<slot />
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/avatar-group.scss';
</style>
+144
View File
@@ -0,0 +1,144 @@
import { useComponentBoolean } from '../../base/composables/use-component-common-props'
import { buildProps, definePropType } from '../../../utils'
import { componentImgModes, componentTypes } from '../../../constants'
import type { ExtractPropTypes } from 'vue'
import type { BadgeProps } from '../../badge'
import type { IconProps } from '../../icon'
/**
* @description
*/
export const avatarShape = ['circle', 'square'] as const
/**
* @description
*/
export interface AvatarIconProps {
/**
* @description
*/
color?: IconProps['color']
/**
* @description
*/
size?: IconProps['size']
/**
* @description
*/
bold?: IconProps['bold']
}
/**
* @description
*/
export type AvatarBadgeProps = Partial<
Pick<
BadgeProps,
| 'max'
| 'type'
| 'size'
| 'bgColor'
| 'textColor'
| 'fontSize'
| 'bold'
| 'dot'
| 'absolute'
| 'absolutePosition'
| 'absoluteCenter'
>
>
export const avatarProps = buildProps({
/**
* @description (url地址和绝对地址)
*/
url: String,
/**
* @descripttion
*/
icon: String,
/**
* @description
*/
iconConfig: {
type: definePropType<AvatarIconProps>(Object),
default: () => ({}),
},
/**
* @description
*/
type: {
type: String,
values: componentTypes,
default: '',
},
/**
* @description
*/
size: {
type: [String, Number],
},
/**
* @description
*/
shape: {
type: String,
values: avatarShape,
default: 'circle',
},
/**
* @description
*/
imgMode: {
type: String,
values: componentImgModes,
default: 'aspectFill',
},
/**
* @description
*/
bgColor: String,
/**
* @description
*/
border: useComponentBoolean,
/**
* @description
*/
borderColor: String,
/**
* @description
*/
borderBold: useComponentBoolean,
/**
* @description
*/
shadow: useComponentBoolean,
/**
* @description
*/
shadowColor: String,
/**
* @description
*/
badge: {
type: [String, Number],
},
/**
* @description
*/
badgeConfig: {
type: definePropType<AvatarBadgeProps>(Object),
default: () => ({}),
},
})
export const avatarEmits = {
/**
* @description
*/
click: () => true,
}
export type AvatarProps = ExtractPropTypes<typeof avatarProps>
export type AvatarEmits = typeof avatarEmits
+62
View File
@@ -0,0 +1,62 @@
<script lang="ts" setup>
import TnIcon from '../../icon/src/icon.vue'
import TnBadge from '../../badge/src/badge.vue'
import { avatarEmits, avatarProps } from './avatar'
import {
useAvatar,
useAvatarBadgeProps,
useAvatarCustomStyle,
useAvatarIconConfig,
useAvatarProps,
} from './composables'
const props = defineProps(avatarProps)
const emits = defineEmits(avatarEmits)
const { componentId, avatarGroupIndex, avatarWidth, avatarClick } = useAvatar(
props,
emits
)
const { ns, avatarClass, avatarStyle } = useAvatarCustomStyle(
props,
avatarGroupIndex,
avatarWidth
)
const { imgMode } = useAvatarProps(props)
const { iconSize, iconColor, iconBold } = useAvatarIconConfig(props.iconConfig)
const { badgeConfig } = useAvatarBadgeProps(props)
</script>
<template>
<view
:id="componentId"
:class="[avatarClass]"
:style="avatarStyle"
@tap="avatarClick"
>
<!-- 图片头像 -->
<view v-if="url" :class="[ns.e('image')]">
<image class="image" :src="url" :mode="imgMode" />
</view>
<!-- 图标头像 -->
<view v-else-if="icon" :class="[ns.e('icon')]">
<TnIcon
:name="icon"
:color="iconColor"
:size="iconSize"
:bold="iconBold"
/>
</view>
<!-- 自定义 -->
<view v-else :class="[ns.e('custom')]">
<slot />
</view>
<!-- 角标 -->
<TnBadge v-bind="badgeConfig" />
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/avatar.scss';
</style>
@@ -0,0 +1,119 @@
import { computed } from 'vue'
import {
useComponentColor,
useComponentSize,
useNamespace,
} from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import { useAvatarProps } from './use-avatar-props'
import type { CSSProperties, Ref } from 'vue'
import type { AvatarProps } from '../avatar'
export const useAvatarCustomStyle = (
props: AvatarProps,
groupIndex: Ref<number>,
avatarWidth: Ref<number>
) => {
const ns = useNamespace('avatar')
const {
type,
size,
shape,
bgColor,
border,
borderColor,
shadow,
shadowColor,
avatarGap,
} = useAvatarProps(props)
// 解析背景颜色
const [bgColorClass, bgColorStyle] = useComponentColor(bgColor, 'bg')
// 解析边框颜色
const [borderColorClass, borderColorStyle] = useComponentColor(
borderColor,
'border'
)
// 解析阴影颜色
const [shadowColorClass] = useComponentColor(shadowColor, 'shadow')
// 解析头像尺寸
const { sizeType } = useComponentSize(size.value)
// 头像动态类
const avatarClass = computed<string>(() => {
const cls: string[] = []
cls.push(ns.b())
// 设置头像颜色类型
if (type.value) cls.push(`tn-type-${type.value}_bg`)
// 设置背景颜色
if (!type.value && bgColorClass.value) cls.push(bgColorClass.value)
// 设置头像尺寸
if (sizeType.value === 'inner') cls.push(ns.m(size.value as string))
// 设置头像形状
if (shape.value) cls.push(ns.m(shape.value))
// 设置边框
if (border.value) {
cls.push('tn-border')
// 设置边框颜色
if (borderColorClass.value) cls.push(borderColorClass.value)
}
// 设置阴影
if (shadow.value) {
cls.push('tn-shadow')
// 设置阴影颜色
if (shadowColorClass.value) cls.push(shadowColorClass.value)
}
return cls.join(' ')
})
// 头像动态样式
const avatarStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 设置头像尺寸
if (sizeType.value === 'custom') {
style.width = formatDomSizeValue(size.value)
style.height = style.width
}
// 设置背景颜色
if (bgColorStyle.value) style.backgroundColor = bgColorStyle.value
// 设置边框颜色
if (border.value && borderColorStyle.value)
style.borderColor = borderColorStyle.value
// 如果是头像组,设置头像间距
if (groupIndex.value != -1) {
style.zIndex = groupIndex.value + 1
// style.transform = `translateX(calc(${
// (groupAvatarCount.value - groupIndex.value - 1) * 100
// }% * ${avatarGap.value}))`
if (groupIndex.value > 0) {
style.marginLeft = `calc(-${avatarWidth.value * avatarGap.value}px)`
} else {
style.marginLeft = '0px'
}
}
return style
})
return {
ns,
avatarClass,
avatarStyle,
}
}
@@ -0,0 +1,6 @@
export * from './use-avatar-icon-props'
export * from './use-avatar-props'
export * from './avatar-custom'
export * from './use-avatar'
export * from './use-avatar-group'
export * from './use-avatar-badge-props'
@@ -0,0 +1,120 @@
import { computed, inject } from 'vue'
import { avatarGroupContextKey } from '../../../../tokens'
import { isEmptyDoubleVariableInDefault } from '../../../../utils'
import type { AvatarProps } from '../avatar'
import type { BadgeProps } from '../../../badge'
export const useAvatarBadgeProps = (props: AvatarProps) => {
const avatarGroup = inject(avatarGroupContextKey, undefined)
// 徽标最大值
const max = computed<BadgeProps['max']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.max,
avatarGroup?.badgeConfig?.max
)
})
// 徽标类型
const type = computed<BadgeProps['type']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.type,
avatarGroup?.badgeConfig?.type,
'primary'
)
})
// 徽标背景颜色
const bgColor = computed<BadgeProps['bgColor']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.bgColor,
avatarGroup?.badgeConfig?.bgColor
)
})
// 徽标文本颜色
const textColor = computed<BadgeProps['textColor']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.textColor,
avatarGroup?.badgeConfig?.textColor
)
})
// 徽标字体大小
const fontSize = computed<BadgeProps['fontSize']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.fontSize,
avatarGroup?.badgeConfig?.fontSize
)
})
// 徽标大小
const size = computed<BadgeProps['size']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.size,
avatarGroup?.badgeConfig?.size
)
})
// 徽标加粗
const bold = computed<BadgeProps['bold']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.bold,
avatarGroup?.badgeConfig?.bold,
false
)
})
// 设置点徽标
const dot = computed<BadgeProps['dot']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.dot,
avatarGroup?.badgeConfig?.dot,
false
)
})
// 设置徽标的位置
const absolutePosition = computed<BadgeProps['absolutePosition']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.absolutePosition,
avatarGroup?.badgeConfig?.absolutePosition,
{}
)
})
// 设置徽标是否居中
const absoluteCenter = computed<BadgeProps['absoluteCenter']>(() => {
return isEmptyDoubleVariableInDefault(
props?.badgeConfig?.absoluteCenter,
avatarGroup?.badgeConfig?.absoluteCenter,
true
)
})
// 徽标配置
const badgeConfig = computed<BadgeProps>(() => {
return {
value: props.badge,
max: max.value,
type: type.value,
bgColor: bgColor.value,
textColor: textColor.value,
fontSize: fontSize.value,
size: size.value,
bold: bold.value,
customClass: '',
customStyle: {},
dot: dot.value,
absolute: true,
absolutePosition: absolutePosition.value,
absoluteCenter: absoluteCenter.value,
index: '',
}
})
return {
badgeConfig,
}
}
@@ -0,0 +1,36 @@
import { provide, reactive, toRefs } from 'vue'
import { avatarGroupContextKey } from '../../../../tokens'
import { useOrderedChildren } from '../../../../hooks'
import type { SetupContext } from 'vue'
import type { AvatarContext } from '../../../../tokens'
import type { AvatarGroupEmits, AvatarGroupProps } from '../avatar-group'
export const useAvatarGroup = (
props: AvatarGroupProps,
emits: SetupContext<AvatarGroupEmits>['emit']
) => {
const {
children: avatarItems,
addChild: addItem,
removeChild: removeItem,
} = useOrderedChildren<AvatarContext>()
const handleItemClick = (uid: number) => {
// 查找出对应头像的索引
const index = avatarItems.value.findIndex((item) => item.uid === uid)
emits('click', index)
}
provide(
avatarGroupContextKey,
reactive({
...toRefs(props),
avatarItems,
addItem,
removeItem,
handleItemClick,
})
)
}
@@ -0,0 +1,29 @@
import { computed, inject } from 'vue'
import { avatarGroupContextKey } from '../../../../tokens'
import type { AvatarProps } from '../avatar'
export const useAvatarIconConfig = (config: AvatarProps['iconConfig']) => {
const avatarGroup = inject(avatarGroupContextKey, undefined)
// 图标颜色
const iconColor = computed<string>(() => {
return config?.color || avatarGroup?.iconConfig?.color || ''
})
// 图标大小
const iconSize = computed<string | number>(() => {
return config?.size || avatarGroup?.iconConfig?.size || ''
})
// 图标加粗
const iconBold = computed<boolean>(() => {
return config?.bold || avatarGroup?.iconConfig?.bold || false
})
return {
iconColor,
iconSize,
iconBold,
}
}
@@ -0,0 +1,116 @@
import { computed, inject } from 'vue'
import { avatarGroupContextKey } from '../../../../tokens'
import {
isEmptyDoubleVariableInDefault,
isEmptyVariableInDefault,
} from '../../../../utils'
import type { AvatarProps } from '../avatar'
export const useAvatarProps = (props: AvatarProps) => {
const avatarGroup = inject(avatarGroupContextKey, undefined)
// 头像颜色类型
const type = computed<string>(() => {
return isEmptyDoubleVariableInDefault(props?.type, avatarGroup?.type, '')
})
// 头像尺寸
const size = computed<string | number>(() => {
return isEmptyDoubleVariableInDefault(props?.size, avatarGroup?.size, '')
})
// 头像形状
const shape = computed<string>(() => {
return isEmptyDoubleVariableInDefault(
props?.shape,
avatarGroup?.shape,
'circle'
)
})
// 头像图片模式
const imgMode = computed<string>(() => {
return isEmptyDoubleVariableInDefault(
props?.imgMode,
avatarGroup?.imgMode,
'aspectFill'
)
})
// 背景颜色
const bgColor = computed<string>(() => {
return isEmptyDoubleVariableInDefault(
props?.bgColor,
avatarGroup?.bgColor,
'tn-gray-light'
)
})
// 显示边框
const border = computed<boolean>(() => {
return isEmptyDoubleVariableInDefault(
props?.border,
avatarGroup?.border,
false
)
})
// 边框颜色
const borderColor = computed<string>(() => {
return isEmptyDoubleVariableInDefault(
props?.borderColor,
avatarGroup?.borderColor,
''
)
})
// 是否加粗边框
const borderBold = computed<boolean>(() => {
return isEmptyDoubleVariableInDefault(
props?.borderBold,
avatarGroup?.borderBold,
false
)
})
// 显示阴影
const shadow = computed<boolean>(() => {
return isEmptyDoubleVariableInDefault(
props?.shadow,
avatarGroup?.shadow,
false
)
})
// 阴影颜色
const shadowColor = computed<string>(() => {
return isEmptyDoubleVariableInDefault(
props?.shadowColor,
avatarGroup?.shadowColor,
''
)
})
// 头像间的间距
const avatarGap = computed<number>(() => {
let gap = Number(isEmptyVariableInDefault(avatarGroup?.gap, 0))
if (gap < 0) gap = 0
if (gap > 1) gap = 1
return gap
})
return {
type,
size,
shape,
imgMode,
bgColor,
border,
borderColor,
borderBold,
shadow,
shadowColor,
avatarGap,
}
}
@@ -0,0 +1,97 @@
import {
computed,
getCurrentInstance,
inject,
nextTick,
onUnmounted,
ref,
} from 'vue'
import { avatarGroupContextKey } from '../../../../tokens'
import { useSelectorQuery } from '../../../../hooks'
import {
debugWarn,
generateId,
isEmptyVariableInDefault,
} from '../../../../utils'
import type { SetupContext } from 'vue'
import type { AvatarEmits, AvatarProps } from '../avatar'
export const useAvatar = (
props: AvatarProps,
emits: SetupContext<AvatarEmits>['emit']
) => {
const instance = getCurrentInstance()
if (!instance) {
debugWarn('TnAvatarGroup', '请在 setup 中使用 useAvatarGroup')
}
const { uid } = instance!
const avatarGroup = inject(avatarGroupContextKey, undefined)
avatarGroup?.addItem({ uid })
const componentId = `ta-${generateId()}`
const { getSelectorNodeInfo } = useSelectorQuery(instance)
// 头像组头像数量
const groupAvatarCount = computed<number>(() => {
return isEmptyVariableInDefault(avatarGroup?.avatarItems.length, 0)
})
const avatarGroupIndex = ref<number>(-1)
nextTick(() => {
// 获取当前头像的索引
const avatarIndex = avatarGroup?.avatarItems.findIndex(
(item) => item.uid === uid
)
avatarGroupIndex.value = isEmptyVariableInDefault(avatarIndex, -1)
if (!avatarWidth.value && avatarGroupIndex.value !== -1) {
getAvatarWidthNodeInfo()
}
})
// 头像宽度信息
const avatarWidth = ref<number>(0)
// 获取头像的宽度信息
let initCount = 0
const getAvatarWidthNodeInfo = async () => {
try {
const rectInfo = await getSelectorNodeInfo(`#${componentId}`)
if (!rectInfo.width) {
throw new Error('获取头像宽度信息失败')
}
avatarWidth.value = rectInfo.width || 0
} catch (err) {
if (initCount > 10) {
initCount = 0
debugWarn('TnAvatar', `获取头像宽度信息失败:${err}`)
return
}
initCount++
setTimeout(() => {
getAvatarWidthNodeInfo()
}, 150)
}
}
const avatarClick = () => {
avatarGroup?.handleItemClick(uid)
emits('click')
}
onUnmounted(() => {
avatarGroup?.removeItem(uid)
})
return {
componentId,
groupAvatarCount,
avatarGroupIndex,
avatarWidth,
avatarClick,
}
}
@@ -0,0 +1,5 @@
import type Avatar from './avatar.vue'
import type AvatarGroup from './avatar-group.vue'
export type AvatarInstance = InstanceType<typeof Avatar>
export type AvatarGroupInstance = InstanceType<typeof AvatarGroup>
+9
View File
@@ -0,0 +1,9 @@
import { withNoopInstall } from '../../utils'
import Badge from './src/badge.vue'
export const TnBadge = withNoopInstall(Badge)
export default TnBadge
export * from './src/badge'
export type { BadgeInstance } from './src/instance'
+117
View File
@@ -0,0 +1,117 @@
import {
useComponentCustomStyleProp,
useComponentIndexProp,
} from '../../base/composables/use-component-common-props'
import { buildProps, definePropType } from '../../../utils'
import { componentTypes } from '../../../constants'
import type { ExtractPropTypes } from 'vue'
import type { ComponentIndex } from '../../base/composables/use-component-common-props'
/**
* @description
*/
export interface BadgeAbsolutePositionConfig {
/**
* @description
*/
top?: string | number
/**
* @description
*/
right?: string | number
}
export const badgeProps = buildProps({
/**
* @description max会显示{max}+icon-
*/
value: {
type: [String, Number],
},
/**
* @description value为number时有效{max}+
*/
max: {
type: [String, Number],
},
/**
* @description
*/
type: {
type: String,
values: componentTypes,
default: 'primary',
},
/**
* @description , tn开头则使用图鸟内置的颜色
*/
bgColor: String,
/**
* @description , tn开头则使用图鸟内置的颜色
*/
textColor: String,
/**
* @description
*/
size: {
type: [String, Number],
},
/**
* @description
*/
fontSize: {
type: [String, Number],
},
/**
* @description
*/
bold: Boolean,
/**
* @description
*/
customStyle: useComponentCustomStyleProp,
/**
* @description
*/
customClass: String,
/**
* @description
*/
dot: Boolean,
/**
* @description
*/
absolute: {
type: Boolean,
default: true,
},
/**
* @description
*/
absolutePosition: {
type: definePropType<BadgeAbsolutePositionConfig>(Object),
default: () => ({}),
},
/**
* @description
*/
absoluteCenter: {
type: Boolean,
default: true,
},
/**
* @description
*/
index: useComponentIndexProp,
})
export const badgeEmits = {
/**
* @description
*/
click: (index: ComponentIndex) =>
typeof index === 'number' || typeof index === 'string',
}
export type BadgeProps = ExtractPropTypes<typeof badgeProps>
export type BadgeEmits = typeof badgeEmits
+51
View File
@@ -0,0 +1,51 @@
<script lang="ts" setup>
import { computed, useSlots } from 'vue'
import TnIcon from '../../icon/src/icon.vue'
import { badgeEmits, badgeProps } from './badge'
import { useBadge, useBadgeCustomStyle } from './composables'
const props = defineProps(badgeProps)
const emits = defineEmits(badgeEmits)
const slots = useSlots()
const { ns, contentNs, badgeContentClass, badgeContentStyle } =
useBadgeCustomStyle(props)
const { showBadge, contentType, content, badgeClick } = useBadge(props, emits)
// badge
const badgeClass = computed<string>(() => {
const cls: string[] = []
cls.push(ns.b())
if (!slots?.default) {
//
if (props.absolute) {
cls.push(ns.e('absolute'))
if (props.absoluteCenter) cls.push(ns.em('absolute', 'center'))
}
}
return cls.join(' ')
})
</script>
<template>
<view :class="[badgeClass]">
<slot />
<!-- 徽标 -->
<view
v-if="showBadge"
:class="[badgeContentClass]"
:style="badgeContentStyle"
@tap.stop="badgeClick"
>
<template v-if="content">
<TnIcon v-if="contentType === 'icon'" :name="content" />
<span v-else :class="`${contentNs.e('data')}`">{{ content }}</span>
</template>
</view>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/badge.scss';
</style>
@@ -0,0 +1,105 @@
import { computed, toRef } from 'vue'
import {
useComponentColor,
useComponentSize,
useNamespace,
} from '../../../../hooks'
import { formatDomSizeValue, isEmpty } from '../../../../utils'
import { useBadge } from './use-badge'
import type { CSSProperties } from 'vue'
import type { BadgeProps } from '../badge'
export const useBadgeCustomStyle = (props: BadgeProps) => {
const ns = useNamespace('badge')
const contentNs = useNamespace('badge-content')
const { contentType } = useBadge(props)
// 解析背景颜色
const [bgColorClass, bgColorStyle] = useComponentColor(
toRef(props, 'bgColor'),
'bg'
)
// 解析文字颜色
const [textColorClass, textColorStyle] = useComponentColor(
toRef(props, 'textColor'),
'text'
)
// 解析尺寸大小
const { sizeType } = useComponentSize(props.size)
// 徽标内容对应的类
const badgeContentClass = computed<string>(() => {
const cls: string[] = []
cls.push(contentNs.b())
// 点徽标
if (props.dot) cls.push(contentNs.m('dot'))
// 图标徽标
if (contentType.value === 'icon') cls.push(contentNs.m('icon'))
// 绝对定位
if (props.absolute) {
cls.push(contentNs.e('absolute'))
if (props.absoluteCenter) cls.push(contentNs.em('absolute', 'center'))
}
// 设置类型颜色
if (props.type) cls.push(`tn-type-${props.type}_bg`)
// 背景颜色
if (bgColorClass.value) cls.push(bgColorClass.value)
// 字体颜色
if (textColorClass.value) cls.push(textColorClass.value)
// 尺寸大小
if (props.size && sizeType.value === 'inner')
cls.push(contentNs.m(props.size as string))
// 加粗字体
if (props.bold) cls.push('tn-text-bold')
if (props.customClass) cls.push(props.customClass)
return cls.join(' ')
})
// 徽标对应的样式
const badgeContentStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
// 背景颜色
if (bgColorStyle.value) style.backgroundColor = bgColorStyle.value
// 字体颜色
if (textColorStyle.value) style.color = textColorStyle.value
// 尺寸大小
if (
props.size &&
(sizeType.value === 'custom' || contentType.value === 'icon')
)
style.width = style.height = formatDomSizeValue(props.size)
// 字体尺寸
if (props.fontSize) style.fontSize = formatDomSizeValue(props.fontSize)
// 绝对定位是徽标偏移量
if (props.absolutePosition.top)
style.top = formatDomSizeValue(props.absolutePosition.top)
if (props.absolutePosition.right)
style.right = formatDomSizeValue(props.absolutePosition.right)
if (!isEmpty(props.customStyle)) {
Object.assign(style, props.customStyle)
}
return style
})
return {
ns,
contentNs,
badgeContentClass,
badgeContentStyle,
}
}
@@ -0,0 +1,2 @@
export * from './badge-custom'
export * from './use-badge'
@@ -0,0 +1,52 @@
import { computed } from 'vue'
import { isNumber, isString } from '../../../../utils'
import type { SetupContext } from 'vue'
import type { BadgeEmits, BadgeProps } from '../badge'
export const badgeContentTypes = ['number', 'string', 'icon'] as const
export type BadgeContentType = (typeof badgeContentTypes)[number]
export const useBadge = (
props: BadgeProps,
emits?: SetupContext<BadgeEmits>['emit']
) => {
// 判断是否需要显示角标
const showBadge = computed<boolean>(() => {
return !!props.dot || (props.value !== '' && props.value !== undefined)
})
// 显示的内容类型
const contentType = computed<BadgeContentType>(() => {
let type: BadgeContentType = 'string'
if (isNumber(props.value)) type = 'number'
if (isString(props.value) && props.value.startsWith('icon-')) type = 'icon'
return type
})
// 显示的内容
const content = computed<string>(() => {
if (props.dot) return ''
if (contentType.value === 'number' && props.max) {
const value = Number(props.value || 0)
const max = Number(props.max || 0)
return value > max ? `${max}+` : `${value}`
}
if (contentType.value === 'icon')
return (props.value as string).replace('icon-', '')
return props.value as string
})
// 角标点击事件
const badgeClick = () => {
if (emits) emits('click', props.index)
}
return {
showBadge,
contentType,
content,
badgeClick,
}
}
@@ -0,0 +1,3 @@
import type Badge from './badge.vue'
export type BadgeInstance = InstanceType<typeof Badge>
@@ -0,0 +1,36 @@
import { buildProps } from '../../../../utils'
import { useFormSizeProps } from '../../composables/use-component-common-props'
import { checkboxCheckedShapes } from '../../types/checkbox'
export const checkboxBaseProps = buildProps({
/**
* @description
*/
size: useFormSizeProps,
/**
* @description
*/
checkedShape: {
type: String,
values: checkboxCheckedShapes,
},
/**
* @description
*/
disabled: Boolean,
/**
* @description
*/
labelDisabled: Boolean,
/**
* @description
*/
border: Boolean,
/**
* @description tn开头则使用图鸟内置的颜色只支持普通颜色
*/
activeColor: {
type: String,
default: '',
},
} as const)
@@ -0,0 +1,16 @@
import { buildProps } from '../../../../utils'
import { formComponentSizes } from '../../../../constants'
export const formMetaProps = buildProps({
/**
* @description
*/
size: {
type: String,
values: formComponentSizes,
},
/**
* @description
*/
disabled: Boolean,
} as const)
@@ -0,0 +1,52 @@
import { buildProps } from '../../../../utils'
import { ZIndex } from '../../../../constants'
export const pickerBaseProps = buildProps({
/**
* @description
*/
showCancel: {
type: Boolean,
default: true,
},
/**
* @description
*/
cancelText: {
type: String,
default: '取 消',
},
/**
* @description
*/
cancelColor: String,
/**
* @description
*/
showConfirm: {
type: Boolean,
default: true,
},
/**
* @description
*/
confirmText: {
type: String,
default: '确 定',
},
/**
* @description
*/
confirmColor: String,
/**
* @description
*/
mask: Boolean,
/**
* zIndex
*/
zIndex: {
type: Number,
default: ZIndex.popup,
},
} as const)
@@ -0,0 +1,34 @@
import { buildProps } from '../../../../utils'
import type { ExtractPropTypes } from 'vue'
export const propgressBaseProps = buildProps({
/**
* @description
*/
percent: {
type: Number,
default: 0,
},
/**
* @description tn开头则使用图鸟内置的颜色使
*/
activeColor: String,
/**
* @description tn开头则使用图鸟内置的颜色使
*/
inactiveColor: String,
/**
* @description
*/
showPercent: Boolean,
/**
* @description ms
*/
duration: {
type: Number,
default: 1500,
},
} as const)
export type ProgressBaseProps = ExtractPropTypes<typeof propgressBaseProps>
@@ -0,0 +1,28 @@
import { buildProps } from '../../../../utils'
import { useFormSizeProps } from '../../composables/use-component-common-props'
export const radioBaseProps = buildProps({
/**
* @description radio单选框尺寸
*/
size: useFormSizeProps,
/**
* @description radio单选框是否禁用
*/
disabled: Boolean,
/**
* @description radio禁止点击标签进行选择
*/
labelDisabled: Boolean,
/**
* @description
*/
border: Boolean,
/**
* @description radio激活时的颜色tn开头则使用图鸟内置的颜色只支持普通颜色
*/
activeColor: {
type: String,
default: '',
},
} as const)
@@ -0,0 +1,16 @@
import { buildProps } from '../../../../utils'
export const stepsBaseProps = buildProps({
/**
* @description tn开头使用图鸟内置颜色
*/
color: String,
/**
* @description tn开头使用图鸟内置颜色
*/
activeColor: String,
/**
* @description
*/
disabled: Boolean,
} as const)
@@ -0,0 +1,16 @@
import { buildProps } from '../../../../utils'
export const subsectionBaseProps = buildProps({
/**
* @description tn开头使用图鸟内置的颜色
*/
color: String,
/**
* @description tn开头使用图鸟内置的颜色
*/
activeColor: String,
/**
* @description
*/
disabled: Boolean,
} as const)
@@ -0,0 +1,11 @@
import { buildProps } from '../../../../utils'
export const swipeActionBaseProps = buildProps({
/**
* @description
*/
autoClose: {
type: Boolean,
default: true,
},
} as const)
@@ -0,0 +1,20 @@
import { buildProps } from '../../../../utils'
export const tabbarBaseProps = buildProps({
/**
* @description
*/
inactiveColor: String,
/**
* @description
*/
activeColor: String,
/**
* @description
*/
iconSize: String,
/**
* @description
*/
fontSize: String,
} as const)
@@ -0,0 +1,20 @@
import { buildProps } from '../../../../utils'
export const tabsBaseProps = buildProps({
/**
* @description tn开头时使用图鸟内置的颜色
*/
color: String,
/**
* @description tn开头时使用图鸟内置的颜色
*/
activeColor: String,
/**
* @description
*/
fontSize: String,
/**
* @description
*/
activeFontSize: String,
} as const)
@@ -0,0 +1,54 @@
import { buildProp, definePropType, generateId } from '../../../../utils'
import { componentSizes, formComponentSizes } from '../../../../constants'
export type ComponentIndex = string | number
/**
* @description Boolean类型定义
*/
export const useComponentBoolean = buildProp({
type: [Boolean, undefined],
default: undefined,
})
/**
* @description
*/
export const useComponentSizeProp = buildProp({
type: String,
values: componentSizes,
required: false,
} as const)
/**
* @description
*/
export const useFormSizeProps = buildProp({
type: String,
values: formComponentSizes,
required: false,
} as const)
/**
* @description
*/
export const useComponentCustomStyleProp = buildProp({
type: Object,
default: () => ({}),
})
/**
* @description index
*/
export const useComponentIndexProp = buildProp({
type: definePropType<ComponentIndex>([String, Number]),
default: () => generateId(),
})
/**
* @description
*/
export const useComponentSafeAreaInsetBottomProp = buildProp({
type: Boolean,
default: true,
})
@@ -0,0 +1,48 @@
import { ref, toRef, watch } from 'vue'
import { useComponentColor } from '../../../../hooks'
import type { ProgressBaseProps } from '../../common-props/progress'
interface ProgressPropsType extends ProgressBaseProps {
[key: string]: any
}
export const useProgressProps = (props: ProgressPropsType) => {
const [activeColorClass, activeColorStyle] = useComponentColor(
toRef(props, 'activeColor'),
'bg'
)
const [inactiveColorClass, inactiveColorStyle] = useComponentColor(
toRef(props, 'inactiveColor'),
'bg'
)
// 当前的进度百分比
const percent = ref(0)
let firstInitPercent = true
// 为了有动画效果,需要在下一次渲染时再设置进度条的长度
watch(
() => props.percent,
(val) => {
if (!firstInitPercent) {
percent.value = val
} else {
setTimeout(() => {
percent.value = val
firstInitPercent = false
}, 50)
}
},
{
immediate: true,
}
)
return {
percent,
activeColorClass,
activeColorStyle,
inactiveColorClass,
inactiveColorStyle,
}
}
@@ -0,0 +1,4 @@
export const checkboxCheckedShapes = ['square', 'circle'] as const
export type CheckboxCheckedShape = (typeof checkboxCheckedShapes)[number]
export type CheckboxValueType = string | number | boolean
+8
View File
@@ -0,0 +1,8 @@
import { withNoopInstall } from '../../utils'
import BubbleBox from './src/bubble-box.vue'
export const TnBubbleBox = withNoopInstall(BubbleBox)
export default TnBubbleBox
export * from './src/bubble-box'
export type { TnBubbleBoxInstance } from './src/instance'
@@ -0,0 +1,102 @@
import { ZIndex } from '../../../constants'
import { buildProps, definePropType, isNumber } from '../../../utils'
import type { ExtractPropTypes } from 'vue'
export interface BubbleBoxOptionItem {
/**
* @description
*/
text: string
/**
* @description
*/
icon?: string
/**
* @description tn开头使用图鸟内置的颜色
*/
textColor?: string
/**
* @description
*/
disabled?: boolean
}
export type BubbleBoxOption = BubbleBoxOptionItem[]
export const bubbleBoxPosition = ['top', 'bottom', 'left', 'right'] as const
export const bubbleBoxProps = buildProps({
/**
* @description
*/
options: {
type: definePropType<BubbleBoxOption>(Array),
default: () => [],
},
/**
* @description
*/
position: {
type: String,
values: bubbleBoxPosition,
default: 'top',
},
/**
* @description rpx
*/
width: String,
/**
* @description rpx
*/
height: String,
/**
* @description tn开头使用图鸟内置的颜色
*/
bgColor: String,
/**
* @description tn开头使用图鸟内置的颜色
*/
textColor: String,
/**
* @description
*/
optionItemPadding: String,
/**
* @description
*/
disabled: Boolean,
/**
* @description
*/
autoClose: {
type: Boolean,
default: true,
},
/**
* @description ZIndex
*/
zIndex: {
type: Number,
default: ZIndex.bubble,
},
})
export const bubbleBoxEmits = {
/**
* @description
*/
open: () => true,
/**
* @description
*/
close: () => true,
/**
* @description
*/
click: (index: number) => isNumber(index),
}
export type BubbleBoxProps = ExtractPropTypes<typeof bubbleBoxProps>
export type BubbleBoxEmits = typeof bubbleBoxEmits
export type BubbleBoxPosition = (typeof bubbleBoxPosition)[number]
@@ -0,0 +1,75 @@
<script lang="ts" setup>
import TnOverlay from '../../overlay/src/overlay.vue'
import TnIcon from '../../icon/src/icon.vue'
import { bubbleBoxEmits, bubbleBoxProps } from './bubble-box'
import {
useBubbleBox,
useBubbleBoxCustomStyle,
useBubbleOptions,
} from './composables'
const props = defineProps(bubbleBoxProps)
const emits = defineEmits(bubbleBoxEmits)
const {
showBubble,
openBubbleOptions,
closeBubbleOptions,
bubbleOptionClickEvent,
} = useBubbleBox(props, emits)
const {
ns,
optionsClass,
optionsStyle,
optionsAuxiliaryElementClass,
optionsAuxiliaryElementStyle,
optionItemClass,
optionItemStyle,
} = useBubbleBoxCustomStyle(props, showBubble)
const { bubbleOptions } = useBubbleOptions(props)
</script>
<template>
<view :class="[ns.b()]">
<!-- 遮罩 -->
<TnOverlay
:show="showBubble"
:opacity="0"
:z-index="zIndex - 1"
@click="closeBubbleOptions"
/>
<!-- 内容 -->
<view :class="[ns.e('content')]" @tap.stop="openBubbleOptions">
<slot />
<!-- 气泡弹框选项 -->
<view :class="[optionsClass]" :style="optionsStyle">
<!-- 小三角 -->
<view
:class="[optionsAuxiliaryElementClass]"
:style="optionsAuxiliaryElementStyle"
/>
<!-- 选项内容 -->
<scroll-view class="scroll-view" scroll-y>
<view class="options-content">
<view
v-for="(item, index) in bubbleOptions"
:key="index"
:class="[optionItemClass(item)]"
:style="optionItemStyle(item)"
@tap.stop="bubbleOptionClickEvent(item, index)"
>
<view v-if="item.icon" class="icon">
<TnIcon :name="item.icon" />
</view>
<view class="text">{{ item.text }}</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<style lang="scss" scoped>
@import '../../../theme-chalk/src/bubble-box.scss';
</style>
@@ -0,0 +1,131 @@
import { computed, toRef } from 'vue'
import { useComponentColor, useNamespace } from '../../../../hooks'
import { formatDomSizeValue } from '../../../../utils'
import type { CSSProperties, Ref } from 'vue'
import type { BubbleBoxProps } from '../bubble-box'
import type { BubbleBoxOptionItemData } from '../types'
type OptionsClassType = (item: BubbleBoxOptionItemData) => string
type OptionsStyleType = (item: BubbleBoxOptionItemData) => CSSProperties
export const useBubbleBoxCustomStyle = (
props: BubbleBoxProps,
showBubble: Ref<boolean>
) => {
const ns = useNamespace('bubble-box')
// 解析颜色
const [bgColorClass, bgColorStyle] = useComponentColor(
toRef(props, 'bgColor'),
'bg'
)
const [borderColorClass, borderColorStyle] = useComponentColor(
toRef(props, 'bgColor'),
'border'
)
// 选项的类
const optionsClass = computed<string>(() => {
const cls: string[] = [
ns.e('options'),
ns.em('options', props.position),
ns.is('show', showBubble.value),
]
if (bgColorClass.value) cls.push(bgColorClass.value)
return cls.join(' ')
})
// 选项的样式
const optionsStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (!bgColorClass.value)
style.backgroundColor = bgColorStyle.value || 'var(--tn-color-white)'
if (props.zIndex) style.zIndex = props.zIndex
if (props.width) style.width = formatDomSizeValue(props.width)
if (props.height) style.height = formatDomSizeValue(props.height)
return style
})
// 选项辅助元素的类
const optionsAuxiliaryElementClass = computed<string>(() => {
const cls: string[] = ['auxiliary-element']
if (borderColorClass.value) cls.push(borderColorClass.value)
return cls.join(' ')
})
// 选项辅助元素的样式
const optionsAuxiliaryElementStyle = computed<CSSProperties>(() => {
const style: CSSProperties = {}
if (!borderColorClass.value)
style.borderColor = borderColorStyle.value || 'var(--tn-color-white)'
// 根据不同的位置,设置不同的边框样式
if (props.position === 'top') {
style.borderRightColor = 'transparent'
style.borderBottomColor = 'transparent'
style.borderLeftColor = 'transparent'
}
if (props.position === 'right') {
style.borderTopColor = 'transparent'
style.borderBottomColor = 'transparent'
style.borderLeftColor = 'transparent'
}
if (props.position === 'bottom') {
style.borderTopColor = 'transparent'
style.borderRightColor = 'transparent'
style.borderLeftColor = 'transparent'
}
if (props.position === 'left') {
style.borderTopColor = 'transparent'
style.borderRightColor = 'transparent'
style.borderBottomColor = 'transparent'
}
return style
})
// 选项item的类
const optionItemClass = computed<OptionsClassType>(() => {
return (item: BubbleBoxOptionItemData) => {
const cls: string[] = [
ns.e('option-item'),
ns.is('disabled', item.disabled),
]
if (item.color.class) cls.push(item.color.class)
return cls.join(' ')
}
})
// 选项item的样式
const optionItemStyle = computed<OptionsStyleType>(() => {
return (item: BubbleBoxOptionItemData) => {
const style: CSSProperties = {}
if (!item.color.class)
style.color = item.color.style || 'var(--tn-text-color-primary)'
if (props.optionItemPadding) style.padding = props.optionItemPadding
return style
}
})
return {
ns,
optionsClass,
optionsStyle,
optionsAuxiliaryElementClass,
optionsAuxiliaryElementStyle,
optionItemClass,
optionItemStyle,
}
}
@@ -0,0 +1,3 @@
export * from './bubble-box-custom'
export * from './use-bubble-options'
export * from './use-bubble-box'

Some files were not shown because too many files have changed in this diff Show More