feat(ellipsis): ellipsis by lines

This commit is contained in:
07akioni 2021-03-21 01:02:13 +08:00
parent e161183de8
commit 4ce0897fdd
20 changed files with 326 additions and 75 deletions

View File

@ -310,6 +310,10 @@ export const componentChildRoutes = withPrefix('/:lang/:theme/components', [
path: '/n-dynamic-tags',
component: () => import('../../src/dynamic-tags/demos/index.entry')
},
{
path: '/n-ellipsis',
component: () => import('../../src/ellipsis/demos/index.entry')
},
// deprecated
{
path: '/n-nimbus-service-layout',

View File

@ -157,6 +157,12 @@ export function createComponentMenuOptions ({ lang, theme, mode }) {
enSuffix: true,
path: '/n-dropdown'
},
{
en: 'Ellipsis',
zh: '文本省略',
enSuffix: true,
path: '/n-ellipsis'
},
{
en: 'Gradient Text',
zh: '渐变文字',

View File

@ -24,6 +24,7 @@ export * from './dropdown'
export * from './dynamic-input'
export * from './dynamic-tags'
export * from './element'
export * from './ellipsis'
export * from './empty'
export * from './form'
export * from './gradient-text'

View File

@ -24,6 +24,7 @@ import type { DropdownTheme } from '../../dropdown/styles'
import type { DynamicInputTheme } from '../../dynamic-input/styles'
import type { DynamicTagsTheme } from '../../dynamic-tags/styles'
import type { ElementTheme } from '../../element/styles'
import type { EllipsisTheme } from '../../ellipsis/styles'
import type { EmptyTheme } from '../../empty/styles'
import type { FormTheme } from '../../form/styles'
import type { GradientTextTheme } from '../../gradient-text/styles'
@ -76,8 +77,7 @@ import { Size as ButtonSize } from '../../button/src/interface'
import { FilterRender, SorterRender } from '../../data-table/src/interface'
import { IconPlacement } from '../../dialog/src/interface'
export interface GlobalTheme {
common?: ThemeCommonVars
interface GlobalThemeWithoutCommon {
Alert?: AlertTheme
Anchor?: AnchorTheme
AutoComplete?: AutoCompleteTheme
@ -101,6 +101,7 @@ export interface GlobalTheme {
DynamicInput?: DynamicInputTheme
DynamicTags?: DynamicTagsTheme
Element?: ElementTheme
Ellipsis?: EllipsisTheme
Empty?: EmptyTheme
Form?: FormTheme
GradientText?: GradientTextTheme
@ -147,75 +148,16 @@ export interface GlobalTheme {
InternalSelection?: InternalSelectionTheme
}
export interface GlobalThemeOverrides {
export interface GlobalTheme extends GlobalThemeWithoutCommon {
common?: ThemeCommonVars
}
export type GlobalThemeOverrides = {
common?: Partial<ThemeCommonVars>
Alert?: ExtractThemeOverrides<AlertTheme>
Anchor?: ExtractThemeOverrides<AnchorTheme>
AutoComplete?: ExtractThemeOverrides<AutoCompleteTheme>
Avatar?: ExtractThemeOverrides<AvatarTheme>
BackTop?: ExtractThemeOverrides<BackTopTheme>
Badge?: ExtractThemeOverrides<BadgeTheme>
Breadcrumb?: ExtractThemeOverrides<BreadcrumbTheme>
Button?: ExtractThemeOverrides<ButtonTheme>
Card?: ExtractThemeOverrides<CardTheme>
Cascader?: ExtractThemeOverrides<CascaderTheme>
Checkbox?: ExtractThemeOverrides<CheckboxTheme>
Code?: ExtractThemeOverrides<CodeTheme>
Collapse?: ExtractThemeOverrides<CollapseTheme>
DataTable?: ExtractThemeOverrides<DataTableTheme>
DatePicker?: ExtractThemeOverrides<DatePickerTheme>
Descriptions?: ExtractThemeOverrides<DescriptionsTheme>
Dialog?: ExtractThemeOverrides<DialogTheme>
Divider?: ExtractThemeOverrides<DividerTheme>
Drawer?: ExtractThemeOverrides<DrawerTheme>
Dropdown?: ExtractThemeOverrides<DropdownTheme>
DynamicInput?: ExtractThemeOverrides<DynamicInputTheme>
DynamicTags?: ExtractThemeOverrides<DynamicTagsTheme>
Element?: ExtractThemeOverrides<ElementTheme>
Empty?: ExtractThemeOverrides<EmptyTheme>
Form?: ExtractThemeOverrides<FormTheme>
GradientText?: ExtractThemeOverrides<GradientTextTheme>
Icon?: ExtractThemeOverrides<IconTheme>
Input?: ExtractThemeOverrides<InputTheme>
InputNumber?: ExtractThemeOverrides<InputNumberTheme>
Layout?: ExtractThemeOverrides<LayoutTheme>
List?: ExtractThemeOverrides<ListTheme>
LoadingBar?: ExtractThemeOverrides<LoadingBarTheme>
Log?: ExtractThemeOverrides<LogTheme>
Menu?: ExtractThemeOverrides<MenuTheme>
Message?: ExtractThemeOverrides<MessageTheme>
Modal?: ExtractThemeOverrides<ModalTheme>
Notification?: ExtractThemeOverrides<NotificationTheme>
Pagination?: ExtractThemeOverrides<PaginationTheme>
Popconfirm?: ExtractThemeOverrides<PopconfirmTheme>
Popover?: ExtractThemeOverrides<PopoverTheme>
Popselect?: ExtractThemeOverrides<PopselectTheme>
Progress?: ExtractThemeOverrides<ProgressTheme>
Radio?: ExtractThemeOverrides<RadioTheme>
Rate?: ExtractThemeOverrides<RateTheme>
Result?: ExtractThemeOverrides<ResultTheme>
Select?: ExtractThemeOverrides<SelectTheme>
Scrollbar?: ExtractThemeOverrides<ScrollbarTheme>
Slider?: ExtractThemeOverrides<SliderTheme>
Space?: ExtractThemeOverrides<SpaceTheme>
Spin?: ExtractThemeOverrides<SpinTheme>
Statistic?: ExtractThemeOverrides<StatisticTheme>
Steps?: ExtractThemeOverrides<StepsTheme>
Switch?: ExtractThemeOverrides<SwitchTheme>
Table?: ExtractThemeOverrides<TableTheme>
Tabs?: ExtractThemeOverrides<TabsTheme>
Tag?: ExtractThemeOverrides<TagTheme>
Thing?: ExtractThemeOverrides<ThingTheme>
TimePicker?: ExtractThemeOverrides<TimePickerTheme>
Timeline?: ExtractThemeOverrides<TimelineTheme>
Tooltip?: ExtractThemeOverrides<TooltipTheme>
Transfer?: ExtractThemeOverrides<TransferTheme>
Typography?: ExtractThemeOverrides<TypographyTheme>
Tree?: ExtractThemeOverrides<TreeTheme>
Upload?: ExtractThemeOverrides<UploadTheme>
// internal
InternalSelectMenu?: ExtractThemeOverrides<InternalSelectMenuTheme>
InternalSelection?: ExtractThemeOverrides<InternalSelectionTheme>
} & {
[key in keyof GlobalThemeWithoutCommon]: ExtractThemeOverrides<
GlobalThemeWithoutCommon[key]
>
}
export interface ConfigProviderInjection {

View File

@ -0,0 +1,11 @@
# Basic
Basic single line ellipsis with tooltip.
(The following content is intended to be Chinese, since it's hard to find proper translation for the lyrics.)
```html
<n-ellipsis style="max-width: 240px;">
住在我心里孤独的 孤独的海怪 痛苦之王 开始厌倦 深海的光 停滞的海浪
</n-ellipsis>
```

View File

@ -0,0 +1,26 @@
# Ellipsis
Complexity has to live somewhere.
When you hearing somebody talks about subtle concept model, keep alert.
## Demos
```demo
basic
line-clamp
```
## Props
| Name | Type | Default | Description | Version |
| --- | --- | --- | --- | --- |
| expand-trigger | `'click'` | `undefined` | | 2.0.2 |
| line-clmap | `number \| string` | `undefined` | Max lines. | 2.0.2 |
| tooltip | `boolean \| TooltipProps` | `true` | The props of tooltip. | 2.0.2 |
## Slots
| Name | Parameters | Description |
| ------- | ---------- | ----------- |
| default | `()` | |

View File

@ -0,0 +1,10 @@
# Line Clamp
Naive-ui provide multiple line ellipsis based on `-webkit-line-clamp`. See [caniuse](https://caniuse.com/?search=line-clamp) for compitablilty.
```html
<n-ellipsis :line-clamp="2">
电灯熄灭 物换星移 泥牛入海<br />黑暗好像 一颗巨石 按在胸口<br />独脚大盗
百万富翁 摸爬滚打<br />黑暗好像 一颗巨石 按在胸口
</n-ellipsis>
```

View File

View File

@ -0,0 +1,9 @@
# 基础
带弹出提示基本的单行省略。
```html
<n-ellipsis style="max-width: 240px;">
住在我心里孤独的 孤独的海怪 痛苦之王 开始厌倦 深海的光 停滞的海浪
</n-ellipsis>
```

View File

@ -0,0 +1,26 @@
# 文本省略 Ellipsis
复杂度不会消失,只会转移。
当你听到一些人对于精致的概念模型侃侃而谈,请保持清醒。
## 演示
```demo
basic
line-clamp
```
## Props
| 名称 | 类型 | 默认值 | 说明 | 版本 |
| --- | --- | --- | --- | --- |
| expand-trigger | `'click'` | `undefined` | | 2.0.2 |
| line-clamp | `number \| string` | `undefined` | 最大行数 | 2.0.2 |
| tooltip | `boolean \| TooltipProps` | `true` | Tooltip 的属性 | 2.0.2 |
## Slots
| 名称 | 参数 | 说明 |
| ------- | ---- | ---- |
| default | `()` | |

View File

@ -0,0 +1,10 @@
# 最大行数
naive-ui 提供基于 `-webkit-line-clamp` 的多行省略。兼容性参见 [caniuse](https://caniuse.com/?search=line-clamp)。
```html
<n-ellipsis :line-clamp="2">
电灯熄灭 物换星移 泥牛入海<br />黑暗好像 一颗巨石 按在胸口<br />独脚大盗
百万富翁 摸爬滚打<br />黑暗好像 一颗巨石 按在胸口
</n-ellipsis>
```

1
src/ellipsis/index.ts Normal file
View File

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

145
src/ellipsis/src/index.tsx Normal file
View File

@ -0,0 +1,145 @@
import { defineComponent, h, ref, PropType, computed, mergeProps } from 'vue'
import type { PopoverProps } from '../../popover/src/Popover'
import { TooltipRef } from '../../tooltip/src/Tooltip'
import { NTooltip } from '../../tooltip'
import { useTheme } from '../../_mixins'
import type { ThemeProps } from '../../_mixins'
import { ellipsisLight } from '../styles'
import type { EllipsisTheme } from '../styles'
import style from './styles/index.cssr'
const lineClampClass = 'n-ellpisis--line-clamp'
export default defineComponent({
name: 'Ellipsis',
inheritAttrs: false,
props: {
...(useTheme.props as ThemeProps<EllipsisTheme>),
expandTrigger: String as PropType<'click'>,
lineClamp: [Number, String],
tooltip: {
type: [Boolean, Object] as PropType<PopoverProps | boolean>,
default: true
}
},
setup (props, { slots, attrs }) {
const mergedTheme = useTheme(
'Ellipsis',
'ellipsis',
style,
ellipsisLight,
props
)
const triggerRef = ref<HTMLElement | null>(null)
const tooltipRef = ref<TooltipRef | null>(null)
const expandedRef = ref(false)
const ellpisisStyleRef = computed(() => {
const { lineClamp } = props
const { value: expanded } = expandedRef
const cursor = props.expandTrigger === 'click' ? 'pointer' : ''
if (lineClamp !== undefined) {
return {
cursor,
textOverflow: '',
'-webkit-line-clamp': expanded ? '' : lineClamp
}
} else {
return {
cursor,
textOverflow: expanded ? '' : 'ellipsis',
'-webkit-line-clamp': ''
}
}
})
function getTooltipDisabled (): boolean {
const { value: expanded } = expandedRef
if (expanded) return true
const { value: trigger } = triggerRef
if (trigger) {
const { lineClamp } = props
// we need to apply style here, since the dom may be updated in
// nextTick, measure dom size will derive wrong result
syncEllipsisStyle(trigger)
if (lineClamp !== undefined) {
return trigger.scrollHeight <= trigger.offsetHeight
}
return trigger.scrollWidth <= trigger.offsetWidth
}
return false
}
const handleClickRef = computed(() => {
return props.expandTrigger === 'click'
? () => {
const { value: expanded } = expandedRef
if (expanded) {
tooltipRef.value?.setShow(false)
}
expandedRef.value = !expanded
}
: undefined
})
const renderTrigger = (): JSX.Element => (
<span
{...mergeProps(attrs, {
class: [
'n-ellpisis',
props.lineClamp !== undefined ? lineClampClass : undefined
],
style: ellpisisStyleRef.value
})}
ref="triggerRef"
onClick={handleClickRef.value}
>
{slots}
</span>
)
function syncEllipsisStyle (trigger: HTMLElement): void {
if (!trigger) return
const latestStyle = ellpisisStyleRef.value
if (props.lineClamp !== undefined) {
if (!trigger.classList.contains(lineClampClass)) {
trigger.classList.add(lineClampClass)
}
} else {
if (trigger.classList.contains(lineClampClass)) {
trigger.classList.remove(lineClampClass)
}
}
for (const key in latestStyle) {
// guard can make it a little faster
if ((trigger.style as any)[key] !== (latestStyle as any)[key]) {
;(trigger.style as any)[key] = (latestStyle as any)[key]
}
}
}
return {
mergedTheme,
triggerRef,
tooltipRef,
handleClick: handleClickRef,
renderTrigger,
getTooltipDisabled
}
},
render () {
const { tooltip, renderTrigger } = this
if (tooltip) {
const { mergedTheme } = this
return (
<NTooltip
ref="tooltipRef"
placement="top"
{...tooltip}
getDisabled={this.getTooltipDisabled}
theme={mergedTheme.peers.Tooltip}
themeOverrides={mergedTheme.peerOverrides.Tooltip}
>
{{
trigger: renderTrigger,
default: this.$slots.default
}}
</NTooltip>
)
} else return renderTrigger()
}
})

View File

@ -0,0 +1,15 @@
import { cB, cNotM, cM } from '../../../_utils/cssr'
export default cB('ellpisis', {
overflow: 'hidden'
}, [
cNotM('line-clamp', `
white-space: nowrap;
display: inline-block;
max-width: 100%;
`),
cM('line-clamp', `
display: -webkit-inline-box;
-webkit-box-orient: vertical;
`)
])

View File

@ -0,0 +1,13 @@
import { commonDark } from '../../_styles/common'
import { EllipsisTheme } from './light'
import { tooltipDark } from '../../tooltip/styles'
const ellipsisDark: EllipsisTheme = {
name: 'Ellipsis',
common: commonDark,
peers: {
Tooltip: tooltipDark
}
}
export default ellipsisDark

View File

@ -0,0 +1,3 @@
export { default as ellipsisDark } from './dark'
export { default as ellipsisLight } from './light'
export type { EllipsisTheme } from './light'

View File

@ -0,0 +1,14 @@
import { createTheme } from '../../_mixins'
import { commonLight } from '../../_styles/common'
import { tooltipLight } from '../../tooltip/styles'
const ellipsisLight = createTheme({
name: 'Ellipsis',
common: commonLight,
peers: {
Tooltip: tooltipLight
}
})
export default ellipsisLight
export type EllipsisTheme = typeof ellipsisLight

View File

@ -8,7 +8,8 @@ import {
PropType,
VNode,
provide,
CSSProperties
CSSProperties,
ExtractPropTypes
} from 'vue'
import { VBinder, VTarget, FollowerPlacement } from 'vueuc'
import { useMergedState, useCompitable, useIsMounted, useMemo } from 'vooks'
@ -179,6 +180,8 @@ export const popoverProps = {
maxWidth: Number
}
export type PopoverProps = Partial<ExtractPropTypes<typeof popoverProps>>
export default defineComponent({
name: 'Popover',
inheritAttrs: false,
@ -303,7 +306,7 @@ export default defineComponent({
}
}
function handleClick (): void {
if (props.trigger === 'click' && !props.disabled) {
if (props.trigger === 'click' && !getMergedDisabled()) {
clearTimer()
const nextShow = !getMergedShow()
doUpdateShow(nextShow)

View File

@ -22,6 +22,7 @@ import { dropdownDark } from '../dropdown/styles'
import { dynamicInputDark } from '../dynamic-input/styles'
import { dynamicTagsDark } from '../dynamic-tags/styles'
import { elementDark } from '../element/styles'
import { ellipsisDark } from '../ellipsis/styles'
import { emptyDark } from '../empty/styles'
import { formDark } from '../form/styles'
import { gradientTextDark } from '../gradient-text/styles'
@ -91,6 +92,7 @@ export const darkTheme: BuiltInGlobalTheme = {
DynamicTags: dynamicTagsDark,
Element: elementDark,
Empty: emptyDark,
Ellipsis: ellipsisDark,
Form: formDark,
GradientText: gradientTextDark,
Icon: iconDark,

View File

@ -7,6 +7,8 @@ import type { ThemeProps } from '../../_mixins'
import { tooltipLight } from '../styles'
import type { TooltipTheme } from '../styles'
export type TooltipRef = PopoverRef
export default defineComponent({
name: 'Tooltip',
props: {
@ -26,11 +28,19 @@ export default defineComponent({
props
)
const popoverRef = ref<PopoverRef | null>(null)
return {
popoverRef,
const tooltipExposedMethod: TooltipRef = {
syncPosition () {
;(popoverRef.value as PopoverRef).syncPosition()
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
popoverRef.value!.syncPosition()
},
setShow (show: boolean) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
popoverRef.value!.setShow(show)
}
}
return {
...tooltipExposedMethod,
popoverRef,
mergedTheme: themeRef,
popoverThemeOverrides: computed(() => {
return themeRef.value.self