@charcoal-ui/react 2.0.0-alpha.2 → 2.0.0-alpha.20

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 (34) hide show
  1. package/dist/components/Icon/index.d.ts +12 -0
  2. package/dist/components/Icon/index.d.ts.map +1 -0
  3. package/dist/components/Icon/index.story.d.ts +24 -0
  4. package/dist/components/Icon/index.story.d.ts.map +1 -0
  5. package/dist/components/Modal/ModalPlumbing.d.ts +5 -0
  6. package/dist/components/Modal/ModalPlumbing.d.ts.map +1 -0
  7. package/dist/components/Modal/index.d.ts +16 -0
  8. package/dist/components/Modal/index.d.ts.map +1 -0
  9. package/dist/components/Modal/index.story.d.ts +33 -0
  10. package/dist/components/Modal/index.story.d.ts.map +1 -0
  11. package/dist/components/TextField/index.d.ts +3 -3
  12. package/dist/components/TextField/index.d.ts.map +1 -1
  13. package/dist/components/TextField/index.story.d.ts +1 -0
  14. package/dist/components/TextField/index.story.d.ts.map +1 -1
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.modern.js +35 -31
  20. package/dist/index.modern.js.map +1 -1
  21. package/dist/index.module.js +1 -1
  22. package/dist/index.module.js.map +1 -1
  23. package/package.json +9 -6
  24. package/src/components/FieldLabel/index.tsx +1 -1
  25. package/src/components/Icon/index.story.tsx +29 -0
  26. package/src/components/Icon/index.tsx +33 -0
  27. package/src/components/Modal/ModalPlumbing.tsx +47 -0
  28. package/src/components/Modal/index.story.tsx +195 -0
  29. package/src/components/Modal/index.tsx +226 -0
  30. package/src/components/TextField/index.story.tsx +29 -8
  31. package/src/components/TextField/index.tsx +48 -30
  32. package/src/components/a11y.test.tsx +11 -0
  33. package/src/index.ts +1 -0
  34. package/src/styled.ts +1 -1
@@ -0,0 +1,195 @@
1
+ import React from 'react'
2
+ import { Story } from '../../_lib/compat'
3
+ import Modal, { ModalDismissButton, Props } from '.'
4
+ import { OverlayProvider } from '@react-aria/overlays'
5
+ import { useOverlayTriggerState } from 'react-stately'
6
+ import Button from '../Button'
7
+ import {
8
+ ModalAlign,
9
+ ModalBody,
10
+ ModalButtons,
11
+ ModalHeader,
12
+ } from './ModalPlumbing'
13
+ import styled from 'styled-components'
14
+ import { theme } from '../../styled'
15
+ import TextField from '../TextField'
16
+
17
+ export default {
18
+ title: 'Modal',
19
+ component: Modal,
20
+ args: {
21
+ title: 'Title',
22
+ },
23
+ argTypes: {
24
+ size: {
25
+ options: ['S', 'M', 'L'],
26
+ control: {
27
+ type: 'inline-radio',
28
+ },
29
+ },
30
+ bottomSheet: {
31
+ options: ['full', 'true', 'false'],
32
+ mapping: { full: 'full', true: true, false: false },
33
+ control: {
34
+ type: 'inline-radio',
35
+ },
36
+ },
37
+ },
38
+ }
39
+
40
+ const DefaultStory = (args: Props) => {
41
+ const state = useOverlayTriggerState({})
42
+ return (
43
+ // Application must be wrapped in an OverlayProvider so that it can be
44
+ // hidden from screen readers when a modal opens.
45
+ <OverlayProvider>
46
+ <Button onClick={() => state.open()}>Open Modal</Button>
47
+
48
+ <Modal
49
+ isOpen={state.isOpen}
50
+ onClose={() => state.close()}
51
+ isDismissable
52
+ {...args}
53
+ >
54
+ <ModalHeader />
55
+ <ModalBody>
56
+ <ModalVStack>
57
+ <StyledModalText>
58
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod
59
+ placeat tenetur, necessitatibus laudantium cumque exercitationem
60
+ provident. Quaerat iure enim, eveniet dolores earum odio quo
61
+ possimus fugiat aspernatur, numquam, commodi repellat.
62
+ </StyledModalText>
63
+ <ModalAlign>
64
+ <TextField
65
+ showLabel
66
+ label="Name"
67
+ placeholder="Nagisa"
68
+ ></TextField>
69
+ </ModalAlign>
70
+ <ModalAlign>
71
+ <TextField
72
+ showLabel
73
+ label="Country"
74
+ placeholder="Tokyo"
75
+ ></TextField>
76
+ </ModalAlign>
77
+ </ModalVStack>
78
+ <ModalButtons>
79
+ <Button variant="Primary" onClick={() => state.close()} fixed>
80
+ Apply
81
+ </Button>
82
+ <Button onClick={() => state.close()} fixed>
83
+ Cancel
84
+ </Button>
85
+ </ModalButtons>
86
+ </ModalBody>
87
+ </Modal>
88
+ </OverlayProvider>
89
+ )
90
+ }
91
+
92
+ const ModalVStack = styled.div`
93
+ display: grid;
94
+ gap: 24px;
95
+ `
96
+
97
+ const StyledModalText = styled(ModalAlign)`
98
+ ${theme((o) => [o.font.text2, o.typography(14)])}
99
+ `
100
+
101
+ export const Default: Story<Props> = DefaultStory.bind({})
102
+
103
+ const FullBottomSheetStory = (args: Props) => {
104
+ const state = useOverlayTriggerState({})
105
+ return (
106
+ // Application must be wrapped in an OverlayProvider so that it can be
107
+ // hidden from screen readers when a modal opens.
108
+ <OverlayProvider>
109
+ <Button onClick={() => state.open()}>Open Modal</Button>
110
+
111
+ <Modal
112
+ isOpen={state.isOpen}
113
+ onClose={() => state.close()}
114
+ isDismissable
115
+ bottomSheet="full"
116
+ {...args}
117
+ >
118
+ <ModalHeader />
119
+ <ModalBody>
120
+ <ModalVStack>
121
+ <StyledModalText>
122
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod
123
+ placeat tenetur, necessitatibus laudantium cumque exercitationem
124
+ provident. Quaerat iure enim, eveniet dolores earum odio quo
125
+ possimus fugiat aspernatur, numquam, commodi repellat.
126
+ </StyledModalText>
127
+ <ModalAlign>
128
+ <TextField
129
+ showLabel
130
+ label="Name"
131
+ placeholder="Nagisa"
132
+ ></TextField>
133
+ </ModalAlign>
134
+ <ModalAlign>
135
+ <TextField
136
+ showLabel
137
+ label="Country"
138
+ placeholder="Tokyo"
139
+ ></TextField>
140
+ </ModalAlign>
141
+ </ModalVStack>
142
+ <ModalButtons>
143
+ <Button variant="Primary" onClick={() => state.close()} fixed>
144
+ Apply
145
+ </Button>
146
+ <Button onClick={() => state.close()} fixed>
147
+ Cancel
148
+ </Button>
149
+ </ModalButtons>
150
+ </ModalBody>
151
+ </Modal>
152
+ </OverlayProvider>
153
+ )
154
+ }
155
+
156
+ export const FullBottomSheet: Story<Props> = FullBottomSheetStory.bind({})
157
+
158
+ const BottomSheetStory = (args: Props) => {
159
+ const state = useOverlayTriggerState({})
160
+ return (
161
+ // Application must be wrapped in an OverlayProvider so that it can be
162
+ // hidden from screen readers when a modal opens.
163
+ <OverlayProvider>
164
+ <Button onClick={() => state.open()}>Open Modal</Button>
165
+
166
+ <Modal
167
+ isOpen={state.isOpen}
168
+ onClose={() => state.close()}
169
+ bottomSheet
170
+ isDismissable
171
+ {...args}
172
+ >
173
+ <ModalHeader />
174
+ <ModalBody>
175
+ <ModalVStack>
176
+ <StyledModalText>
177
+ Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod
178
+ placeat tenetur, necessitatibus laudantium cumque exercitationem
179
+ provident. Quaerat iure enim, eveniet dolores earum odio quo
180
+ possimus fugiat aspernatur, numquam, commodi repellat.
181
+ </StyledModalText>
182
+ </ModalVStack>
183
+ <ModalButtons>
184
+ <Button variant="Danger" onClick={() => state.close()} fixed>
185
+ 削除する
186
+ </Button>
187
+ <ModalDismissButton>キャンセル</ModalDismissButton>
188
+ </ModalButtons>
189
+ </ModalBody>
190
+ </Modal>
191
+ </OverlayProvider>
192
+ )
193
+ }
194
+
195
+ export const BottomSheet: Story<Props> = BottomSheetStory.bind({})
@@ -0,0 +1,226 @@
1
+ import React, { useContext, useRef } from 'react'
2
+ import {
3
+ OverlayContainer,
4
+ OverlayProps,
5
+ useModal,
6
+ useOverlay,
7
+ usePreventScroll,
8
+ } from '@react-aria/overlays'
9
+ import styled, { css, useTheme } from 'styled-components'
10
+ import { theme } from '../../styled'
11
+ import { FocusScope } from '@react-aria/focus'
12
+ import { useDialog } from '@react-aria/dialog'
13
+ import { AriaDialogProps } from '@react-types/dialog'
14
+ import { columnSystem, COLUMN_UNIT, GUTTER_UNIT } from '@charcoal-ui/foundation'
15
+ import { unreachable } from '../../_lib'
16
+ import { maxWidth } from '@charcoal-ui/utils'
17
+ import { useMedia } from '@charcoal-ui/styled'
18
+ import { animated, useTransition, easings } from 'react-spring'
19
+ import Button, { ButtonProps } from '../Button'
20
+ import IconButton from '../IconButton'
21
+
22
+ export type Props = OverlayProps &
23
+ AriaDialogProps & {
24
+ children: React.ReactNode
25
+ zIndex?: number
26
+ title: string
27
+ size?: 'S' | 'M' | 'L'
28
+ bottomSheet?: boolean | 'full'
29
+
30
+ // NOTICE: デフォルト値を与えてはならない
31
+ // (たとえば document.body をデフォルト値にすると SSR できなくなる)
32
+ portalContainer?: HTMLElement
33
+ }
34
+
35
+ const DEFAULT_Z_INDEX = 10
36
+
37
+ export default function Modal({
38
+ children,
39
+ zIndex = DEFAULT_Z_INDEX,
40
+ portalContainer,
41
+ ...props
42
+ }: Props) {
43
+ const {
44
+ title,
45
+ size = 'M',
46
+ bottomSheet = false,
47
+ isDismissable,
48
+ onClose,
49
+ isOpen = false,
50
+ } = props
51
+
52
+ const ref = useRef<HTMLDivElement>(null)
53
+ const { overlayProps, underlayProps } = useOverlay(props, ref)
54
+
55
+ usePreventScroll()
56
+ const { modalProps } = useModal()
57
+
58
+ const { dialogProps, titleProps } = useDialog(props, ref)
59
+
60
+ const theme = useTheme()
61
+ const isMobile = useMedia(maxWidth(theme.breakpoint.screen1)) ?? false
62
+ const transitionEnabled = isMobile && bottomSheet !== false
63
+ const transition = useTransition(isOpen, {
64
+ from: {
65
+ transform: 'translateY(100%)',
66
+ backgroundColor: 'rgba(0, 0, 0, 0)',
67
+ },
68
+ enter: {
69
+ transform: 'translateY(0%)',
70
+ backgroundColor: 'rgba(0, 0, 0, 0.4)',
71
+ },
72
+ leave: {
73
+ transform: 'translateY(100%)',
74
+ backgroundColor: 'rgba(0, 0, 0, 0)',
75
+ },
76
+ config: transitionEnabled
77
+ ? { duration: 400, easing: easings.easeOutQuart }
78
+ : { duration: 0 },
79
+ })
80
+ const showDismiss = !isMobile || bottomSheet !== true
81
+
82
+ return transition(
83
+ ({ backgroundColor, transform }, item) =>
84
+ item && (
85
+ <OverlayContainer portalContainer={portalContainer}>
86
+ <ModalBackground
87
+ zIndex={zIndex}
88
+ {...underlayProps}
89
+ style={transitionEnabled ? { backgroundColor } : {}}
90
+ >
91
+ <FocusScope contain restoreFocus autoFocus>
92
+ <ModalDialog
93
+ ref={ref}
94
+ {...overlayProps}
95
+ {...modalProps}
96
+ {...dialogProps}
97
+ style={transitionEnabled ? { transform } : {}}
98
+ size={size}
99
+ bottomSheet={bottomSheet}
100
+ >
101
+ <ModalContext.Provider
102
+ value={{ titleProps, title, close: onClose, showDismiss }}
103
+ >
104
+ {children}
105
+ {isDismissable === true && (
106
+ <ModalCrossButton
107
+ size="S"
108
+ icon="24/Close"
109
+ onClick={onClose}
110
+ />
111
+ )}
112
+ </ModalContext.Provider>
113
+ </ModalDialog>
114
+ </FocusScope>
115
+ </ModalBackground>
116
+ </OverlayContainer>
117
+ )
118
+ )
119
+ }
120
+
121
+ const ModalContext = React.createContext<{
122
+ titleProps: React.HTMLAttributes<HTMLElement>
123
+ title: string
124
+ close?: () => void
125
+ showDismiss: boolean
126
+ }>({
127
+ titleProps: {},
128
+ title: '',
129
+ close: undefined,
130
+ showDismiss: true,
131
+ })
132
+
133
+ const ModalBackground = animated(styled.div<{ zIndex: number }>`
134
+ z-index: ${({ zIndex }) => zIndex};
135
+ position: fixed;
136
+ top: 0;
137
+ left: 0;
138
+ width: 100%;
139
+ height: 100%;
140
+
141
+ ${theme((o) => [o.bg.surface4])}
142
+ `)
143
+
144
+ const ModalDialog = animated(styled.div<{
145
+ size: 'S' | 'M' | 'L'
146
+ bottomSheet: boolean | 'full'
147
+ }>`
148
+ position: absolute;
149
+ top: 50%;
150
+ left: 50%;
151
+ transform: translate(-50%, -50%);
152
+ width: ${(p) =>
153
+ p.size === 'S'
154
+ ? columnSystem(3, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
155
+ : p.size === 'M'
156
+ ? columnSystem(4, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
157
+ : // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
158
+ p.size === 'L'
159
+ ? columnSystem(6, COLUMN_UNIT, GUTTER_UNIT) + GUTTER_UNIT * 2
160
+ : unreachable(p.size)}px;
161
+
162
+ ${theme((o) => [o.bg.background1, o.borderRadius(24)])}
163
+
164
+ @media ${({ theme }) => maxWidth(theme.breakpoint.screen1)} {
165
+ ${(p) =>
166
+ p.bottomSheet === 'full'
167
+ ? css`
168
+ top: auto;
169
+ bottom: 0;
170
+ left: 0;
171
+ transform: none;
172
+ border-radius: 0;
173
+ width: 100%;
174
+ height: 100%;
175
+ `
176
+ : p.bottomSheet
177
+ ? css`
178
+ top: auto;
179
+ bottom: 0;
180
+ left: 0;
181
+ transform: none;
182
+ border-radius: 0;
183
+ width: 100%;
184
+ `
185
+ : css`
186
+ width: calc(100% - 48px);
187
+ `}
188
+ }
189
+ `)
190
+
191
+ const ModalCrossButton = styled(IconButton)`
192
+ position: absolute;
193
+ top: 8px;
194
+ right: 8px;
195
+
196
+ ${theme((o) => [o.font.text3.hover.press])}
197
+ `
198
+
199
+ export function ModalTitle(props: React.HTMLAttributes<HTMLHeadingElement>) {
200
+ const { titleProps, title } = useContext(ModalContext)
201
+ return (
202
+ <ModalHeading {...titleProps} {...props}>
203
+ {title}
204
+ </ModalHeading>
205
+ )
206
+ }
207
+
208
+ const ModalHeading = styled.h3`
209
+ margin: 0;
210
+ font-weight: inherit;
211
+ font-size: inherit;
212
+ `
213
+
214
+ export function ModalDismissButton({ children, ...props }: ButtonProps) {
215
+ const { close, showDismiss } = useContext(ModalContext)
216
+
217
+ if (!showDismiss) {
218
+ return null
219
+ }
220
+
221
+ return (
222
+ <Button {...props} onClick={close} fixed>
223
+ {children}
224
+ </Button>
225
+ )
226
+ }
@@ -1,6 +1,6 @@
1
1
  import { action } from '@storybook/addon-actions'
2
2
  import React from 'react'
3
- import { css } from 'styled-components'
3
+ import styled from 'styled-components'
4
4
  import { Story } from '../../_lib/compat'
5
5
  import Clickable from '../Clickable'
6
6
  import TextField, {
@@ -9,6 +9,7 @@ import TextField, {
9
9
  TextFieldProps,
10
10
  } from '.'
11
11
  import { px } from '@charcoal-ui/utils'
12
+ import IconButton from '../IconButton'
12
13
 
13
14
  export default {
14
15
  title: 'TextField',
@@ -24,13 +25,13 @@ export default {
24
25
  },
25
26
  }
26
27
 
28
+ const Container = styled.div`
29
+ display: grid;
30
+ gap: ${({ theme }) => px(theme.spacing[24])};
31
+ `
32
+
27
33
  const Template: Story<Partial<TextFieldProps>> = (args) => (
28
- <div
29
- css={css`
30
- display: grid;
31
- gap: ${({ theme }) => px(theme.spacing[24])};
32
- `}
33
- >
34
+ <Container>
34
35
  <TextField
35
36
  label="Label"
36
37
  requiredText="*必須"
@@ -57,7 +58,7 @@ const Template: Story<Partial<TextFieldProps>> = (args) => (
57
58
  {...(args as Partial<MultiLineTextFieldProps>)}
58
59
  multiline
59
60
  />
60
- </div>
61
+ </Container>
61
62
  )
62
63
 
63
64
  export const Default = Template.bind({})
@@ -91,3 +92,23 @@ export const AutoHeight: Story<Partial<MultiLineTextFieldProps>> = (args) => (
91
92
  AutoHeight.args = {
92
93
  autoHeight: true,
93
94
  }
95
+
96
+ export const PrefixIcon: Story<Partial<SingleLineTextFieldProps>> = (args) => (
97
+ <TextField
98
+ label="Label"
99
+ placeholder="Icon prefix"
100
+ prefix={
101
+ <PrefixIconWrap>
102
+ <pixiv-icon name="16/Search" />
103
+ </PrefixIconWrap>
104
+ }
105
+ suffix={<IconButton variant="Overlay" icon={'16/Remove'} size="XS" />}
106
+ {...args}
107
+ />
108
+ )
109
+
110
+ const PrefixIconWrap = styled.div`
111
+ color: ${({ theme }) => theme.color.text4};
112
+ margin-top: 2px;
113
+ margin-right: 4px;
114
+ `
@@ -1,9 +1,15 @@
1
1
  import { useTextField } from '@react-aria/textfield'
2
2
  import { useVisuallyHidden } from '@react-aria/visually-hidden'
3
- import React, { useCallback, useEffect, useRef, useState } from 'react'
3
+ import React, {
4
+ ReactNode,
5
+ useCallback,
6
+ useEffect,
7
+ useRef,
8
+ useState,
9
+ } from 'react'
4
10
  import styled, { css } from 'styled-components'
5
11
  import FieldLabel, { FieldLabelProps } from '../FieldLabel'
6
- import createTheme from '@charcoal-ui/styled'
12
+ import { createTheme } from '@charcoal-ui/styled'
7
13
 
8
14
  const theme = createTheme(styled)
9
15
 
@@ -32,8 +38,8 @@ export interface SingleLineTextFieldProps extends TextFieldBaseProps {
32
38
  readonly multiline?: false
33
39
  readonly rows?: never
34
40
  readonly type?: string
35
- readonly prefix?: string
36
- readonly suffix?: string
41
+ readonly prefix?: ReactNode
42
+ readonly suffix?: ReactNode
37
43
  }
38
44
 
39
45
  export interface MultiLineTextFieldProps extends TextFieldBaseProps {
@@ -94,8 +100,8 @@ const SingleLineTextField = React.forwardRef<
94
100
  invalid = false,
95
101
  assistiveText,
96
102
  maxLength,
97
- prefix = '',
98
- suffix = '',
103
+ prefix = null,
104
+ suffix = null,
99
105
  } = props
100
106
 
101
107
  const { visuallyHiddenProps } = useVisuallyHidden()
@@ -184,9 +190,9 @@ const SingleLineTextField = React.forwardRef<
184
190
  />
185
191
  <SuffixContainer ref={suffixRef}>
186
192
  <Affix>{suffix}</Affix>
187
- {showCount && maxLength && (
193
+ {showCount && (
188
194
  <SingleLineCounter>
189
- {count}/{maxLength}
195
+ {maxLength !== undefined ? `${count}/${maxLength}` : count}
190
196
  </SingleLineCounter>
191
197
  )}
192
198
  </SuffixContainer>
@@ -290,16 +296,23 @@ const MultiLineTextField = React.forwardRef<
290
296
  required={required}
291
297
  subLabel={subLabel}
292
298
  {...labelProps}
293
- {...(showLabel ? visuallyHiddenProps : {})}
299
+ {...(!showLabel ? visuallyHiddenProps : {})}
294
300
  />
295
- <StyledTextareaContainer rows={rows}>
301
+ <StyledTextareaContainer
302
+ invalid={invalid}
303
+ rows={showCount ? rows + 1 : rows}
304
+ >
296
305
  <StyledTextarea
297
306
  ref={mergeRefs(textareaRef, forwardRef, ariaRef)}
298
- invalid={invalid}
299
307
  rows={rows}
308
+ noBottomPadding={showCount}
300
309
  {...inputProps}
301
310
  />
302
- {showCount && <MultiLineCounter>{count}</MultiLineCounter>}
311
+ {showCount && (
312
+ <MultiLineCounter>
313
+ {maxLength !== undefined ? `${count}/${maxLength}` : count}
314
+ </MultiLineCounter>
315
+ )}
303
316
  </StyledTextareaContainer>
304
317
  {assistiveText != null && assistiveText.length !== 0 && (
305
318
  <AssistiveText
@@ -370,8 +383,6 @@ const StyledInput = styled.input<{
370
383
  height: calc(100% / 0.875);
371
384
  font-size: calc(14px / 0.875);
372
385
  line-height: calc(22px / 0.875);
373
- padding-top: calc(9px / 0.875);
374
- padding-bottom: calc(9px / 0.875);
375
386
  padding-left: calc((8px + ${(p) => p.extraLeftPadding}px) / 0.875);
376
387
  padding-right: calc((8px + ${(p) => p.extraRightPadding}px) / 0.875);
377
388
  border-radius: calc(4px / 0.875);
@@ -392,21 +403,35 @@ const StyledInput = styled.input<{
392
403
  }
393
404
  `
394
405
 
395
- const StyledTextareaContainer = styled.div<{ rows: number }>`
396
- display: grid;
406
+ const StyledTextareaContainer = styled.div<{ rows: number; invalid: boolean }>`
397
407
  position: relative;
408
+ overflow: hidden;
409
+ padding: 0 8px;
410
+
411
+ ${(p) =>
412
+ theme((o) => [
413
+ o.bg.surface3.hover,
414
+ p.invalid && o.outline.assertive,
415
+ o.font.text2,
416
+ o.borderRadius(4),
417
+ ])}
418
+
419
+ &:focus-within {
420
+ ${(p) =>
421
+ theme((o) => (p.invalid ? o.outline.assertive : o.outline.default))}
422
+ }
398
423
 
399
424
  ${({ rows }) => css`
400
- max-height: calc(22px * ${rows} + 18px);
425
+ height: calc(22px * ${rows} + 18px);
401
426
  `};
402
427
  `
403
428
 
404
- const StyledTextarea = styled.textarea<{ invalid: boolean }>`
429
+ const StyledTextarea = styled.textarea<{ noBottomPadding: boolean }>`
405
430
  border: none;
406
- box-sizing: border-box;
407
431
  outline: none;
408
432
  resize: none;
409
433
  font-family: inherit;
434
+ color: inherit;
410
435
 
411
436
  /* Prevent zooming for iOS Safari */
412
437
  transform-origin: top left;
@@ -414,23 +439,16 @@ const StyledTextarea = styled.textarea<{ invalid: boolean }>`
414
439
  width: calc(100% / 0.875);
415
440
  font-size: calc(14px / 0.875);
416
441
  line-height: calc(22px / 0.875);
417
- padding: calc(9px / 0.875) calc(8px / 0.875);
418
- border-radius: calc(4px / 0.875);
442
+ padding: calc(9px / 0.875) 0 ${(p) => (p.noBottomPadding ? 0 : '')};
419
443
 
420
- ${({ rows }) => css`
421
- height: calc(22px / 0.875 * ${rows} + 18px / 0.875);
444
+ ${({ rows = 1 }) => css`
445
+ height: calc(22px / 0.875 * ${rows});
422
446
  `};
423
447
 
424
448
  /* Display box-shadow for iOS Safari */
425
449
  appearance: none;
426
450
 
427
- ${(p) =>
428
- theme((o) => [
429
- o.bg.surface3.hover,
430
- o.outline.default.focus,
431
- p.invalid && o.outline.assertive,
432
- o.font.text2,
433
- ])}
451
+ background: none;
434
452
 
435
453
  &::placeholder {
436
454
  ${theme((o) => o.font.text3)}
@@ -63,6 +63,17 @@ beforeEach(() => {
63
63
  return null
64
64
  },
65
65
  }))
66
+
67
+ global.matchMedia = jest.fn().mockImplementation(() => ({
68
+ matches: true,
69
+ media: '(max-width: 600px)',
70
+ addEventListener() {
71
+ // Do Nothing
72
+ },
73
+ removeEventListener() {
74
+ // Do Nothing
75
+ },
76
+ }))
66
77
  })
67
78
 
68
79
  describe.each(themes)('using %s theme', (_name, theme) => {
package/src/index.ts CHANGED
@@ -30,3 +30,4 @@ export {
30
30
  default as TextField,
31
31
  type TextFieldProps,
32
32
  } from './components/TextField'
33
+ export { default as Icon, type IconProps } from './components/Icon'
package/src/styled.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import styled from 'styled-components'
2
- import createTheme from '@charcoal-ui/styled'
2
+ import { createTheme } from '@charcoal-ui/styled'
3
3
  export const theme = createTheme(styled)