@channel.io/bezier-react 4.0.0-next.3 → 4.0.0-next.4

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 (189) hide show
  1. package/dist/cjs/styles.css +1 -1
  2. package/dist/cjs/types/props-helpers.js +8 -0
  3. package/dist/cjs/types/props-helpers.js.map +1 -1
  4. package/dist/cjs/v3/BaseStack/BaseStack.js +46 -0
  5. package/dist/cjs/v3/BaseStack/BaseStack.js.map +1 -0
  6. package/dist/cjs/v3/BaseStack/BaseStack.module.scss.js +8 -0
  7. package/dist/cjs/v3/BaseStack/BaseStack.module.scss.js.map +1 -0
  8. package/dist/cjs/v3/Box/Box.js +56 -0
  9. package/dist/cjs/v3/Box/Box.js.map +1 -0
  10. package/dist/cjs/v3/Box/Box.module.scss.js +8 -0
  11. package/dist/cjs/v3/Box/Box.module.scss.js.map +1 -0
  12. package/dist/cjs/v3/Divider/Divider.js +32 -0
  13. package/dist/cjs/v3/Divider/Divider.js.map +1 -0
  14. package/dist/cjs/v3/Divider/Divider.module.scss.js +8 -0
  15. package/dist/cjs/v3/Divider/Divider.module.scss.js.map +1 -0
  16. package/dist/cjs/v3/HStack/HStack.js +19 -0
  17. package/dist/cjs/v3/HStack/HStack.js.map +1 -0
  18. package/dist/cjs/v3/Icon/Icon.js +51 -0
  19. package/dist/cjs/v3/Icon/Icon.js.map +1 -0
  20. package/dist/cjs/v3/Icon/Icon.module.scss.js +8 -0
  21. package/dist/cjs/v3/Icon/Icon.module.scss.js.map +1 -0
  22. package/dist/cjs/v3/SmoothCornersBox/SmoothCornersBox.js +53 -0
  23. package/dist/cjs/v3/SmoothCornersBox/SmoothCornersBox.js.map +1 -0
  24. package/dist/cjs/v3/SmoothCornersBox/SmoothCornersBox.module.scss.js +8 -0
  25. package/dist/cjs/v3/SmoothCornersBox/SmoothCornersBox.module.scss.js.map +1 -0
  26. package/dist/cjs/v3/Spinner/Spinner.js +45 -0
  27. package/dist/cjs/v3/Spinner/Spinner.js.map +1 -0
  28. package/dist/cjs/v3/Spinner/Spinner.module.scss.js +8 -0
  29. package/dist/cjs/v3/Spinner/Spinner.module.scss.js.map +1 -0
  30. package/dist/cjs/v3/Text/Text.js +60 -0
  31. package/dist/cjs/v3/Text/Text.js.map +1 -0
  32. package/dist/cjs/v3/Text/Text.module.scss.js +8 -0
  33. package/dist/cjs/v3/Text/Text.module.scss.js.map +1 -0
  34. package/dist/cjs/v3/VStack/VStack.js +19 -0
  35. package/dist/cjs/v3/VStack/VStack.js.map +1 -0
  36. package/dist/cjs/v3/index.js +22 -0
  37. package/dist/cjs/v3/index.js.map +1 -0
  38. package/dist/esm/styles.css +1 -1
  39. package/dist/esm/types/props-helpers.mjs +5 -1
  40. package/dist/esm/types/props-helpers.mjs.map +1 -1
  41. package/dist/esm/v3/BaseStack/BaseStack.mjs +44 -0
  42. package/dist/esm/v3/BaseStack/BaseStack.mjs.map +1 -0
  43. package/dist/esm/v3/BaseStack/BaseStack.module.scss.mjs +4 -0
  44. package/dist/esm/v3/BaseStack/BaseStack.module.scss.mjs.map +1 -0
  45. package/dist/esm/v3/Box/Box.mjs +54 -0
  46. package/dist/esm/v3/Box/Box.mjs.map +1 -0
  47. package/dist/esm/v3/Box/Box.module.scss.mjs +4 -0
  48. package/dist/esm/v3/Box/Box.module.scss.mjs.map +1 -0
  49. package/dist/esm/v3/Divider/Divider.mjs +30 -0
  50. package/dist/esm/v3/Divider/Divider.mjs.map +1 -0
  51. package/dist/esm/v3/Divider/Divider.module.scss.mjs +4 -0
  52. package/dist/esm/v3/Divider/Divider.module.scss.mjs.map +1 -0
  53. package/dist/esm/v3/HStack/HStack.mjs +17 -0
  54. package/dist/esm/v3/HStack/HStack.mjs.map +1 -0
  55. package/dist/esm/v3/Icon/Icon.mjs +49 -0
  56. package/dist/esm/v3/Icon/Icon.mjs.map +1 -0
  57. package/dist/esm/v3/Icon/Icon.module.scss.mjs +4 -0
  58. package/dist/esm/v3/Icon/Icon.module.scss.mjs.map +1 -0
  59. package/dist/esm/v3/SmoothCornersBox/SmoothCornersBox.mjs +51 -0
  60. package/dist/esm/v3/SmoothCornersBox/SmoothCornersBox.mjs.map +1 -0
  61. package/dist/esm/v3/SmoothCornersBox/SmoothCornersBox.module.scss.mjs +4 -0
  62. package/dist/esm/v3/SmoothCornersBox/SmoothCornersBox.module.scss.mjs.map +1 -0
  63. package/dist/esm/v3/Spinner/Spinner.mjs +43 -0
  64. package/dist/esm/v3/Spinner/Spinner.mjs.map +1 -0
  65. package/dist/esm/v3/Spinner/Spinner.module.scss.mjs +4 -0
  66. package/dist/esm/v3/Spinner/Spinner.module.scss.mjs.map +1 -0
  67. package/dist/esm/v3/Text/Text.mjs +58 -0
  68. package/dist/esm/v3/Text/Text.mjs.map +1 -0
  69. package/dist/esm/v3/Text/Text.module.scss.mjs +4 -0
  70. package/dist/esm/v3/Text/Text.module.scss.mjs.map +1 -0
  71. package/dist/esm/v3/VStack/VStack.mjs +17 -0
  72. package/dist/esm/v3/VStack/VStack.mjs.map +1 -0
  73. package/dist/esm/v3/index.mjs +9 -0
  74. package/dist/esm/v3/index.mjs.map +1 -0
  75. package/dist/types/types/beta-tokens.d.ts +4 -0
  76. package/dist/types/types/beta-tokens.d.ts.map +1 -1
  77. package/dist/types/types/props-helpers.d.ts +48 -1
  78. package/dist/types/types/props-helpers.d.ts.map +1 -1
  79. package/dist/types/types/props.d.ts +44 -0
  80. package/dist/types/types/props.d.ts.map +1 -1
  81. package/dist/types/v3/BaseStack/BaseStack.d.ts +6 -0
  82. package/dist/types/v3/BaseStack/BaseStack.d.ts.map +1 -0
  83. package/dist/types/v3/BaseStack/BaseStack.types.d.ts +45 -0
  84. package/dist/types/v3/BaseStack/BaseStack.types.d.ts.map +1 -0
  85. package/dist/types/v3/Box/Box.d.ts +19 -0
  86. package/dist/types/v3/Box/Box.d.ts.map +1 -0
  87. package/dist/types/v3/Box/Box.types.d.ts +12 -0
  88. package/dist/types/v3/Box/Box.types.d.ts.map +1 -0
  89. package/dist/types/v3/Box/index.d.ts +3 -0
  90. package/dist/types/v3/Box/index.d.ts.map +1 -0
  91. package/dist/types/v3/Divider/Divider.d.ts +13 -0
  92. package/dist/types/v3/Divider/Divider.d.ts.map +1 -0
  93. package/dist/types/v3/Divider/Divider.types.d.ts +27 -0
  94. package/dist/types/v3/Divider/Divider.types.d.ts.map +1 -0
  95. package/dist/types/v3/Divider/index.d.ts +3 -0
  96. package/dist/types/v3/Divider/index.d.ts.map +1 -0
  97. package/dist/types/v3/HStack/HStack.d.ts +7 -0
  98. package/dist/types/v3/HStack/HStack.d.ts.map +1 -0
  99. package/dist/types/v3/HStack/HStack.types.d.ts +2 -0
  100. package/dist/types/v3/HStack/HStack.types.d.ts.map +1 -0
  101. package/dist/types/v3/HStack/index.d.ts +3 -0
  102. package/dist/types/v3/HStack/index.d.ts.map +1 -0
  103. package/dist/types/v3/Icon/Icon.d.ts +19 -0
  104. package/dist/types/v3/Icon/Icon.d.ts.map +1 -0
  105. package/dist/types/v3/Icon/Icon.types.d.ts +21 -0
  106. package/dist/types/v3/Icon/Icon.types.d.ts.map +1 -0
  107. package/dist/types/v3/Icon/index.d.ts +3 -0
  108. package/dist/types/v3/Icon/index.d.ts.map +1 -0
  109. package/dist/types/v3/SmoothCornersBox/SmoothCornersBox.d.ts +15 -0
  110. package/dist/types/v3/SmoothCornersBox/SmoothCornersBox.d.ts.map +1 -0
  111. package/dist/types/v3/SmoothCornersBox/SmoothCornersBox.types.d.ts +61 -0
  112. package/dist/types/v3/SmoothCornersBox/SmoothCornersBox.types.d.ts.map +1 -0
  113. package/dist/types/v3/SmoothCornersBox/index.d.ts +3 -0
  114. package/dist/types/v3/SmoothCornersBox/index.d.ts.map +1 -0
  115. package/dist/types/v3/Spinner/Spinner.d.ts +15 -0
  116. package/dist/types/v3/Spinner/Spinner.d.ts.map +1 -0
  117. package/dist/types/v3/Spinner/Spinner.types.d.ts +5 -0
  118. package/dist/types/v3/Spinner/Spinner.types.d.ts.map +1 -0
  119. package/dist/types/v3/Spinner/index.d.ts +3 -0
  120. package/dist/types/v3/Spinner/index.d.ts.map +1 -0
  121. package/dist/types/v3/Text/Text.d.ts +16 -0
  122. package/dist/types/v3/Text/Text.d.ts.map +1 -0
  123. package/dist/types/v3/Text/Text.types.d.ts +44 -0
  124. package/dist/types/v3/Text/Text.types.d.ts.map +1 -0
  125. package/dist/types/v3/Text/index.d.ts +3 -0
  126. package/dist/types/v3/Text/index.d.ts.map +1 -0
  127. package/dist/types/v3/VStack/VStack.d.ts +7 -0
  128. package/dist/types/v3/VStack/VStack.d.ts.map +1 -0
  129. package/dist/types/v3/VStack/VStack.types.d.ts +2 -0
  130. package/dist/types/v3/VStack/VStack.types.d.ts.map +1 -0
  131. package/dist/types/v3/VStack/index.d.ts +3 -0
  132. package/dist/types/v3/VStack/index.d.ts.map +1 -0
  133. package/dist/types/v3/index.d.ts +9 -0
  134. package/dist/types/v3/index.d.ts.map +1 -0
  135. package/package.json +6 -1
  136. package/src/types/beta-tokens.ts +8 -0
  137. package/src/types/props-helpers.ts +22 -1
  138. package/src/types/props.ts +52 -0
  139. package/src/v3/BaseStack/BaseStack.module.scss +73 -0
  140. package/src/v3/BaseStack/BaseStack.test.tsx +83 -0
  141. package/src/v3/BaseStack/BaseStack.tsx +72 -0
  142. package/src/v3/BaseStack/BaseStack.types.ts +59 -0
  143. package/src/v3/Box/Box.module.scss +13 -0
  144. package/src/v3/Box/Box.stories.tsx +27 -0
  145. package/src/v3/Box/Box.test.tsx +57 -0
  146. package/src/v3/Box/Box.tsx +77 -0
  147. package/src/v3/Box/Box.types.ts +24 -0
  148. package/src/v3/Box/index.ts +2 -0
  149. package/src/v3/Divider/Divider.module.scss +52 -0
  150. package/src/v3/Divider/Divider.stories.tsx +95 -0
  151. package/src/v3/Divider/Divider.test.tsx +47 -0
  152. package/src/v3/Divider/Divider.tsx +57 -0
  153. package/src/v3/Divider/Divider.types.ts +32 -0
  154. package/src/v3/Divider/index.ts +2 -0
  155. package/src/v3/HStack/HStack.stories.tsx +58 -0
  156. package/src/v3/HStack/HStack.test.tsx +14 -0
  157. package/src/v3/HStack/HStack.tsx +21 -0
  158. package/src/v3/HStack/HStack.types.ts +1 -0
  159. package/src/v3/HStack/index.ts +2 -0
  160. package/src/v3/Icon/Icon.module.scss +20 -0
  161. package/src/v3/Icon/Icon.stories.tsx +173 -0
  162. package/src/v3/Icon/Icon.test.tsx +64 -0
  163. package/src/v3/Icon/Icon.tsx +67 -0
  164. package/src/v3/Icon/Icon.types.ts +32 -0
  165. package/src/v3/Icon/index.ts +2 -0
  166. package/src/v3/SmoothCornersBox/SmoothCornersBox.module.scss +48 -0
  167. package/src/v3/SmoothCornersBox/SmoothCornersBox.stories.tsx +41 -0
  168. package/src/v3/SmoothCornersBox/SmoothCornersBox.test.tsx +83 -0
  169. package/src/v3/SmoothCornersBox/SmoothCornersBox.tsx +84 -0
  170. package/src/v3/SmoothCornersBox/SmoothCornersBox.types.ts +69 -0
  171. package/src/v3/SmoothCornersBox/index.ts +2 -0
  172. package/src/v3/Spinner/Spinner.module.scss +58 -0
  173. package/src/v3/Spinner/Spinner.stories.tsx +28 -0
  174. package/src/v3/Spinner/Spinner.test.tsx +65 -0
  175. package/src/v3/Spinner/Spinner.tsx +61 -0
  176. package/src/v3/Spinner/Spinner.types.ts +12 -0
  177. package/src/v3/Spinner/index.ts +2 -0
  178. package/src/v3/Text/Text.module.scss +69 -0
  179. package/src/v3/Text/Text.stories.tsx +52 -0
  180. package/src/v3/Text/Text.test.tsx +84 -0
  181. package/src/v3/Text/Text.tsx +81 -0
  182. package/src/v3/Text/Text.types.ts +70 -0
  183. package/src/v3/Text/index.ts +2 -0
  184. package/src/v3/VStack/VStack.stories.tsx +58 -0
  185. package/src/v3/VStack/VStack.test.tsx +14 -0
  186. package/src/v3/VStack/VStack.tsx +21 -0
  187. package/src/v3/VStack/VStack.types.ts +1 -0
  188. package/src/v3/VStack/index.ts +2 -0
  189. package/src/v3/index.ts +10 -0
@@ -0,0 +1,173 @@
1
+ import { ChannelBtnFilledIcon, icons } from '@channel.io/bezier-icons'
2
+ import { type Meta, type StoryObj } from '@storybook/react'
3
+
4
+ import { type BetaSemanticColor } from '~/src/types/beta-tokens'
5
+ import { HStack } from '~/src/v3/HStack'
6
+ import { Text } from '~/src/v3/Text'
7
+ import { VStack } from '~/src/v3/VStack'
8
+
9
+ import { Icon } from './Icon'
10
+ import { type IconProps, type IconSize } from './Icon.types'
11
+
12
+ const SIZES: IconSize[] = ['10', '12', '16', '20', '24', '36', '44']
13
+
14
+ const COLORS: BetaSemanticColor[] = [
15
+ 'icon-neutral',
16
+ 'icon-neutral-heavier',
17
+ 'icon-accent-blue',
18
+ 'icon-accent-cobalt',
19
+ 'icon-accent-teal',
20
+ 'icon-accent-green',
21
+ 'icon-accent-olive',
22
+ 'icon-accent-yellow',
23
+ 'icon-accent-orange',
24
+ 'icon-accent-red',
25
+ 'icon-accent-pink',
26
+ 'icon-accent-purple',
27
+ 'icon-accent-navy',
28
+ ]
29
+
30
+ const meta: Meta<typeof Icon> = {
31
+ title: 'V3 components/Icon',
32
+ component: Icon,
33
+ args: {
34
+ source: 'channel-btn-filled' as unknown as IconProps['source'],
35
+ size: '24',
36
+ color: 'icon-neutral',
37
+ },
38
+ argTypes: {
39
+ source: {
40
+ control: 'select',
41
+ options: Object.keys(icons),
42
+ mapping: icons,
43
+ },
44
+ size: {
45
+ control: 'select',
46
+ options: SIZES,
47
+ table: {
48
+ defaultValue: { summary: '"24"' },
49
+ },
50
+ },
51
+ // `color` accepts any beta semantic color token (`V3ColorProps`), so it is
52
+ // left as a free-text control rather than a restricted option list.
53
+ color: {
54
+ control: 'text',
55
+ table: {
56
+ defaultValue: { summary: '"icon-neutral"' },
57
+ },
58
+ },
59
+ },
60
+ }
61
+
62
+ export default meta
63
+
64
+ type Story = StoryObj<typeof Icon>
65
+
66
+ /**
67
+ * Adjust `source` / `size` / `color` with the controls panel.
68
+ */
69
+ export const Primary: Story = {}
70
+
71
+ /**
72
+ * Each size (based on the `icon-neutral` color).
73
+ */
74
+ export const Sizes: Story = {
75
+ tags: ['!autodocs'],
76
+ parameters: { controls: { disable: true } },
77
+ render: () => (
78
+ <VStack spacing={12}>
79
+ {SIZES.map((size) => (
80
+ <HStack
81
+ key={size}
82
+ align="center"
83
+ spacing={24}
84
+ >
85
+ <Text
86
+ typo="12"
87
+ color="text-neutral"
88
+ style={{ width: 32 }}
89
+ >
90
+ {size}
91
+ </Text>
92
+ <Icon
93
+ source={ChannelBtnFilledIcon}
94
+ size={size}
95
+ color="icon-neutral"
96
+ />
97
+ </HStack>
98
+ ))}
99
+ </VStack>
100
+ ),
101
+ }
102
+
103
+ /**
104
+ * Each color (based on the `l` size).
105
+ */
106
+ export const Colors: Story = {
107
+ tags: ['!autodocs'],
108
+ parameters: { controls: { disable: true } },
109
+ render: () => (
110
+ <VStack spacing={12}>
111
+ {COLORS.map((color) => (
112
+ <HStack
113
+ key={color}
114
+ align="center"
115
+ spacing={24}
116
+ >
117
+ <Text
118
+ typo="12"
119
+ color="text-neutral"
120
+ style={{ width: 160 }}
121
+ >
122
+ {color}
123
+ </Text>
124
+ <Icon
125
+ source={ChannelBtnFilledIcon}
126
+ size="36"
127
+ color={color}
128
+ />
129
+ </HStack>
130
+ ))}
131
+ </VStack>
132
+ ),
133
+ }
134
+
135
+ /**
136
+ * Every icon available in `@channel.io/bezier-icons`.
137
+ */
138
+ export const AllIcons: Story = {
139
+ tags: ['!autodocs'],
140
+ parameters: { controls: { disable: true } },
141
+ render: () => (
142
+ <HStack
143
+ wrap
144
+ spacing={8}
145
+ >
146
+ {Object.entries(icons).map(([name, source]) => (
147
+ <VStack
148
+ key={name}
149
+ display="inline-flex"
150
+ align="center"
151
+ justify="center"
152
+ wrap
153
+ spacing={12}
154
+ width={120}
155
+ height={120}
156
+ >
157
+ <Icon
158
+ source={source}
159
+ size="24"
160
+ />
161
+ <Text
162
+ typo="12"
163
+ color="text-neutral"
164
+ align="center"
165
+ style={{ wordBreak: 'break-word' }}
166
+ >
167
+ {name}
168
+ </Text>
169
+ </VStack>
170
+ ))}
171
+ </HStack>
172
+ ),
173
+ }
@@ -0,0 +1,64 @@
1
+ import * as React from 'react'
2
+
3
+ import { AllIcon } from '@channel.io/bezier-icons'
4
+
5
+ import { colorTokenCssVar } from '~/src/utils/style'
6
+ import { render } from '~/src/utils/test'
7
+
8
+ import { Icon } from './Icon'
9
+ import { type IconProps } from './Icon.types'
10
+
11
+ import styles from './Icon.module.scss'
12
+
13
+ describe('Icon', () => {
14
+ const renderIcon = (props?: Partial<IconProps>) =>
15
+ render(
16
+ <Icon
17
+ source={AllIcon}
18
+ {...props}
19
+ />
20
+ )
21
+
22
+ it('should render with default style', () => {
23
+ const { container } = renderIcon()
24
+ const rendered = container.querySelector('svg')
25
+
26
+ expect(rendered).toHaveClass(styles.Icon)
27
+ expect(rendered).toHaveClass(styles['size-24'])
28
+ expect(rendered).toHaveStyle(
29
+ `--b-v3-icon-color: ${colorTokenCssVar('icon-neutral')}`
30
+ )
31
+ })
32
+
33
+ it('should forward ref', () => {
34
+ const ref = React.createRef<SVGSVGElement>()
35
+
36
+ render(
37
+ <Icon
38
+ ref={ref}
39
+ source={AllIcon}
40
+ />
41
+ )
42
+
43
+ expect(ref.current).toBeInTheDocument()
44
+ })
45
+
46
+ it('should receive color, size, margin, style, and class name', () => {
47
+ const { container } = renderIcon({
48
+ color: 'icon-neutral-heavier',
49
+ size: '44',
50
+ marginTop: 10,
51
+ style: { display: 'block' },
52
+ className: 'test-class',
53
+ })
54
+ const rendered = container.querySelector('svg')
55
+
56
+ expect(rendered).toHaveClass(styles['size-44'])
57
+ expect(rendered).toHaveClass('test-class')
58
+ expect(rendered).toHaveStyle(
59
+ `--b-v3-icon-color: ${colorTokenCssVar('icon-neutral-heavier')}`
60
+ )
61
+ expect(rendered).toHaveStyle('display: block')
62
+ expect(rendered).toHaveStyle('--b-margin-top: 10px')
63
+ })
64
+ })
@@ -0,0 +1,67 @@
1
+ 'use client'
2
+
3
+ import { forwardRef, memo } from 'react'
4
+ import * as React from 'react'
5
+
6
+ import classNames from 'classnames'
7
+
8
+ import { type BetaSemanticColor } from '~/src/types/beta-tokens'
9
+ import { getMarginStyles, splitByMarginProps } from '~/src/types/props-helpers'
10
+ import { colorTokenCssVar } from '~/src/utils/style'
11
+
12
+ import { type IconProps } from './Icon.types'
13
+
14
+ import styles from './Icon.module.scss'
15
+
16
+ const DEFAULT_ICON_COLOR = 'icon-neutral' satisfies BetaSemanticColor
17
+
18
+ /**
19
+ * `Icon` renders a Bezier icon as an SVG element.
20
+ * Inject an icon component from the `@channel.io/bezier-icons` package into the `source` prop.
21
+ * @example
22
+ *
23
+ * ```tsx
24
+ * import { HeartFilledIcon } from '@channel.io/bezier-icons'
25
+ *
26
+ * <Icon
27
+ * source={HeartFilledIcon}
28
+ * size="24"
29
+ * color="icon-neutral"
30
+ * />
31
+ * ```
32
+ */
33
+ export const Icon = memo(
34
+ forwardRef<SVGSVGElement, IconProps>(function Icon(props, forwardedRef) {
35
+ const [marginProps, marginRest] = splitByMarginProps(props)
36
+ const marginStyles = getMarginStyles(marginProps)
37
+
38
+ const {
39
+ className,
40
+ size = '24',
41
+ color = DEFAULT_ICON_COLOR,
42
+ source: SourceElement,
43
+ style,
44
+ ...rest
45
+ } = marginRest
46
+
47
+ return (
48
+ <SourceElement
49
+ ref={forwardedRef}
50
+ style={
51
+ {
52
+ '--b-v3-icon-color': colorTokenCssVar(color),
53
+ ...marginStyles.style,
54
+ ...style,
55
+ } as React.CSSProperties
56
+ }
57
+ className={classNames(
58
+ styles.Icon,
59
+ styles[`size-${size}`],
60
+ marginStyles.className,
61
+ className
62
+ )}
63
+ {...rest}
64
+ />
65
+ )
66
+ })
67
+ )
@@ -0,0 +1,32 @@
1
+ import { type BezierIcon } from '@channel.io/bezier-icons'
2
+
3
+ import {
4
+ type BezierComponentProps,
5
+ type SizeProps,
6
+ type V3ColorProps,
7
+ type V3MarginProps,
8
+ } from '~/src/types/props'
9
+
10
+ export type IconSize = '10' | '12' | '16' | '20' | '24' | '36' | '44'
11
+
12
+ interface IconOwnProps {
13
+ /**
14
+ * Controls which icon should be rendered.
15
+ * Inject the icon component from the `@channel.io/bezier-icons` package into this prop.
16
+ * @example
17
+ * ```tsx
18
+ * import { HeartFilledIcon } from '@channel.io/bezier-icons'
19
+ * import { Icon } from '@channel.io/bezier-react/v3'
20
+ *
21
+ * <Icon source={HeartFilledIcon} {...} />
22
+ * ```
23
+ */
24
+ source: BezierIcon
25
+ }
26
+
27
+ export interface IconProps
28
+ extends Omit<BezierComponentProps<'svg'>, keyof V3ColorProps>,
29
+ V3MarginProps,
30
+ SizeProps<IconSize>,
31
+ V3ColorProps,
32
+ IconOwnProps {}
@@ -0,0 +1,2 @@
1
+ export { Icon } from './Icon'
2
+ export type { IconProps, IconSize } from './Icon.types'
@@ -0,0 +1,48 @@
1
+ .SmoothCornersBox {
2
+ --b-smooth-corners-box-border-radius: 0;
3
+ --b-smooth-corners-box-shadow-offset-x: 0;
4
+ --b-smooth-corners-box-shadow-offset-y: 0;
5
+ --b-smooth-corners-box-shadow-blur-radius: 0px;
6
+ --b-smooth-corners-box-shadow-spread-radius: 0px;
7
+ --b-smooth-corners-box-shadow-color: transparent;
8
+ --b-smooth-corners-box-padding: 0px;
9
+ --b-smooth-corners-box-margin: 0px;
10
+ --b-smooth-corners-box-background-color: transparent;
11
+
12
+ box-sizing: content-box;
13
+ margin: var(--b-smooth-corners-box-margin);
14
+
15
+ background-color: var(--b-smooth-corners-box-background-color);
16
+ background-image: var(--b-smooth-corners-box-background-image);
17
+ background-size: cover;
18
+ border-radius: var(--b-smooth-corners-box-border-radius);
19
+ box-shadow: var(--b-smooth-corners-box-shadow-offset-x)
20
+ var(--b-smooth-corners-box-shadow-offset-y)
21
+ var(--b-smooth-corners-box-shadow-blur-radius)
22
+ var(--b-smooth-corners-box-shadow-spread-radius)
23
+ var(--b-smooth-corners-box-shadow-color);
24
+
25
+ &:where([data-state='enabled']) {
26
+ @supports (background: paint(smooth-corners)) {
27
+ --smooth-corners: var(--b-smooth-corners-box-border-radius);
28
+ --smooth-corners-shadow: var(--b-smooth-corners-box-shadow-offset-x),
29
+ var(--b-smooth-corners-box-shadow-offset-y),
30
+ var(--b-smooth-corners-box-shadow-blur-radius),
31
+ var(--b-smooth-corners-box-shadow-spread-radius),
32
+ var(--b-smooth-corners-box-shadow-color);
33
+ --smooth-corners-bg-color: var(--b-smooth-corners-box-background-color);
34
+ --smooth-corners-padding: var(--b-smooth-corners-box-padding);
35
+
36
+ margin: calc(
37
+ var(--b-smooth-corners-box-margin) +
38
+ (-1 * var(--b-smooth-corners-box-padding))
39
+ );
40
+ padding: var(--b-smooth-corners-box-padding);
41
+
42
+ background: paint(smooth-corners);
43
+ border-radius: 0;
44
+ border-image-source: var(--b-smooth-corners-box-background-image);
45
+ box-shadow: none;
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,41 @@
1
+ import { type Meta, type StoryFn, type StoryObj } from '@storybook/react'
2
+
3
+ import { SmoothCornersBox } from './SmoothCornersBox'
4
+ import { type SmoothCornersBoxProps } from './SmoothCornersBox.types'
5
+
6
+ const meta: Meta<typeof SmoothCornersBox> = {
7
+ title: 'V3 components/SmoothCornersBox',
8
+ component: SmoothCornersBox,
9
+ }
10
+
11
+ export default meta
12
+
13
+ const Template: StoryFn<SmoothCornersBoxProps> = ({
14
+ children,
15
+ ...otherSmoothCornersBoxProps
16
+ }) => (
17
+ <SmoothCornersBox
18
+ style={{ width: 200, height: 200 }}
19
+ {...otherSmoothCornersBoxProps}
20
+ >
21
+ {children}
22
+ </SmoothCornersBox>
23
+ )
24
+
25
+ export const Primary: StoryObj<SmoothCornersBoxProps> = {
26
+ render: Template,
27
+ args: {
28
+ disabled: false,
29
+ borderRadius: '42%',
30
+ shadow: {
31
+ offsetX: 0,
32
+ offsetY: 4,
33
+ blurRadius: 20,
34
+ spreadRadius: 0,
35
+ color: 'elevation-large',
36
+ },
37
+ margin: 0,
38
+ backgroundColor: 'fill-absolute-white',
39
+ backgroundImage: '',
40
+ },
41
+ }
@@ -0,0 +1,83 @@
1
+ import { render } from '~/src/utils/test'
2
+
3
+ import { SmoothCornersBox } from './SmoothCornersBox'
4
+ import { type SmoothCornersBoxProps } from './SmoothCornersBox.types'
5
+
6
+ import styles from './SmoothCornersBox.module.scss'
7
+
8
+ describe('SmoothCornersBox', () => {
9
+ const renderSmoothCornersBox = (
10
+ { children, ...rest }: SmoothCornersBoxProps = { borderRadius: 0 }
11
+ ) => render(<SmoothCornersBox {...rest}>{children}</SmoothCornersBox>)
12
+
13
+ it('should render with disabled state by default', () => {
14
+ const children = 'Hello, Channel!'
15
+ const { getByText } = renderSmoothCornersBox({ children, borderRadius: 10 })
16
+ const rendered = getByText(children)
17
+
18
+ expect(rendered).toHaveClass(styles.SmoothCornersBox)
19
+ expect(rendered).toHaveAttribute('data-state', 'disabled')
20
+ })
21
+
22
+ it('each style property must have the correct unit.', () => {
23
+ const children = 'Hello, Channel!'
24
+ const { getByText } = renderSmoothCornersBox({
25
+ children,
26
+ borderRadius: 10,
27
+ margin: 10,
28
+ shadow: {
29
+ offsetX: 10,
30
+ offsetY: 10,
31
+ blurRadius: 10,
32
+ spreadRadius: 10,
33
+ color: 'fill-neutral',
34
+ },
35
+ backgroundColor: 'fill-neutral',
36
+ backgroundImage: 'foo/bar',
37
+ className: 'test-class',
38
+ })
39
+
40
+ const rendered = getByText(children)
41
+ const computedStyle = window.getComputedStyle(rendered)
42
+
43
+ expect(rendered).toHaveClass('test-class')
44
+ expect(
45
+ computedStyle.getPropertyValue('--b-smooth-corners-box-border-radius')
46
+ ).toBe('10')
47
+ expect(
48
+ computedStyle.getPropertyValue('--b-smooth-corners-box-shadow-offset-x')
49
+ ).toBe('10px')
50
+ expect(
51
+ computedStyle.getPropertyValue('--b-smooth-corners-box-shadow-offset-y')
52
+ ).toBe('10px')
53
+ expect(
54
+ computedStyle.getPropertyValue(
55
+ '--b-smooth-corners-box-shadow-blur-radius'
56
+ )
57
+ ).toBe('10px')
58
+ expect(
59
+ computedStyle.getPropertyValue(
60
+ '--b-smooth-corners-box-shadow-spread-radius'
61
+ )
62
+ ).toBe('10px')
63
+ expect(
64
+ computedStyle.getPropertyValue('--b-smooth-corners-box-shadow-color')
65
+ ).toBe('var(--color-fill-neutral)')
66
+ expect(
67
+ computedStyle.getPropertyValue('--b-smooth-corners-box-padding')
68
+ ).toBe('20px')
69
+ expect(
70
+ computedStyle.getPropertyValue('--b-smooth-corners-box-margin')
71
+ ).toBe('10px')
72
+ expect(
73
+ computedStyle.getPropertyValue(
74
+ '--b-smooth-corners-box-background-color'
75
+ )
76
+ ).toBe('var(--color-fill-neutral)')
77
+ expect(
78
+ computedStyle.getPropertyValue(
79
+ '--b-smooth-corners-box-background-image'
80
+ )
81
+ ).toBe('url(foo/bar)')
82
+ })
83
+ })
@@ -0,0 +1,84 @@
1
+ 'use client'
2
+
3
+ import { forwardRef } from 'react'
4
+ import * as React from 'react'
5
+
6
+ import classNames from 'classnames'
7
+
8
+ import { colorTokenCssVar, cssUrl, px } from '~/src/utils/style'
9
+
10
+ import { FeatureType, useFeatureFlag } from '~/src/components/FeatureProvider'
11
+
12
+ import { type SmoothCornersBoxProps } from './SmoothCornersBox.types'
13
+
14
+ import styles from './SmoothCornersBox.module.scss'
15
+
16
+ /**
17
+ * `SmoothCornersBox` is a simple `div` element with smooth corners.
18
+ * It is available by enabling the `SmoothCornersFeature`.
19
+ * @example
20
+ *
21
+ * ```tsx
22
+ * <AppProvider features={[SmoothCornersFeature]}>
23
+ * <SmoothCornersBox />
24
+ * </AppProvider>
25
+ * ```
26
+ */
27
+ export const SmoothCornersBox = forwardRef<
28
+ HTMLDivElement,
29
+ SmoothCornersBoxProps
30
+ >(function SmoothCornersBox(
31
+ {
32
+ children,
33
+ style,
34
+ className,
35
+ disabled,
36
+ borderRadius,
37
+ margin,
38
+ shadow,
39
+ backgroundColor,
40
+ backgroundImage,
41
+ ...rest
42
+ },
43
+ forwardedRef
44
+ ) {
45
+ const shadowBlurRadius = shadow?.blurRadius ?? 0
46
+ const shadowSpreadRadius = shadow?.spreadRadius ?? 0
47
+
48
+ return (
49
+ <div
50
+ {...rest}
51
+ ref={forwardedRef}
52
+ style={
53
+ {
54
+ ...style,
55
+ '--b-smooth-corners-box-border-radius': borderRadius,
56
+ '--b-smooth-corners-box-shadow-offset-x': px(shadow?.offsetX),
57
+ '--b-smooth-corners-box-shadow-offset-y': px(shadow?.offsetY),
58
+ '--b-smooth-corners-box-shadow-blur-radius': `${shadowBlurRadius}px`,
59
+ '--b-smooth-corners-box-shadow-spread-radius': `${shadowSpreadRadius}px`,
60
+ '--b-smooth-corners-box-shadow-color': colorTokenCssVar(
61
+ shadow?.color
62
+ ),
63
+ /**
64
+ * NOTE: Calculate in javascript because it cannot access calculated values via CSS calc() in the paint worklet.
65
+ * @see {@link ~/src/features/SmoothCorners/smoothCornersScript.ts}
66
+ */
67
+ '--b-smooth-corners-box-padding': `${Math.max(shadowBlurRadius, shadowSpreadRadius) * 2}px`,
68
+ '--b-smooth-corners-box-margin': `${margin ?? 0}px`,
69
+ '--b-smooth-corners-box-background-color':
70
+ colorTokenCssVar(backgroundColor),
71
+ '--b-smooth-corners-box-background-image': cssUrl(backgroundImage),
72
+ } as React.CSSProperties
73
+ }
74
+ className={classNames(styles.SmoothCornersBox, className)}
75
+ data-state={
76
+ useFeatureFlag(FeatureType.SmoothCorners) && !disabled
77
+ ? 'enabled'
78
+ : 'disabled'
79
+ }
80
+ >
81
+ {children}
82
+ </div>
83
+ )
84
+ })
@@ -0,0 +1,69 @@
1
+ import { type BetaSemanticColor } from '~/src/types/beta-tokens'
2
+ import type {
3
+ BezierComponentProps,
4
+ ChildrenProps,
5
+ DisableProps,
6
+ } from '~/src/types/props'
7
+
8
+ /**
9
+ * NOTE: The `inset` property is not currently supported.
10
+ */
11
+ interface BoxShadow {
12
+ /**
13
+ * The value specifies the horizontal distance. Negative values place the shadow to the left of the element.
14
+ * @default 0
15
+ */
16
+ offsetX?: number
17
+ /**
18
+ * The value specifies the vertical distance. Negative values place the shadow above the element.
19
+ * @default 0
20
+ */
21
+ offsetY?: number
22
+ /**
23
+ * The larger this value, the bigger the blur, so the shadow becomes bigger and lighter. Negative values are not allowed.
24
+ * @default 0
25
+ */
26
+ blurRadius?: number
27
+ /**
28
+ * Positive values will cause the shadow to expand and grow bigger, negative values will cause the shadow to shrink.
29
+ * @default 0
30
+ */
31
+ spreadRadius?: number
32
+ /**
33
+ * The color of the shadow.
34
+ * @default transparent
35
+ */
36
+ color?: BetaSemanticColor
37
+ }
38
+
39
+ interface SmoothCornersBoxOwnProps {
40
+ /**
41
+ * Rounds the corners of an element's outer border edge.
42
+ * @default 0
43
+ */
44
+ borderRadius: number | string
45
+ /**
46
+ * Shadow effects around an element's frame.
47
+ */
48
+ shadow?: BoxShadow
49
+ /**
50
+ * The margin area on all four sides of an element.
51
+ * @default 0
52
+ */
53
+ margin?: number
54
+ /**
55
+ * The background color of an element.
56
+ * @default 'transparent'
57
+ */
58
+ backgroundColor?: BetaSemanticColor
59
+ /**
60
+ * The background image url of an element.
61
+ */
62
+ backgroundImage?: string
63
+ }
64
+
65
+ export interface SmoothCornersBoxProps
66
+ extends BezierComponentProps<'div'>,
67
+ ChildrenProps,
68
+ DisableProps,
69
+ SmoothCornersBoxOwnProps {}
@@ -0,0 +1,2 @@
1
+ export { SmoothCornersBox } from './SmoothCornersBox'
2
+ export type { SmoothCornersBoxProps } from './SmoothCornersBox.types'