@charcoal-ui/react-sandbox 1.0.0-alpha.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.
Files changed (41) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +18 -0
  3. package/package.json +72 -0
  4. package/src/_lib/compat.ts +15 -0
  5. package/src/components/Carousel/index.story.tsx +86 -0
  6. package/src/components/Carousel/index.tsx +382 -0
  7. package/src/components/CarouselButton/index.story.tsx +44 -0
  8. package/src/components/CarouselButton/index.tsx +162 -0
  9. package/src/components/Filter/index.story.tsx +80 -0
  10. package/src/components/Filter/index.tsx +182 -0
  11. package/src/components/HintText/index.story.tsx +19 -0
  12. package/src/components/HintText/index.tsx +95 -0
  13. package/src/components/Layout/index.story.tsx +121 -0
  14. package/src/components/Layout/index.tsx +363 -0
  15. package/src/components/LeftMenu/index.tsx +68 -0
  16. package/src/components/MenuListItem/index.story.tsx +143 -0
  17. package/src/components/MenuListItem/index.tsx +226 -0
  18. package/src/components/Pager/index.story.tsx +102 -0
  19. package/src/components/Pager/index.tsx +255 -0
  20. package/src/components/Spinner/index.story.tsx +47 -0
  21. package/src/components/Spinner/index.tsx +86 -0
  22. package/src/components/SwitchCheckbox/index.story.tsx +32 -0
  23. package/src/components/SwitchCheckbox/index.tsx +147 -0
  24. package/src/components/TextEllipsis/helper.ts +57 -0
  25. package/src/components/TextEllipsis/index.story.tsx +41 -0
  26. package/src/components/TextEllipsis/index.tsx +35 -0
  27. package/src/components/WithIcon/index.story.tsx +145 -0
  28. package/src/components/WithIcon/index.tsx +158 -0
  29. package/src/components/icons/Base.tsx +75 -0
  30. package/src/components/icons/DotsIcon.tsx +33 -0
  31. package/src/components/icons/InfoIcon.tsx +30 -0
  32. package/src/components/icons/NextIcon.tsx +47 -0
  33. package/src/components/icons/WedgeIcon.tsx +57 -0
  34. package/src/foundation/contants.ts +6 -0
  35. package/src/foundation/hooks.ts +195 -0
  36. package/src/foundation/support.ts +29 -0
  37. package/src/foundation/utils.ts +31 -0
  38. package/src/index.ts +45 -0
  39. package/src/misc/storybook-helper.ts +17 -0
  40. package/src/styled.ts +3 -0
  41. package/src/type.d.ts +12 -0
@@ -0,0 +1,32 @@
1
+ import { action } from '@storybook/addon-actions'
2
+ import { boolean } from '@storybook/addon-knobs'
3
+ import React from 'react'
4
+ import SwitchCheckbox from '.'
5
+
6
+ export default {
7
+ title: 'Sandbox/Selection Control/SwitchCheckbox',
8
+ component: SwitchCheckbox,
9
+ }
10
+
11
+ export const Default = () => {
12
+ const checked = boolean('checked', false)
13
+ const disabled = boolean('disabled', false)
14
+ const flex = boolean('flex', false)
15
+ const rowReverse = boolean('rowReverse', false)
16
+
17
+ return (
18
+ <SwitchCheckbox
19
+ defaultChecked={checked}
20
+ flex={flex}
21
+ disabled={disabled}
22
+ rowReverse={rowReverse}
23
+ onChange={action('onChange')}
24
+ >
25
+ label
26
+ </SwitchCheckbox>
27
+ )
28
+ }
29
+
30
+ export const On = () => <SwitchCheckbox checked />
31
+ export const Off = () => <SwitchCheckbox checked={false} />
32
+ export const Disabled = () => <SwitchCheckbox checked disabled />
@@ -0,0 +1,147 @@
1
+ import React from 'react'
2
+
3
+ import styled, { css } from 'styled-components'
4
+ import { applyEffect } from '@charcoal-ui/utils'
5
+
6
+ export interface Props extends React.ComponentPropsWithoutRef<'input'> {
7
+ gtmClass?: string
8
+ flex?: boolean
9
+ rowReverse?: boolean
10
+ }
11
+
12
+ export default React.forwardRef(function SwitchCheckbox(
13
+ {
14
+ gtmClass,
15
+ flex = false,
16
+ rowReverse = false,
17
+ children,
18
+ disabled,
19
+ ...props
20
+ }: Props,
21
+ ref: React.Ref<HTMLInputElement>
22
+ ) {
23
+ return (
24
+ <Label
25
+ className={gtmClass !== undefined ? `gtm-${gtmClass}` : ''}
26
+ flex={flex}
27
+ rowReverse={rowReverse}
28
+ aria-disabled={disabled}
29
+ >
30
+ <SwitchOuter>
31
+ <SwitchInput {...props} disabled={disabled} ref={ref} />
32
+ <SwitchInner>
33
+ <SwitchInnerKnob />
34
+ </SwitchInner>
35
+ </SwitchOuter>
36
+ {children != null && (
37
+ <Children rowReverse={rowReverse}>{children}</Children>
38
+ )}
39
+ </Label>
40
+ )
41
+ })
42
+
43
+ const Children = styled.span<{ rowReverse: boolean }>`
44
+ ${(p) =>
45
+ p.rowReverse
46
+ ? css`
47
+ margin-right: 8px;
48
+ `
49
+ : css`
50
+ margin-left: 8px;
51
+ `}
52
+ `
53
+
54
+ const Label = styled.label<{ flex: boolean; rowReverse: boolean }>`
55
+ display: inline-flex;
56
+ align-items: center;
57
+ ${({ flex }) =>
58
+ flex &&
59
+ css`
60
+ display: flex;
61
+ justify-content: space-between;
62
+ `}
63
+ ${({ rowReverse }) =>
64
+ css`
65
+ flex-direction: ${rowReverse ? 'row-reverse' : 'row'};
66
+ `}
67
+ cursor: pointer;
68
+ outline: 0;
69
+
70
+ &[aria-disabled='true'] {
71
+ cursor: auto;
72
+ }
73
+ `
74
+
75
+ const SwitchOuter = styled.span`
76
+ display: inline-flex;
77
+ position: relative;
78
+ z-index: 0;
79
+ `
80
+
81
+ const SwitchInner = styled.div`
82
+ position: relative;
83
+ box-sizing: border-box;
84
+ width: 28px;
85
+ height: 16px;
86
+ border-radius: 16px;
87
+ border: 2px solid transparent;
88
+ background: ${({ theme }) => theme.color.text4};
89
+ transition: box-shadow 0.2s, background-color 0.2s;
90
+ `
91
+
92
+ const SwitchInnerKnob = styled.div`
93
+ position: absolute;
94
+ display: block;
95
+ top: 0;
96
+ left: 0;
97
+ width: 12px;
98
+ height: 12px;
99
+ background-color: ${({ theme }) => theme.color.text5};
100
+ border-radius: 50%;
101
+ transform: translateX(0);
102
+ transition: transform 0.2s;
103
+ `
104
+
105
+ const SwitchInput = styled.input.attrs({
106
+ type: 'checkbox' as string,
107
+ })`
108
+ position: absolute;
109
+ /* NOTE: this is contained by the GraphicCheckboxOuter */
110
+ z-index: 1;
111
+ top: 0;
112
+ left: 0;
113
+ width: 100%;
114
+ height: 100%;
115
+ /* just to control the clickable area if used standalone */
116
+ border-radius: 16px;
117
+ opacity: 0;
118
+ appearance: none;
119
+ outline: none;
120
+ cursor: pointer;
121
+
122
+ &:checked {
123
+ ~ ${SwitchInner} {
124
+ background-color: ${({ theme }) => theme.color.brand};
125
+
126
+ ${SwitchInnerKnob} {
127
+ transform: translateX(12px);
128
+ }
129
+ }
130
+ }
131
+
132
+ &:disabled {
133
+ cursor: auto;
134
+
135
+ ~ ${SwitchInner} {
136
+ opacity: ${({ theme }) => theme.elementEffect.disabled.opacity};
137
+ }
138
+ }
139
+
140
+ &:not(:disabled):focus {
141
+ ~ ${SwitchInner} {
142
+ box-shadow: 0 0 0 4px
143
+ ${({ theme }) =>
144
+ applyEffect(theme.color.brand, theme.elementEffect.disabled)};
145
+ }
146
+ }
147
+ `
@@ -0,0 +1,57 @@
1
+ // https://github.com/fernandopasik/react-children-utilities/blob/971d8a0324e6183734d8d1af9a65dbad18ab3d00/src/lib/onlyText.ts
2
+
3
+ import {
4
+ Children,
5
+ isValidElement,
6
+ ReactElement,
7
+ ReactNode,
8
+ ReactText,
9
+ } from 'react'
10
+
11
+ const hasChildren = (
12
+ element: ReactNode
13
+ ): element is ReactElement<{ children: ReactNode[] }> =>
14
+ isValidElement<{ children?: ReactNode[] }>(element) &&
15
+ Boolean(element.props.children)
16
+
17
+ export const childToString = (
18
+ // eslint-disable-next-line @typescript-eslint/ban-types
19
+ child?: ReactText | boolean | {} | null
20
+ ): string => {
21
+ if (
22
+ typeof child === 'undefined' ||
23
+ child === null ||
24
+ typeof child === 'boolean'
25
+ ) {
26
+ return ''
27
+ }
28
+
29
+ if (JSON.stringify(child) === '{}') {
30
+ return ''
31
+ }
32
+
33
+ return (child as string | number).toString()
34
+ }
35
+
36
+ export const onlyText = (children: ReactNode): string => {
37
+ if (!Array.isArray(children) && !isValidElement(children)) {
38
+ return childToString(children)
39
+ }
40
+
41
+ return Children.toArray(children).reduce(
42
+ (text: string, child: ReactNode): string => {
43
+ let newText = ''
44
+
45
+ if (isValidElement(child) && hasChildren(child)) {
46
+ newText = onlyText(child.props.children)
47
+ } else if (isValidElement(child) && !hasChildren(child)) {
48
+ newText = ''
49
+ } else {
50
+ newText = childToString(child)
51
+ }
52
+
53
+ return text.concat(newText)
54
+ },
55
+ ''
56
+ )
57
+ }
@@ -0,0 +1,41 @@
1
+ import { number, text } from '@storybook/addon-knobs'
2
+ import React from 'react'
3
+ import styled from 'styled-components'
4
+ import { TextEllipsis } from '.'
5
+
6
+ export default {
7
+ title: 'Sandbox/TextEllipsis',
8
+ component: TextEllipsis,
9
+ }
10
+
11
+ export const Default = () => {
12
+ const maxRows = number('maxRaws', 2)
13
+ const contentText = text(
14
+ 'text',
15
+ '隴西の李徴は博学才穎、天宝の末年、若くして名を虎榜に連ね、ついで江南尉に補せられたが、性、狷介、自ら恃むところ頗る厚く、賤吏に甘んずるを潔しとしなかった。' +
16
+ 'いくばくもなく官を退いた後は、故山、※(「埒のつくり+虎」、第3水準1-91-48)略に帰臥し、人と交を絶って、ひたすら詩作に耽った。' +
17
+ '下吏となって長く膝を俗悪な大官の前に屈するよりは、詩家としての名を死後百年に遺そうとしたのである。' +
18
+ 'しかし、文名は容易に揚らず、生活は日を逐うて苦しくなる。' +
19
+ '李徴は漸く焦躁に駆られて来た。この頃からその容貌も峭刻となり、肉落ち骨秀で、眼光のみ徒らに炯々として、曾て進士に登第した頃の豊頬の美少年の俤は、何処に求めようもない。' +
20
+ '数年の後、貧窮に堪えず、妻子の衣食のために遂に節を屈して、再び東へ赴き、一地方官吏の職を奉ずることになった。' +
21
+ '一方、これは、己の詩業に半ば絶望したためでもある。' +
22
+ '曾ての同輩は既に遥か高位に進み、彼が昔、鈍物として歯牙にもかけなかったその連中の下命を拝さねばならぬことが、往年の儁才李徴の自尊心を如何に傷けたかは、想像に難くない。' +
23
+ '彼は怏々として楽しまず、狂悖の性は愈々抑え難くなった。' +
24
+ '一年の後、公用で旅に出、汝水のほとりに宿った時、遂に発狂した。' +
25
+ '或夜半、急に顔色を変えて寝床から起上ると、何か訳の分らぬことを叫びつつそのまま下にとび下りて、闇の中へ駈出した。' +
26
+ '彼は二度と戻って来なかった。附近の山野を捜索しても、何の手掛りもない。' +
27
+ 'その後李徴がどうなったかを知る者は、誰もなかった。'
28
+ )
29
+
30
+ return (
31
+ <FontSizeStyleProvider>
32
+ <TextEllipsis lineHeight={22} lineLimit={maxRows}>
33
+ {contentText}
34
+ </TextEllipsis>
35
+ </FontSizeStyleProvider>
36
+ )
37
+ }
38
+
39
+ const FontSizeStyleProvider = styled.div`
40
+ font-size: 14px;
41
+ `
@@ -0,0 +1,35 @@
1
+ import styled, { css } from 'styled-components'
2
+ import { onlyText } from './helper'
3
+
4
+ export interface Props {
5
+ lineHeight: number
6
+ lineLimit?: number
7
+ }
8
+
9
+ /**
10
+ * 複数行のテキストに表示行数制限を設けて`...`で省略する
11
+ */
12
+ export const TextEllipsis = styled.div.attrs(
13
+ ({ children, title = onlyText(children) }) => ({
14
+ title: title !== '' ? title : undefined,
15
+ })
16
+ )<Props>`
17
+ overflow: hidden;
18
+ line-height: ${(props) => props.lineHeight}px;
19
+ /* For english */
20
+ overflow-wrap: break-word;
21
+
22
+ ${({ lineLimit = 1, lineHeight }) =>
23
+ lineLimit === 1
24
+ ? css`
25
+ text-overflow: ellipsis;
26
+ white-space: nowrap;
27
+ `
28
+ : css`
29
+ display: box;
30
+ -webkit-box-orient: vertical;
31
+ -webkit-line-clamp: ${lineLimit};
32
+ /* Fallback for -webkit-line-clamp */
33
+ max-height: ${lineHeight * lineLimit}px;
34
+ `}
35
+ `
@@ -0,0 +1,145 @@
1
+ import { boolean } from '@storybook/addon-knobs'
2
+ import React from 'react'
3
+ import styled, { css } from 'styled-components'
4
+ import { theme } from '../../styled'
5
+ import WithIcon from '.'
6
+ import { applyEffect } from '@charcoal-ui/utils'
7
+
8
+ export default {
9
+ title: 'Sandbox/WithIcon',
10
+ component: WithIcon,
11
+ }
12
+
13
+ export const Default = () => {
14
+ const fit = boolean('fit', true)
15
+ const bigger = boolean(
16
+ 'Icon height bigger than line-height (`fit` is effective)',
17
+ false
18
+ )
19
+ const prefix = boolean('prefix', false)
20
+ const show = boolean('show', true)
21
+ return (
22
+ <Container>
23
+ <WithIcon
24
+ icon={<TestInlineIcon lineHeight={bigger ? 32 : 22} />}
25
+ show={show}
26
+ prefix={prefix}
27
+ fit={fit}
28
+ >
29
+ Menu
30
+ </WithIcon>
31
+ </Container>
32
+ )
33
+ }
34
+
35
+ export const Performance = () => (
36
+ <Container>
37
+ <WithIcon
38
+ icon={<TestInlineIcon lineHeight={22} />}
39
+ show
40
+ fit
41
+ // Hard-coded width (ResizeObserver free and NO measuring cost)
42
+ width={16}
43
+ >
44
+ Menu
45
+ </WithIcon>
46
+ </Container>
47
+ )
48
+
49
+ export const Naive = () => (
50
+ // NO measuring cost
51
+ <Container>
52
+ <WithIcon icon={<TestInlineIcon lineHeight={22} />} show>
53
+ Menu
54
+ </WithIcon>
55
+ </Container>
56
+ )
57
+
58
+ export const Prefix = () => (
59
+ <Container>
60
+ <WithIcon icon={<TestIcon />} show prefix fit>
61
+ Selection
62
+ </WithIcon>
63
+ </Container>
64
+ )
65
+
66
+ function Container(props: { className?: string; children?: React.ReactNode }) {
67
+ return (
68
+ <div
69
+ css={css`
70
+ ${theme((o) => [o.font.text1, o.typography(14).preserveHalfLeading])}
71
+ display: flex;
72
+ `}
73
+ >
74
+ <div
75
+ css={css`
76
+ background-color: ${({ theme }) =>
77
+ applyEffect(theme.color.brand, theme.elementEffect.disabled)};
78
+ `}
79
+ {...props}
80
+ />
81
+ </div>
82
+ )
83
+ }
84
+
85
+ export const Hide = () => (
86
+ <Container>
87
+ <WithIcon icon={<TestIcon />} show={false} prefix>
88
+ Selection
89
+ </WithIcon>
90
+ </Container>
91
+ )
92
+
93
+ export const Collapse = () => (
94
+ <Container>
95
+ <WithIcon icon={<TestIcon />} show="collapse" prefix>
96
+ Selection
97
+ </WithIcon>
98
+ </Container>
99
+ )
100
+
101
+ export const LongText = () => (
102
+ <Container
103
+ css={`
104
+ width: 200px;
105
+ `}
106
+ >
107
+ <WithIcon icon={<TestIcon />}>
108
+ Long Long Long Long Long Long Long Long Long Long Text
109
+ </WithIcon>
110
+ </Container>
111
+ )
112
+
113
+ export const LongTextOverflow = () => (
114
+ <Container
115
+ css={`
116
+ width: 200px;
117
+ `}
118
+ >
119
+ <WithIcon icon={<TestIcon />} fixed>
120
+ Long Long Long Long Long Long Long Long Long Long Text
121
+ </WithIcon>
122
+ </Container>
123
+ )
124
+
125
+ const TestIcon = styled.div`
126
+ display: inline-block;
127
+ width: 16px;
128
+ height: 16px;
129
+ background-color: currentColor;
130
+ `
131
+
132
+ const TestInlineIcon = styled.div<{ lineHeight: number }>`
133
+ display: inline-flex;
134
+ vertical-align: top;
135
+ align-items: center;
136
+ line-height: ${(p) => p.lineHeight}px;
137
+ height: ${(p) => p.lineHeight}px;
138
+ &::before {
139
+ content: '';
140
+ display: inline-block;
141
+ height: 16px;
142
+ width: 16px;
143
+ background-color: currentColor;
144
+ }
145
+ `
@@ -0,0 +1,158 @@
1
+ import React, { useRef } from 'react'
2
+ import styled, { css } from 'styled-components'
3
+ import { useElementSize } from '../../foundation/hooks'
4
+
5
+ export interface Props {
6
+ children?: React.ReactNode
7
+ icon: React.ReactNode
8
+ /**
9
+ * アイコンを表示。デフォルトがtrueなので、非表示にするときに使います。 (アイコン自体の幅を維持します)
10
+ */
11
+ show?: boolean | 'collapse'
12
+ /**
13
+ * アイコンを前にする
14
+ */
15
+ prefix?: boolean
16
+ /**
17
+ * アイコンの高さが文字の高さよりも大きいケースで有効。アイコンの高さをゼロにしてインラインの高さに関与させないようにします。
18
+ */
19
+ fit?: boolean
20
+ /**
21
+ * `fit`と併用した時にのみ有効な最適化オプション。アイコンの幅の自動計算を行わず指定した数値を利用します。
22
+ */
23
+ width?: number
24
+ /**
25
+ * 親要素のサイズに合わせるのではなく、コンテンツのサイズを優先する
26
+ */
27
+ fixed?: boolean
28
+ }
29
+
30
+ export default React.memo(function WithIcon({
31
+ children,
32
+ icon,
33
+ show = true,
34
+ prefix: pre = false,
35
+ width,
36
+ fit = false,
37
+ fixed = false,
38
+ }: Props) {
39
+ const node = fit ? (
40
+ width === undefined ? (
41
+ <AutoWidthIconAnchor show={show} pre={pre}>
42
+ {icon}
43
+ </AutoWidthIconAnchor>
44
+ ) : (
45
+ <IconAnchor width={width} show={show} pre={pre}>
46
+ <Icon>{icon}</Icon>
47
+ </IconAnchor>
48
+ )
49
+ ) : (
50
+ <IconAnchorNaive show={show} pre={pre}>
51
+ <IconNaive>{icon}</IconNaive>
52
+ </IconAnchorNaive>
53
+ )
54
+
55
+ return (
56
+ <Root>
57
+ {pre && node}
58
+ <Text fixed={fixed}>{children}</Text>
59
+ {!pre && node}
60
+ </Root>
61
+ )
62
+ })
63
+
64
+ const Root = styled.div`
65
+ display: flex;
66
+ align-items: center;
67
+ `
68
+
69
+ const Text = styled.div<{ fixed: boolean }>`
70
+ ${(p) =>
71
+ !p.fixed &&
72
+ css`
73
+ min-width: 0;
74
+ overflow: hidden;
75
+ `}
76
+ white-space: nowrap;
77
+ text-overflow: ellipsis;
78
+ `
79
+
80
+ function AutoWidthIconAnchor({
81
+ children,
82
+ show,
83
+ pre,
84
+ }: {
85
+ children: React.ReactNode
86
+ show: boolean | 'collapse'
87
+ pre: boolean
88
+ }) {
89
+ const ref = useRef<HTMLDivElement>(null)
90
+ // depsを空配列にしないことで初回だけ同期で幅を計算させるテクニック
91
+ const width = useElementSize(ref, [null])?.width ?? 0
92
+ return (
93
+ <IconAnchor width={width} show={show} pre={pre}>
94
+ <Icon ref={ref}>{children}</Icon>
95
+ </IconAnchor>
96
+ )
97
+ }
98
+
99
+ const forceCenteringCss = css`
100
+ > svg {
101
+ display: block;
102
+ }
103
+ `
104
+
105
+ const iconAnchorCss = css`
106
+ ${(p: { show: boolean | 'collapse'; pre: boolean }) =>
107
+ p.show === 'collapse'
108
+ ? css`
109
+ display: none;
110
+ `
111
+ : css`
112
+ visibility: ${p.show ? 'visible' : 'hidden'};
113
+ `};
114
+ ${(p) =>
115
+ p.pre
116
+ ? css`
117
+ margin-right: 4px;
118
+ `
119
+ : css`
120
+ margin-left: 4px;
121
+ `}
122
+ `
123
+
124
+ const IconAnchorNaive = styled.div`
125
+ display: flex;
126
+ align-items: center;
127
+
128
+ ${iconAnchorCss}
129
+ `
130
+
131
+ const IconNaive = styled.div`
132
+ display: inline-flex;
133
+
134
+ ${forceCenteringCss}
135
+ `
136
+
137
+ const IconAnchor = styled.div<{
138
+ width: number
139
+ show: boolean | 'collapse'
140
+ pre: boolean
141
+ }>`
142
+ display: flex;
143
+ position: relative;
144
+ /* Iconをline-heightに関与させない */
145
+ height: 0;
146
+ /* 横方向の領域は確保する */
147
+ width: ${(p) => p.width}px;
148
+
149
+ ${iconAnchorCss}
150
+ `
151
+
152
+ const Icon = styled.div`
153
+ display: inline-flex;
154
+ position: absolute;
155
+ transform: translateY(-50%);
156
+
157
+ ${forceCenteringCss}
158
+ `
@@ -0,0 +1,75 @@
1
+ import React from 'react'
2
+ import styled from 'styled-components'
3
+
4
+ export type IconSizes = 16 | 24 | 32
5
+
6
+ interface Props {
7
+ path: string | React.ReactNode
8
+ viewBoxSize: number
9
+ size?: IconSizes | 40 | 48 | 64 | 72
10
+ transform?: string
11
+ currentColor?: boolean
12
+ fillRule?: 'nonzero' | 'evenodd'
13
+ clipRule?: 'nonzero' | 'evenodd' | 'inherit'
14
+ }
15
+
16
+ export default function IconBase({
17
+ size = 24,
18
+ viewBoxSize,
19
+ currentColor,
20
+ path,
21
+ transform,
22
+ fillRule,
23
+ clipRule,
24
+ }: Props) {
25
+ return (
26
+ <Icon
27
+ viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
28
+ size={size}
29
+ currentColor={currentColor}
30
+ >
31
+ <IconBasePath
32
+ path={path}
33
+ transform={transform}
34
+ fillRule={fillRule}
35
+ clipRule={clipRule}
36
+ />
37
+ </Icon>
38
+ )
39
+ }
40
+
41
+ const Icon = styled.svg<{ size: number; currentColor?: boolean }>`
42
+ stroke: none;
43
+ fill: ${({ currentColor = false, theme }) =>
44
+ currentColor ? 'currentColor' : theme.color.text2};
45
+ width: ${(props) => props.size}px;
46
+ height: ${(props) => props.size}px;
47
+ line-height: 0;
48
+ font-size: 0;
49
+ vertical-align: middle;
50
+ `
51
+
52
+ type IconBasePathProps = Pick<
53
+ Props,
54
+ 'path' | 'transform' | 'fillRule' | 'clipRule'
55
+ >
56
+ export const IconBasePath = ({
57
+ path,
58
+ transform,
59
+ fillRule,
60
+ clipRule,
61
+ }: IconBasePathProps) => {
62
+ if (typeof path === 'string') {
63
+ return (
64
+ <path
65
+ d={path}
66
+ transform={transform}
67
+ fillRule={fillRule}
68
+ clipRule={clipRule}
69
+ />
70
+ )
71
+ } else {
72
+ // eslint-disable-next-line react/jsx-no-useless-fragment
73
+ return <>{path}</>
74
+ }
75
+ }