@charcoal-ui/react 5.3.0 → 5.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/TextEllipsis/helper.d.ts +5 -0
- package/dist/components/TextEllipsis/helper.d.ts.map +1 -0
- package/dist/components/TextEllipsis/index.d.ts +14 -0
- package/dist/components/TextEllipsis/index.d.ts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +24 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/layered.css +24 -0
- package/dist/layered.css.map +1 -1
- package/package.json +5 -5
- package/src/components/Button/index.story.tsx +1 -1
- package/src/components/Checkbox/CheckboxInput/index.story.tsx +1 -1
- package/src/components/Checkbox/index.story.tsx +1 -1
- package/src/components/Clickable/index.story.tsx +1 -1
- package/src/components/DropdownSelector/ListItem/index.story.tsx +1 -1
- package/src/components/DropdownSelector/MenuList/index.story.tsx +1 -1
- package/src/components/DropdownSelector/Popover/index.story.tsx +1 -1
- package/src/components/DropdownSelector/index.story.tsx +1 -1
- package/src/components/HintText/index.story.tsx +1 -1
- package/src/components/Icon/index.story.tsx +1 -1
- package/src/components/IconButton/index.story.tsx +1 -1
- package/src/components/LoadingSpinner/index.story.tsx +1 -1
- package/src/components/Modal/index.story.tsx +1 -1
- package/src/components/MultiSelect/index.story.tsx +1 -1
- package/src/components/Radio/index.story.tsx +1 -1
- package/src/components/SegmentedControl/index.story.tsx +1 -1
- package/src/components/Switch/index.story.tsx +1 -1
- package/src/components/TagItem/index.story.tsx +1 -1
- package/src/components/TextArea/TextArea.story.tsx +1 -1
- package/src/components/TextEllipsis/TextEllipsis.story.tsx +121 -0
- package/src/components/TextEllipsis/helper.ts +72 -0
- package/src/components/TextEllipsis/index.css +23 -0
- package/src/components/TextEllipsis/index.tsx +53 -0
- package/src/components/TextField/TextField.story.tsx +1 -1
- package/src/index.ts +4 -0
|
@@ -12,7 +12,7 @@ import TextField from '../TextField'
|
|
|
12
12
|
import DropdownSelector from '../DropdownSelector'
|
|
13
13
|
import Checkbox from '../Checkbox'
|
|
14
14
|
import DropdownMenuItem from '../DropdownSelector/DropdownMenuItem'
|
|
15
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
15
|
+
import { Meta, StoryObj } from '@storybook/react-vite'
|
|
16
16
|
|
|
17
17
|
export default {
|
|
18
18
|
title: 'react/Modal',
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
default as MultiSelect,
|
|
5
5
|
MultiSelectGroupProps,
|
|
6
6
|
} from '.'
|
|
7
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
7
|
+
import { Meta, StoryObj } from '@storybook/react-vite'
|
|
8
8
|
import { action } from 'storybook/actions'
|
|
9
9
|
|
|
10
10
|
const StyledMultiSelectGroup = (props: MultiSelectGroupProps) => {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { action } from 'storybook/actions'
|
|
2
2
|
import Clickable from '../Clickable'
|
|
3
3
|
import TextArea from '.'
|
|
4
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
4
|
+
import { Meta, StoryObj } from '@storybook/react-vite'
|
|
5
5
|
|
|
6
6
|
export default {
|
|
7
7
|
title: 'react/TextArea',
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react-webpack5'
|
|
2
|
+
import TextEllipsis from '.'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'react/TextEllipsis',
|
|
6
|
+
component: TextEllipsis,
|
|
7
|
+
parameters: {
|
|
8
|
+
layout: 'centered',
|
|
9
|
+
},
|
|
10
|
+
render(args) {
|
|
11
|
+
return (
|
|
12
|
+
<div style={{ width: 200 }}>
|
|
13
|
+
<TextEllipsis {...args} />
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
},
|
|
17
|
+
} satisfies Meta<typeof TextEllipsis>
|
|
18
|
+
|
|
19
|
+
export const Default: StoryObj<typeof TextEllipsis> = {
|
|
20
|
+
args: {
|
|
21
|
+
lineHeight: 24,
|
|
22
|
+
lineLimit: 1,
|
|
23
|
+
children:
|
|
24
|
+
'これは非常に長いテキストです。1行で収まらない場合に省略記号(...)で表示が切り詰められます。ホバーすると全文がツールチップで表示されます。',
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const SingleLine: StoryObj<typeof TextEllipsis> = {
|
|
29
|
+
args: {
|
|
30
|
+
lineHeight: 24,
|
|
31
|
+
lineLimit: 1,
|
|
32
|
+
children:
|
|
33
|
+
'Single line ellipsis. This long text will be truncated with an ellipsis at the end.',
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const MultiLine: StoryObj<typeof TextEllipsis> = {
|
|
38
|
+
args: {
|
|
39
|
+
lineHeight: 24,
|
|
40
|
+
lineLimit: 2,
|
|
41
|
+
children:
|
|
42
|
+
'複数行の省略表示です。lineLimit で指定した行数まで表示し、それ以降は...で省略されます。',
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const CustomTitle: StoryObj<typeof TextEllipsis> = {
|
|
47
|
+
args: {
|
|
48
|
+
lineHeight: 24,
|
|
49
|
+
lineLimit: 1,
|
|
50
|
+
title: 'カスタムのツールチップ文言',
|
|
51
|
+
children: 'title を渡すとツールチップはその文言になります。',
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const EmptyTitle: StoryObj<typeof TextEllipsis> = {
|
|
56
|
+
args: {
|
|
57
|
+
lineHeight: 24,
|
|
58
|
+
lineLimit: 1,
|
|
59
|
+
title: '',
|
|
60
|
+
children:
|
|
61
|
+
'title="" を渡すとツールチップは表示されません。ホバーしても何も出ません。',
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const narrowRender = (args: React.ComponentProps<typeof TextEllipsis>) => (
|
|
66
|
+
<div style={{ width: 80 }} lang="en">
|
|
67
|
+
<TextEllipsis {...args} />
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
export const HyphensAuto: StoryObj<typeof TextEllipsis> = {
|
|
72
|
+
args: {
|
|
73
|
+
lineHeight: 24,
|
|
74
|
+
lineLimit: 2,
|
|
75
|
+
hyphens: 'auto',
|
|
76
|
+
children: 'internationalization',
|
|
77
|
+
},
|
|
78
|
+
render: narrowRender,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const HyphensManual: StoryObj<typeof TextEllipsis> = {
|
|
82
|
+
args: {
|
|
83
|
+
lineHeight: 24,
|
|
84
|
+
lineLimit: 2,
|
|
85
|
+
hyphens: 'manual',
|
|
86
|
+
children: 'inter\u00ADnational\u00ADization',
|
|
87
|
+
},
|
|
88
|
+
render: narrowRender,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const HyphensNone: StoryObj<typeof TextEllipsis> = {
|
|
92
|
+
args: {
|
|
93
|
+
lineHeight: 24,
|
|
94
|
+
lineLimit: 2,
|
|
95
|
+
hyphens: 'none',
|
|
96
|
+
children: 'internationalization',
|
|
97
|
+
},
|
|
98
|
+
render: narrowRender,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const LineHeightInherit: StoryObj<typeof TextEllipsis> = {
|
|
102
|
+
args: {
|
|
103
|
+
lineLimit: 2,
|
|
104
|
+
children: 'lineHeight を指定しない場合、親の line-height を継承します。',
|
|
105
|
+
},
|
|
106
|
+
render: (args) => (
|
|
107
|
+
<div style={{ lineHeight: 1.8, width: 200 }}>
|
|
108
|
+
<TextEllipsis {...args} />
|
|
109
|
+
</div>
|
|
110
|
+
),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export const ShowTooltipFalse: StoryObj<typeof TextEllipsis> = {
|
|
114
|
+
args: {
|
|
115
|
+
lineHeight: 24,
|
|
116
|
+
lineLimit: 1,
|
|
117
|
+
showTooltip: false,
|
|
118
|
+
children:
|
|
119
|
+
'showTooltip=false の場合、ホバーしてもツールチップは表示されません。',
|
|
120
|
+
},
|
|
121
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// https://github.com/fernandopasik/react-children-utilities/blob/971d8a0324e6183734d8d1af9a65dbad18ab3d00/src/lib/onlyText.ts
|
|
2
|
+
|
|
3
|
+
import { Children, isValidElement, ReactElement, ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
export const getFinalTitle = (
|
|
6
|
+
showTooltip: boolean,
|
|
7
|
+
title?: string,
|
|
8
|
+
children?: ReactNode,
|
|
9
|
+
): string | undefined => {
|
|
10
|
+
// 1. 表示フラグが false なら即終了
|
|
11
|
+
if (!showTooltip) {
|
|
12
|
+
return undefined
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// 2. タイトルの候補を取得
|
|
16
|
+
const resolvedTitle = title ?? onlyText(children)
|
|
17
|
+
|
|
18
|
+
// 3. resolvedTitle が falsy (undefined, null, "") なら終了
|
|
19
|
+
if (!resolvedTitle) {
|
|
20
|
+
return undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// 4. 有効な文字列のみを返す
|
|
24
|
+
return resolvedTitle
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const hasChildren = (
|
|
28
|
+
element: ReactNode,
|
|
29
|
+
): element is ReactElement<{ children: ReactNode[] }> =>
|
|
30
|
+
isValidElement<{ children?: ReactNode[] }>(element) &&
|
|
31
|
+
Boolean(element.props.children)
|
|
32
|
+
|
|
33
|
+
export const childToString = (
|
|
34
|
+
child?: number | string | boolean | {} | null,
|
|
35
|
+
): string => {
|
|
36
|
+
if (
|
|
37
|
+
typeof child === 'undefined' ||
|
|
38
|
+
child === null ||
|
|
39
|
+
typeof child === 'boolean'
|
|
40
|
+
) {
|
|
41
|
+
return ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (JSON.stringify(child) === '{}') {
|
|
45
|
+
return ''
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (child as string | number).toString()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const onlyText = (children: ReactNode): string => {
|
|
52
|
+
if (!Array.isArray(children) && !isValidElement(children)) {
|
|
53
|
+
return childToString(children)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return Children.toArray(children).reduce(
|
|
57
|
+
(text: string, child: ReactNode): string => {
|
|
58
|
+
let newText = ''
|
|
59
|
+
|
|
60
|
+
if (isValidElement(child) && hasChildren(child)) {
|
|
61
|
+
newText = onlyText(child.props.children)
|
|
62
|
+
} else if (isValidElement(child) && !hasChildren(child)) {
|
|
63
|
+
newText = ''
|
|
64
|
+
} else {
|
|
65
|
+
newText = childToString(child)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return text.concat(newText)
|
|
69
|
+
},
|
|
70
|
+
'',
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
.charcoal-text-ellipsis {
|
|
2
|
+
overflow: hidden;
|
|
3
|
+
overflow-wrap: break-word;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.charcoal-text-ellipsis[data-has-line-height='true'] {
|
|
7
|
+
line-height: var(--charcoal-text-ellipsis-line-height);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.charcoal-text-ellipsis[data-has-line-height='false'] {
|
|
11
|
+
line-height: inherit;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.charcoal-text-ellipsis[data-line-limit='1'] {
|
|
15
|
+
text-overflow: ellipsis;
|
|
16
|
+
white-space: nowrap;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.charcoal-text-ellipsis[data-line-limit]:not([data-line-limit='1']) {
|
|
20
|
+
display: -webkit-box;
|
|
21
|
+
-webkit-box-orient: vertical;
|
|
22
|
+
-webkit-line-clamp: var(--charcoal-text-ellipsis-line-limit);
|
|
23
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import './index.css'
|
|
2
|
+
|
|
3
|
+
import { getFinalTitle } from './helper'
|
|
4
|
+
import { useClassNames } from '../../_lib/useClassNames'
|
|
5
|
+
import { CSSProperties } from 'react'
|
|
6
|
+
|
|
7
|
+
export type TextEllipsisProps = {
|
|
8
|
+
lineHeight?: number
|
|
9
|
+
lineLimit?: number
|
|
10
|
+
hyphens?: CSSProperties['hyphens']
|
|
11
|
+
/** html title属性を付与。false のときは付与せず、空文字のときは表示しない */
|
|
12
|
+
showTooltip?: boolean
|
|
13
|
+
} & React.ComponentPropsWithoutRef<'div'>
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 複数行のテキストに表示行数制限を設けて`...`で省略する
|
|
17
|
+
*/
|
|
18
|
+
export default function TextEllipsis({
|
|
19
|
+
lineHeight,
|
|
20
|
+
lineLimit = 1,
|
|
21
|
+
children,
|
|
22
|
+
title,
|
|
23
|
+
hyphens = 'auto',
|
|
24
|
+
showTooltip = true,
|
|
25
|
+
...props
|
|
26
|
+
}: TextEllipsisProps) {
|
|
27
|
+
const finalTitle = getFinalTitle(showTooltip, title, children)
|
|
28
|
+
|
|
29
|
+
const classNames = useClassNames('charcoal-text-ellipsis', props.className)
|
|
30
|
+
const hasLineHeight = lineHeight !== undefined
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div
|
|
34
|
+
{...props}
|
|
35
|
+
className={classNames}
|
|
36
|
+
data-line-limit={lineLimit}
|
|
37
|
+
data-has-line-height={hasLineHeight}
|
|
38
|
+
style={
|
|
39
|
+
{
|
|
40
|
+
...(hasLineHeight && {
|
|
41
|
+
'--charcoal-text-ellipsis-line-height': `${lineHeight}px`,
|
|
42
|
+
}),
|
|
43
|
+
'--charcoal-text-ellipsis-line-limit': lineLimit,
|
|
44
|
+
hyphens,
|
|
45
|
+
...props.style,
|
|
46
|
+
} satisfies React.CSSProperties
|
|
47
|
+
}
|
|
48
|
+
title={finalTitle}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</div>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import Clickable from '../Clickable'
|
|
2
2
|
import TextField from '.'
|
|
3
3
|
import { useState } from 'react'
|
|
4
|
-
import { Meta, StoryObj } from '@storybook/react-
|
|
4
|
+
import { Meta, StoryObj } from '@storybook/react-vite'
|
|
5
5
|
|
|
6
6
|
export default {
|
|
7
7
|
title: 'react/TextField',
|