feat(skeleton): new component

This commit is contained in:
07akioni 2021-04-09 00:43:33 +08:00
parent a156a9aa79
commit d09a79cc6b
25 changed files with 407 additions and 25 deletions

View File

@ -1,5 +1,11 @@
# CHANGELOG
## Pending
### Feats
- Add `n-skeleton` component.
## 2.4.2
### Feats

View File

@ -1,5 +1,11 @@
# CHANGELOG
## Pending
### Feats
- 添加 `n-skeleton` 组件
## 2.4.2
### Feats

View File

@ -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',

View File

@ -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: '加载',

View File

@ -138,3 +138,4 @@ export function useDeferredTrue (
}
export { useAdjustedTo } from './use-adjusted-to'
export { useHoudini } from './use-houdini'

View 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'
})
}
}
})
}

View File

@ -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'

View File

@ -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

View File

@ -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'
>(() => {

View 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%;" />
```

View 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>
```

View 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` | |

View 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)
}
}
})
```

View File

@ -0,0 +1,7 @@
# 基础用法
使用 `text` 设定文本骨架。
```html
<n-skeleton text :repeat="2" /> <n-skeleton text style="width: 60%;" />
```

View 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>
```

View 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` | |

View 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
View File

@ -0,0 +1 @@
export { default as NSkeleton } from './src/Skeleton'

View 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
}
})

View 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);
}
`)
])

View 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
}
}
}

View File

@ -0,0 +1,3 @@
export { skeletonDark } from './dark'
export { skeletonLight } from './light'
export type { SkeletonTheme, SkeletonThemeVars } from './light'

View 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

View File

@ -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,

View File

@ -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,