mirror of
https://github.com/tusen-ai/naive-ui.git
synced 2025-04-12 14:40:47 +08:00
feat(skeleton): new component
This commit is contained in:
parent
a156a9aa79
commit
d09a79cc6b
@ -1,5 +1,11 @@
|
||||
# CHANGELOG
|
||||
|
||||
## Pending
|
||||
|
||||
### Feats
|
||||
|
||||
- Add `n-skeleton` component.
|
||||
|
||||
## 2.4.2
|
||||
|
||||
### Feats
|
||||
|
@ -1,5 +1,11 @@
|
||||
# CHANGELOG
|
||||
|
||||
## Pending
|
||||
|
||||
### Feats
|
||||
|
||||
- 添加 `n-skeleton` 组件
|
||||
|
||||
## 2.4.2
|
||||
|
||||
### Feats
|
||||
|
@ -393,6 +393,10 @@ export const enComponentRoutes = [
|
||||
path: 'n-image',
|
||||
component: () => import('../../src/image/demos/enUS/index.demo-entry.md')
|
||||
},
|
||||
{
|
||||
path: 'n-skeleton',
|
||||
component: () => import('../../src/skeleton/demos/enUS/index.demo-entry.md')
|
||||
},
|
||||
// deprecated
|
||||
{
|
||||
path: 'n-nimbus-service-layout',
|
||||
@ -718,6 +722,10 @@ export const zhComponentRoutes = [
|
||||
path: 'n-image',
|
||||
component: () => import('../../src/image/demos/zhCN/index.demo-entry.md')
|
||||
},
|
||||
{
|
||||
path: 'n-skeleton',
|
||||
component: () => import('../../src/skeleton/demos/zhCN/index.demo-entry.md')
|
||||
},
|
||||
// deprecated
|
||||
{
|
||||
path: 'n-nimbus-service-layout',
|
||||
|
@ -538,6 +538,12 @@ export function createComponentMenuOptions ({ lang, theme, mode }) {
|
||||
enSuffix: true,
|
||||
path: '/n-result'
|
||||
},
|
||||
{
|
||||
en: 'Skeleton',
|
||||
zh: '骨架屏',
|
||||
enSuffix: true,
|
||||
path: '/n-skeleton'
|
||||
},
|
||||
{
|
||||
en: 'Spin',
|
||||
zh: '加载',
|
||||
|
@ -138,3 +138,4 @@ export function useDeferredTrue (
|
||||
}
|
||||
|
||||
export { useAdjustedTo } from './use-adjusted-to'
|
||||
export { useHoudini } from './use-houdini'
|
||||
|
25
src/_utils/composable/use-houdini.ts
Normal file
25
src/_utils/composable/use-houdini.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { onBeforeMount } from 'vue'
|
||||
|
||||
let houdiniRegistered = false
|
||||
|
||||
export function useHoudini (): void {
|
||||
onBeforeMount(() => {
|
||||
if (!houdiniRegistered) {
|
||||
houdiniRegistered = true
|
||||
if ((window?.CSS as any)?.registerProperty) {
|
||||
;(CSS as any).registerProperty({
|
||||
name: '--color-start',
|
||||
syntax: '<color>',
|
||||
inherits: false,
|
||||
initialValue: 'transparent'
|
||||
})
|
||||
;(CSS as any).registerProperty({
|
||||
name: '--color-end',
|
||||
syntax: '<color>',
|
||||
inherits: false,
|
||||
initialValue: 'transparent'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -54,6 +54,7 @@ export * from './rate'
|
||||
export * from './result'
|
||||
export * from './scrollbar'
|
||||
export * from './select'
|
||||
export * from './skeleton'
|
||||
export * from './slider'
|
||||
export * from './space'
|
||||
export * from './spin'
|
||||
|
@ -50,6 +50,7 @@ import type { RateTheme } from '../../rate/styles'
|
||||
import type { ResultTheme } from '../../result/styles'
|
||||
import type { ScrollbarTheme } from '../../scrollbar/styles'
|
||||
import type { SelectTheme } from '../../select/styles'
|
||||
import type { SkeletonTheme } from '../../skeleton/styles'
|
||||
import type { SliderTheme } from '../../slider/styles'
|
||||
import type { SpaceTheme } from '../../space/styles'
|
||||
import type { SpinTheme } from '../../spin/styles'
|
||||
@ -131,6 +132,7 @@ export interface GlobalThemeWithoutCommon {
|
||||
Result?: ResultTheme
|
||||
Scrollbar?: ScrollbarTheme
|
||||
Select?: SelectTheme
|
||||
Skeleton?: SkeletonTheme
|
||||
Slider?: SliderTheme
|
||||
Space?: SpaceTheme
|
||||
Spin?: SpinTheme
|
||||
|
@ -1,13 +1,11 @@
|
||||
import { defineComponent, computed, onBeforeMount, h, PropType } from 'vue'
|
||||
import { defineComponent, computed, h, PropType } from 'vue'
|
||||
import { useTheme } from '../../_mixins'
|
||||
import type { ThemeProps } from '../../_mixins'
|
||||
import { createKey, formatLength } from '../../_utils'
|
||||
import { createKey, formatLength, useHoudini } from '../../_utils'
|
||||
import { gradientTextLight } from '../styles'
|
||||
import type { GradientTextTheme } from '../styles'
|
||||
import style from './styles/index.cssr'
|
||||
|
||||
let houdiniRegistered = false
|
||||
|
||||
type Gradient =
|
||||
| string
|
||||
| {
|
||||
@ -32,25 +30,7 @@ export default defineComponent({
|
||||
gradient: [Object, String] as PropType<Gradient>
|
||||
},
|
||||
setup (props) {
|
||||
onBeforeMount(() => {
|
||||
if (!houdiniRegistered) {
|
||||
houdiniRegistered = true
|
||||
if ((window?.CSS as any)?.registerProperty) {
|
||||
;(CSS as any).registerProperty({
|
||||
name: '--color-start',
|
||||
syntax: '<color>',
|
||||
inherits: false,
|
||||
initialValue: 'transparent'
|
||||
})
|
||||
;(CSS as any).registerProperty({
|
||||
name: '--color-end',
|
||||
syntax: '<color>',
|
||||
inherits: false,
|
||||
initialValue: 'transparent'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
useHoudini()
|
||||
const compatibleTypeRef = computed<
|
||||
'info' | 'success' | 'warning' | 'error' | 'primary'
|
||||
>(() => {
|
||||
|
7
src/skeleton/demos/enUS/basic.demo.md
Normal file
7
src/skeleton/demos/enUS/basic.demo.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Basic Usage
|
||||
|
||||
Use `text` to create text skeleton.
|
||||
|
||||
```html
|
||||
<n-skeleton text :repeat="2" /> <n-skeleton text style="width: 60%;" />
|
||||
```
|
12
src/skeleton/demos/enUS/box.demo.md
Normal file
12
src/skeleton/demos/enUS/box.demo.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Box
|
||||
|
||||
Use it as a box.
|
||||
|
||||
```html
|
||||
<n-space vertical>
|
||||
<n-skeleton height="40px" width="33%" />
|
||||
<n-skeleton height="40px" width="66%" :sharp="false" />
|
||||
<n-skeleton height="40px" round />
|
||||
<n-skeleton height="40px" circle />
|
||||
</n-space>
|
||||
```
|
25
src/skeleton/demos/enUS/index.demo-entry.md
Normal file
25
src/skeleton/demos/enUS/index.demo-entry.md
Normal file
@ -0,0 +1,25 @@
|
||||
# Skeleton
|
||||
|
||||
A twinkle placeholder.
|
||||
|
||||
## Demos
|
||||
|
||||
```demo
|
||||
basic
|
||||
box
|
||||
size
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
| -------- | -------------------------------- | ----------- | ----------- |
|
||||
| text | `boolean` | `false` | |
|
||||
| round | `boolean` | `false` | |
|
||||
| circle | `boolean` | `false` | |
|
||||
| height | `string \| number` | `undefined` | |
|
||||
| width | `string \| number` | `undefined` | |
|
||||
| size | `'small' \| 'medium' \| 'large'` | `undefined` | |
|
||||
| repeat | `string \| number` | `1` | |
|
||||
| animated | `boolean` | `true` | |
|
||||
| sharp | `boolean` | `true` | |
|
29
src/skeleton/demos/enUS/size.demo.md
Normal file
29
src/skeleton/demos/enUS/size.demo.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Size
|
||||
|
||||
Use `size` to make its height the same as other components.
|
||||
|
||||
```html
|
||||
<n-space vertical>
|
||||
<n-space><n-switch v-model:value="loading" />Loading</n-space>
|
||||
<n-space>
|
||||
<n-skeleton v-if="loading" :width="146" :sharp="false" size="medium" />
|
||||
<n-button v-else>Won't you fly high</n-button>
|
||||
<n-skeleton v-if="loading" :width="132" round size="medium" />
|
||||
<n-button v-else round>free bird, yeah</n-button>
|
||||
<n-skeleton v-if="loading" circle size="medium" />
|
||||
<n-button v-else circle>?</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
```
|
||||
|
||||
```js
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
loading: ref(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
7
src/skeleton/demos/zhCN/basic.demo.md
Normal file
7
src/skeleton/demos/zhCN/basic.demo.md
Normal file
@ -0,0 +1,7 @@
|
||||
# 基础用法
|
||||
|
||||
使用 `text` 设定文本骨架。
|
||||
|
||||
```html
|
||||
<n-skeleton text :repeat="2" /> <n-skeleton text style="width: 60%;" />
|
||||
```
|
12
src/skeleton/demos/zhCN/box.demo.md
Normal file
12
src/skeleton/demos/zhCN/box.demo.md
Normal file
@ -0,0 +1,12 @@
|
||||
# 盒子
|
||||
|
||||
把它当成个块用。
|
||||
|
||||
```html
|
||||
<n-space vertical>
|
||||
<n-skeleton height="40px" width="33%" />
|
||||
<n-skeleton height="40px" width="66%" :sharp="false" />
|
||||
<n-skeleton height="40px" round />
|
||||
<n-skeleton height="40px" circle />
|
||||
</n-space>
|
||||
```
|
25
src/skeleton/demos/zhCN/index.demo-entry.md
Normal file
25
src/skeleton/demos/zhCN/index.demo-entry.md
Normal file
@ -0,0 +1,25 @@
|
||||
# 骨架屏 Skeleton
|
||||
|
||||
可以闪的占位符。
|
||||
|
||||
## 演示
|
||||
|
||||
```demo
|
||||
basic
|
||||
box
|
||||
size
|
||||
```
|
||||
|
||||
## Props
|
||||
|
||||
| 名称 | 类型 | 默认值 | 说明 |
|
||||
| -------- | -------------------------------- | ----------- | ---- |
|
||||
| text | `boolean` | `false` | |
|
||||
| round | `boolean` | `false` | |
|
||||
| circle | `boolean` | `false` | |
|
||||
| height | `string \| number` | `undefined` | |
|
||||
| width | `string \| number` | `undefined` | |
|
||||
| size | `'small' \| 'medium' \| 'large'` | `undefined` | |
|
||||
| repeat | `string \| number` | `1` | |
|
||||
| animated | `boolean` | `true` | |
|
||||
| sharp | `boolean` | `true` | |
|
29
src/skeleton/demos/zhCN/size.demo.md
Normal file
29
src/skeleton/demos/zhCN/size.demo.md
Normal file
@ -0,0 +1,29 @@
|
||||
# 尺寸
|
||||
|
||||
使用 `size` 装成其他组件。
|
||||
|
||||
```html
|
||||
<n-space vertical>
|
||||
<n-space><n-switch v-model:value="loading" />加载</n-space>
|
||||
<n-space>
|
||||
<n-skeleton v-if="loading" :width="146" :sharp="false" size="medium" />
|
||||
<n-button v-else>Won't you fly high</n-button>
|
||||
<n-skeleton v-if="loading" :width="132" round size="medium" />
|
||||
<n-button v-else round>free bird, yeah</n-button>
|
||||
<n-skeleton v-if="loading" circle size="medium" />
|
||||
<n-button v-else circle>?</n-button>
|
||||
</n-space>
|
||||
</n-space>
|
||||
```
|
||||
|
||||
```js
|
||||
import { defineComponent, ref } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
loading: ref(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
1
src/skeleton/index.ts
Normal file
1
src/skeleton/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as NSkeleton } from './src/Skeleton'
|
117
src/skeleton/src/Skeleton.tsx
Normal file
117
src/skeleton/src/Skeleton.tsx
Normal file
@ -0,0 +1,117 @@
|
||||
import { pxfy } from 'seemly'
|
||||
import {
|
||||
computed,
|
||||
defineComponent,
|
||||
h,
|
||||
PropType,
|
||||
Fragment,
|
||||
mergeProps
|
||||
} from 'vue'
|
||||
import type { ThemeProps } from '../../_mixins'
|
||||
import { useTheme } from '../../_mixins'
|
||||
import { createKey, useHoudini } from '../../_utils'
|
||||
import type { SkeletonTheme } from '../styles'
|
||||
import { skeletonLight } from '../styles'
|
||||
import style from './styles/index.cssr'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Skeleton',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
...(useTheme.props as ThemeProps<SkeletonTheme>),
|
||||
text: Boolean,
|
||||
round: Boolean,
|
||||
circle: Boolean,
|
||||
height: [String, Number],
|
||||
width: [String, Number],
|
||||
size: String as PropType<'small' | 'medium' | 'large'>,
|
||||
repeat: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
animated: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
sharp: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
setup (props) {
|
||||
useHoudini()
|
||||
const themeRef = useTheme(
|
||||
'Skeleton',
|
||||
'Skeleton',
|
||||
style,
|
||||
skeletonLight,
|
||||
props
|
||||
)
|
||||
return {
|
||||
style: computed(() => {
|
||||
const theme = themeRef.value
|
||||
const {
|
||||
common: { cubicBezierEaseInOut }
|
||||
} = theme
|
||||
const selfThemeVars = theme.self
|
||||
const { color, colorEnd, borderRadius } = selfThemeVars
|
||||
let sizeHeight: string | undefined
|
||||
const {
|
||||
circle,
|
||||
sharp,
|
||||
round,
|
||||
width,
|
||||
height,
|
||||
size,
|
||||
text,
|
||||
animated
|
||||
} = props
|
||||
if (size !== undefined) {
|
||||
sizeHeight = selfThemeVars[createKey('height', size)]
|
||||
}
|
||||
const mergedWidth = circle ? width ?? height ?? sizeHeight : width
|
||||
const mergedHeight = (circle ? width ?? height : height) ?? sizeHeight
|
||||
return {
|
||||
display: text ? 'inline-block' : '',
|
||||
verticalAlign: text ? '-0.125em' : '',
|
||||
borderRadius: circle
|
||||
? '50%'
|
||||
: round
|
||||
? '4096px'
|
||||
: sharp
|
||||
? ''
|
||||
: borderRadius,
|
||||
width:
|
||||
typeof mergedWidth === 'number' ? pxfy(mergedWidth) : mergedWidth,
|
||||
height:
|
||||
typeof mergedHeight === 'number'
|
||||
? pxfy(mergedHeight)
|
||||
: mergedHeight,
|
||||
animation: !animated ? 'none' : '',
|
||||
'--bezier': cubicBezierEaseInOut,
|
||||
'--color-start': color,
|
||||
'--color-end': colorEnd
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
render () {
|
||||
const { repeat, style, $attrs } = this
|
||||
const child = h(
|
||||
'div',
|
||||
mergeProps(
|
||||
{
|
||||
class: 'n-skeleton',
|
||||
style: style
|
||||
},
|
||||
$attrs
|
||||
)
|
||||
)
|
||||
if (repeat > 1) {
|
||||
return (
|
||||
<>{Array.apply(null, { length: repeat } as any).map((_) => child)}</>
|
||||
)
|
||||
}
|
||||
return child
|
||||
}
|
||||
})
|
33
src/skeleton/src/styles/index.cssr.ts
Normal file
33
src/skeleton/src/styles/index.cssr.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { c, cB } from '../../../_utils/cssr'
|
||||
|
||||
// vars:
|
||||
// --color
|
||||
// --color-end
|
||||
// --bezier
|
||||
export default c([
|
||||
cB('skeleton', `
|
||||
height: 1em;
|
||||
width: 100%;
|
||||
transition: background-color .3s var(--bezier);
|
||||
transition:
|
||||
--color-start .3s var(--bezier),
|
||||
--color-end .3s var(--bezier),
|
||||
background-color .3s var(--bezier);
|
||||
animation: 2s skeleton-loading infinite cubic-bezier(0.36, 0, 0.64, 1);
|
||||
background-color: var(--color-start);
|
||||
`),
|
||||
c('@keyframes skeleton-loading', `
|
||||
0% {
|
||||
background: var(--color-start);
|
||||
}
|
||||
40% {
|
||||
background: var(--color-end);
|
||||
}
|
||||
80% {
|
||||
background: var(--color-start);
|
||||
}
|
||||
100% {
|
||||
background: var(--color-start);
|
||||
}
|
||||
`)
|
||||
])
|
18
src/skeleton/styles/dark.ts
Normal file
18
src/skeleton/styles/dark.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { commonDark } from '../../_styles/common'
|
||||
import { SkeletonTheme } from './light'
|
||||
|
||||
export const skeletonDark: SkeletonTheme = {
|
||||
name: 'Skeleton',
|
||||
common: commonDark,
|
||||
self (vars) {
|
||||
const { heightSmall, heightMedium, heightLarge, borderRadius } = vars
|
||||
return {
|
||||
color: 'rgba(255, 255, 255, 0.12)',
|
||||
colorEnd: 'rgba(255, 255, 255, 0.18)',
|
||||
borderRadius,
|
||||
heightSmall,
|
||||
heightMedium,
|
||||
heightLarge
|
||||
}
|
||||
}
|
||||
}
|
3
src/skeleton/styles/index.ts
Normal file
3
src/skeleton/styles/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export { skeletonDark } from './dark'
|
||||
export { skeletonLight } from './light'
|
||||
export type { SkeletonTheme, SkeletonThemeVars } from './light'
|
25
src/skeleton/styles/light.ts
Normal file
25
src/skeleton/styles/light.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { commonLight } from '../../_styles/common'
|
||||
import type { ThemeCommonVars } from '../../_styles/common'
|
||||
import { Theme } from '../../_mixins'
|
||||
|
||||
const self = (vars: ThemeCommonVars) => {
|
||||
const { heightSmall, heightMedium, heightLarge, borderRadius } = vars
|
||||
return {
|
||||
color: '#eee',
|
||||
colorEnd: '#ddd',
|
||||
borderRadius,
|
||||
heightSmall,
|
||||
heightMedium,
|
||||
heightLarge
|
||||
}
|
||||
}
|
||||
|
||||
export type SkeletonThemeVars = ReturnType<typeof self>
|
||||
|
||||
export const skeletonLight: Theme<'Skeleton', SkeletonThemeVars> = {
|
||||
name: 'Skeleton',
|
||||
common: commonLight,
|
||||
self
|
||||
}
|
||||
|
||||
export type SkeletonTheme = typeof skeletonLight
|
@ -49,6 +49,8 @@ import { radioDark } from '../radio/styles'
|
||||
import { rateDark } from '../rate/styles'
|
||||
import { resultDark } from '../result/styles'
|
||||
import { scrollbarDark } from '../scrollbar/styles'
|
||||
import { selectDark } from '../select/styles'
|
||||
import { skeletonDark } from '../skeleton/styles'
|
||||
import { sliderDark } from '../slider/styles'
|
||||
import { spaceDark } from '../space/styles'
|
||||
import { spinDark } from '../spin/styles'
|
||||
@ -66,7 +68,6 @@ import { transferDark } from '../transfer/styles'
|
||||
import { typographyDark } from '../typography/styles'
|
||||
import { treeDark } from '../tree/styles'
|
||||
import { uploadDark } from '../upload/styles'
|
||||
import { selectDark } from '../select/styles'
|
||||
import type { BuiltInGlobalTheme } from './interface'
|
||||
|
||||
export const darkTheme: BuiltInGlobalTheme = {
|
||||
@ -122,6 +123,7 @@ export const darkTheme: BuiltInGlobalTheme = {
|
||||
Result: resultDark,
|
||||
Scrollbar: scrollbarDark,
|
||||
Select: selectDark,
|
||||
Skeleton: skeletonDark,
|
||||
Slider: sliderDark,
|
||||
Space: spaceDark,
|
||||
Spin: spinDark,
|
||||
|
@ -51,6 +51,8 @@ import { radioLight } from '../radio/styles'
|
||||
import { rateLight } from '../rate/styles'
|
||||
import { resultLight } from '../result/styles'
|
||||
import { scrollbarLight } from '../scrollbar/styles'
|
||||
import { selectLight } from '../select/styles'
|
||||
import { skeletonLight } from '../skeleton/styles'
|
||||
import { sliderLight } from '../slider/styles'
|
||||
import { spaceLight } from '../space/styles'
|
||||
import { spinLight } from '../spin/styles'
|
||||
@ -68,7 +70,6 @@ import { transferLight } from '../transfer/styles'
|
||||
import { typographyLight } from '../typography/styles'
|
||||
import { treeLight } from '../tree/styles'
|
||||
import { uploadLight } from '../upload/styles'
|
||||
import { selectLight } from '../select/styles'
|
||||
import type { BuiltInGlobalTheme } from './interface'
|
||||
|
||||
export const lightTheme: BuiltInGlobalTheme = {
|
||||
@ -123,6 +124,7 @@ export const lightTheme: BuiltInGlobalTheme = {
|
||||
Rate: rateLight,
|
||||
Result: resultLight,
|
||||
Scrollbar: scrollbarLight,
|
||||
Skeleton: skeletonLight,
|
||||
Select: selectLight,
|
||||
Slider: sliderLight,
|
||||
Space: spaceLight,
|
||||
|
Loading…
x
Reference in New Issue
Block a user