From d09a79cc6bcc69079799bea709529e26e0423d4b Mon Sep 17 00:00:00 2001 From: 07akioni <07akioni2@gmail.com> Date: Fri, 9 Apr 2021 00:43:33 +0800 Subject: [PATCH] feat(skeleton): new component --- CHANGELOG.en-US.md | 6 + CHANGELOG.zh-CN.md | 6 + demo/routes/routes.js | 8 ++ demo/store/menu-options.js | 6 + src/_utils/composable/index.ts | 1 + src/_utils/composable/use-houdini.ts | 25 ++++ src/components.ts | 1 + src/config-provider/src/internal-interface.ts | 2 + src/gradient-text/src/GradientText.tsx | 26 +--- src/skeleton/demos/enUS/basic.demo.md | 7 ++ src/skeleton/demos/enUS/box.demo.md | 12 ++ src/skeleton/demos/enUS/index.demo-entry.md | 25 ++++ src/skeleton/demos/enUS/size.demo.md | 29 +++++ src/skeleton/demos/zhCN/basic.demo.md | 7 ++ src/skeleton/demos/zhCN/box.demo.md | 12 ++ src/skeleton/demos/zhCN/index.demo-entry.md | 25 ++++ src/skeleton/demos/zhCN/size.demo.md | 29 +++++ src/skeleton/index.ts | 1 + src/skeleton/src/Skeleton.tsx | 117 ++++++++++++++++++ src/skeleton/src/styles/index.cssr.ts | 33 +++++ src/skeleton/styles/dark.ts | 18 +++ src/skeleton/styles/index.ts | 3 + src/skeleton/styles/light.ts | 25 ++++ src/themes/dark.ts | 4 +- src/themes/light.ts | 4 +- 25 files changed, 407 insertions(+), 25 deletions(-) create mode 100644 src/_utils/composable/use-houdini.ts create mode 100644 src/skeleton/demos/enUS/basic.demo.md create mode 100644 src/skeleton/demos/enUS/box.demo.md create mode 100644 src/skeleton/demos/enUS/index.demo-entry.md create mode 100644 src/skeleton/demos/enUS/size.demo.md create mode 100644 src/skeleton/demos/zhCN/basic.demo.md create mode 100644 src/skeleton/demos/zhCN/box.demo.md create mode 100644 src/skeleton/demos/zhCN/index.demo-entry.md create mode 100644 src/skeleton/demos/zhCN/size.demo.md create mode 100644 src/skeleton/index.ts create mode 100644 src/skeleton/src/Skeleton.tsx create mode 100644 src/skeleton/src/styles/index.cssr.ts create mode 100644 src/skeleton/styles/dark.ts create mode 100644 src/skeleton/styles/index.ts create mode 100644 src/skeleton/styles/light.ts diff --git a/CHANGELOG.en-US.md b/CHANGELOG.en-US.md index 688f3419c..d2fe5c8bd 100644 --- a/CHANGELOG.en-US.md +++ b/CHANGELOG.en-US.md @@ -1,5 +1,11 @@ # CHANGELOG +## Pending + +### Feats + +- Add `n-skeleton` component. + ## 2.4.2 ### Feats diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md index ceb2e0d5c..e1d8757e9 100644 --- a/CHANGELOG.zh-CN.md +++ b/CHANGELOG.zh-CN.md @@ -1,5 +1,11 @@ # CHANGELOG +## Pending + +### Feats + +- 添加 `n-skeleton` 组件 + ## 2.4.2 ### Feats diff --git a/demo/routes/routes.js b/demo/routes/routes.js index c0142ba19..5407eff96 100644 --- a/demo/routes/routes.js +++ b/demo/routes/routes.js @@ -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', diff --git a/demo/store/menu-options.js b/demo/store/menu-options.js index 5bcf9076e..3b77a0908 100644 --- a/demo/store/menu-options.js +++ b/demo/store/menu-options.js @@ -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: '加载', diff --git a/src/_utils/composable/index.ts b/src/_utils/composable/index.ts index 7a6ee5f0e..e488ee6b0 100644 --- a/src/_utils/composable/index.ts +++ b/src/_utils/composable/index.ts @@ -138,3 +138,4 @@ export function useDeferredTrue ( } export { useAdjustedTo } from './use-adjusted-to' +export { useHoudini } from './use-houdini' diff --git a/src/_utils/composable/use-houdini.ts b/src/_utils/composable/use-houdini.ts new file mode 100644 index 000000000..a4d1efc44 --- /dev/null +++ b/src/_utils/composable/use-houdini.ts @@ -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: '', + inherits: false, + initialValue: 'transparent' + }) + ;(CSS as any).registerProperty({ + name: '--color-end', + syntax: '', + inherits: false, + initialValue: 'transparent' + }) + } + } + }) +} diff --git a/src/components.ts b/src/components.ts index 0dbc7c34a..404840073 100644 --- a/src/components.ts +++ b/src/components.ts @@ -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' diff --git a/src/config-provider/src/internal-interface.ts b/src/config-provider/src/internal-interface.ts index 46083fa16..f27e61097 100644 --- a/src/config-provider/src/internal-interface.ts +++ b/src/config-provider/src/internal-interface.ts @@ -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 diff --git a/src/gradient-text/src/GradientText.tsx b/src/gradient-text/src/GradientText.tsx index 6f1add02c..0b1046a5f 100644 --- a/src/gradient-text/src/GradientText.tsx +++ b/src/gradient-text/src/GradientText.tsx @@ -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 }, setup (props) { - onBeforeMount(() => { - if (!houdiniRegistered) { - houdiniRegistered = true - if ((window?.CSS as any)?.registerProperty) { - ;(CSS as any).registerProperty({ - name: '--color-start', - syntax: '', - inherits: false, - initialValue: 'transparent' - }) - ;(CSS as any).registerProperty({ - name: '--color-end', - syntax: '', - inherits: false, - initialValue: 'transparent' - }) - } - } - }) + useHoudini() const compatibleTypeRef = computed< 'info' | 'success' | 'warning' | 'error' | 'primary' >(() => { diff --git a/src/skeleton/demos/enUS/basic.demo.md b/src/skeleton/demos/enUS/basic.demo.md new file mode 100644 index 000000000..33a6b2f03 --- /dev/null +++ b/src/skeleton/demos/enUS/basic.demo.md @@ -0,0 +1,7 @@ +# Basic Usage + +Use `text` to create text skeleton. + +```html + +``` diff --git a/src/skeleton/demos/enUS/box.demo.md b/src/skeleton/demos/enUS/box.demo.md new file mode 100644 index 000000000..a528506ec --- /dev/null +++ b/src/skeleton/demos/enUS/box.demo.md @@ -0,0 +1,12 @@ +# Box + +Use it as a box. + +```html + + + + + + +``` diff --git a/src/skeleton/demos/enUS/index.demo-entry.md b/src/skeleton/demos/enUS/index.demo-entry.md new file mode 100644 index 000000000..7adcf949d --- /dev/null +++ b/src/skeleton/demos/enUS/index.demo-entry.md @@ -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` | | diff --git a/src/skeleton/demos/enUS/size.demo.md b/src/skeleton/demos/enUS/size.demo.md new file mode 100644 index 000000000..9bcbc40fb --- /dev/null +++ b/src/skeleton/demos/enUS/size.demo.md @@ -0,0 +1,29 @@ +# Size + +Use `size` to make its height the same as other components. + +```html + + Loading + + + Won't you fly high + + free bird, yeah + + ? + + +``` + +```js +import { defineComponent, ref } from 'vue' + +export default defineComponent({ + setup () { + return { + loading: ref(true) + } + } +}) +``` diff --git a/src/skeleton/demos/zhCN/basic.demo.md b/src/skeleton/demos/zhCN/basic.demo.md new file mode 100644 index 000000000..212a3258d --- /dev/null +++ b/src/skeleton/demos/zhCN/basic.demo.md @@ -0,0 +1,7 @@ +# 基础用法 + +使用 `text` 设定文本骨架。 + +```html + +``` diff --git a/src/skeleton/demos/zhCN/box.demo.md b/src/skeleton/demos/zhCN/box.demo.md new file mode 100644 index 000000000..a5e7da6cb --- /dev/null +++ b/src/skeleton/demos/zhCN/box.demo.md @@ -0,0 +1,12 @@ +# 盒子 + +把它当成个块用。 + +```html + + + + + + +``` diff --git a/src/skeleton/demos/zhCN/index.demo-entry.md b/src/skeleton/demos/zhCN/index.demo-entry.md new file mode 100644 index 000000000..b27fca338 --- /dev/null +++ b/src/skeleton/demos/zhCN/index.demo-entry.md @@ -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` | | diff --git a/src/skeleton/demos/zhCN/size.demo.md b/src/skeleton/demos/zhCN/size.demo.md new file mode 100644 index 000000000..336685951 --- /dev/null +++ b/src/skeleton/demos/zhCN/size.demo.md @@ -0,0 +1,29 @@ +# 尺寸 + +使用 `size` 装成其他组件。 + +```html + + 加载 + + + Won't you fly high + + free bird, yeah + + ? + + +``` + +```js +import { defineComponent, ref } from 'vue' + +export default defineComponent({ + setup () { + return { + loading: ref(true) + } + } +}) +``` diff --git a/src/skeleton/index.ts b/src/skeleton/index.ts new file mode 100644 index 000000000..3e50628b8 --- /dev/null +++ b/src/skeleton/index.ts @@ -0,0 +1 @@ +export { default as NSkeleton } from './src/Skeleton' diff --git a/src/skeleton/src/Skeleton.tsx b/src/skeleton/src/Skeleton.tsx new file mode 100644 index 000000000..6bb04f9ad --- /dev/null +++ b/src/skeleton/src/Skeleton.tsx @@ -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), + 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 + } +}) diff --git a/src/skeleton/src/styles/index.cssr.ts b/src/skeleton/src/styles/index.cssr.ts new file mode 100644 index 000000000..a16a5ccac --- /dev/null +++ b/src/skeleton/src/styles/index.cssr.ts @@ -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); + } + `) +]) diff --git a/src/skeleton/styles/dark.ts b/src/skeleton/styles/dark.ts new file mode 100644 index 000000000..dae2386d3 --- /dev/null +++ b/src/skeleton/styles/dark.ts @@ -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 + } + } +} diff --git a/src/skeleton/styles/index.ts b/src/skeleton/styles/index.ts new file mode 100644 index 000000000..9997d4eed --- /dev/null +++ b/src/skeleton/styles/index.ts @@ -0,0 +1,3 @@ +export { skeletonDark } from './dark' +export { skeletonLight } from './light' +export type { SkeletonTheme, SkeletonThemeVars } from './light' diff --git a/src/skeleton/styles/light.ts b/src/skeleton/styles/light.ts new file mode 100644 index 000000000..77dfcfc22 --- /dev/null +++ b/src/skeleton/styles/light.ts @@ -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 + +export const skeletonLight: Theme<'Skeleton', SkeletonThemeVars> = { + name: 'Skeleton', + common: commonLight, + self +} + +export type SkeletonTheme = typeof skeletonLight diff --git a/src/themes/dark.ts b/src/themes/dark.ts index 129f9a1f2..fc3d0a5b9 100644 --- a/src/themes/dark.ts +++ b/src/themes/dark.ts @@ -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, diff --git a/src/themes/light.ts b/src/themes/light.ts index e2e6d78d8..9aff8b9ae 100644 --- a/src/themes/light.ts +++ b/src/themes/light.ts @@ -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,