@planningcenter/tapestry-react 2.6.0-rc.0 → 2.6.0-rc.10

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 (77) hide show
  1. package/dist/cjs/Button/Button.js +8 -1
  2. package/dist/cjs/Button/Button.test.js +51 -8
  3. package/dist/cjs/DataTable/components/BodyRow.js +2 -2
  4. package/dist/cjs/DataTable/components/CheckboxCell.js +1 -1
  5. package/dist/cjs/DataTable/hooks/useCollapsibleRows.js +2 -2
  6. package/dist/cjs/DragDrop/DragDrop.js +7 -0
  7. package/dist/cjs/Dropdown/Dropdown.js +5 -3
  8. package/dist/cjs/Dropdown/Link.js +2 -4
  9. package/dist/cjs/Input/InputLabel.js +40 -63
  10. package/dist/cjs/Modal/Modal.js +18 -8
  11. package/dist/cjs/Popover/Popover.js +10 -2
  12. package/dist/cjs/Portal/Portal.js +11 -1
  13. package/dist/cjs/Scrim/Scrim.js +16 -4
  14. package/dist/cjs/ThemeProvider/ThemeProvider.js +24 -6
  15. package/dist/cjs/ThemeProvider/styles.js +1 -4
  16. package/dist/cjs/TimeField/TimeField.js +1 -1
  17. package/dist/cjs/Tooltip/Tooltip.js +17 -17
  18. package/dist/cjs/index.d.js +70 -25
  19. package/dist/cjs/system/split-styles.js +2 -2
  20. package/dist/esm/Button/Button.js +8 -1
  21. package/dist/esm/Button/Button.test.js +67 -9
  22. package/dist/esm/DataTable/components/BodyRow.js +2 -2
  23. package/dist/esm/DataTable/components/CheckboxCell.js +1 -1
  24. package/dist/esm/DataTable/hooks/useCollapsibleRows.js +1 -1
  25. package/dist/esm/DragDrop/DragDrop.js +5 -0
  26. package/dist/esm/Dropdown/Dropdown.js +6 -4
  27. package/dist/esm/Dropdown/Link.js +1 -2
  28. package/dist/esm/Input/InputLabel.js +40 -63
  29. package/dist/esm/Modal/Modal.js +16 -8
  30. package/dist/esm/Popover/Popover.js +8 -2
  31. package/dist/esm/Portal/Portal.js +10 -0
  32. package/dist/esm/Scrim/Scrim.js +15 -4
  33. package/dist/esm/ThemeProvider/ThemeProvider.js +21 -6
  34. package/dist/esm/ThemeProvider/styles.js +1 -4
  35. package/dist/esm/TimeField/TimeField.js +1 -1
  36. package/dist/esm/Tooltip/Tooltip.js +18 -18
  37. package/dist/esm/index.d.js +39 -6
  38. package/dist/esm/system/split-styles.js +1 -1
  39. package/dist/types/Button/Button.d.ts +4 -0
  40. package/dist/types/Divider/Divider.d.ts +2 -2
  41. package/dist/types/Portal/Portal.d.ts +3 -0
  42. package/dist/types/Spinner/Spinner.d.ts +3 -1
  43. package/dist/types/ThemeProvider/ThemeProvider.d.ts +4 -2
  44. package/dist/types/index.d.ts +74 -5
  45. package/package.json +3 -3
  46. package/src/Button/Button.test.tsx +30 -0
  47. package/src/Button/Button.tsx +14 -1
  48. package/src/DataTable/DataTable.js +1 -1
  49. package/src/DataTable/components/BodyRow.js +1 -1
  50. package/src/DataTable/components/BodyRows.js +4 -1
  51. package/src/DataTable/components/CheckboxCell.js +1 -2
  52. package/src/DataTable/hooks/useCollapsibleRows.js +1 -1
  53. package/src/Divider/Divider.tsx +2 -2
  54. package/src/DragDrop/DragDrop.js +5 -0
  55. package/src/Dropdown/Dropdown.js +7 -4
  56. package/src/Dropdown/Dropdown.mdx +3 -3
  57. package/src/Dropdown/Link.js +1 -7
  58. package/src/Icon/Icon.mdx +45 -47
  59. package/src/Input/InputLabel.js +39 -36
  60. package/src/Input/InputLabel.mdx +1 -0
  61. package/src/Modal/Modal.js +16 -6
  62. package/src/Modal/Modal.mdx +2 -1
  63. package/src/Popover/Popover.mdx +1 -0
  64. package/src/Popover/Popover.tsx +8 -2
  65. package/src/Portal/Portal.tsx +14 -0
  66. package/src/RangeSlider/RangeSlider.mdx +10 -12
  67. package/src/Scrim/Scrim.mdx +1 -0
  68. package/src/Scrim/Scrim.tsx +11 -6
  69. package/src/Sidebar/Sidebar.mdx +0 -1
  70. package/src/Spinner/Spinner.tsx +2 -1
  71. package/src/ThemeProvider/ThemeProvider.tsx +22 -10
  72. package/src/ThemeProvider/styles.ts +23 -12
  73. package/src/TimeField/TimeField.js +1 -1
  74. package/src/Tooltip/Tooltip.js +20 -18
  75. package/src/index.d.ts +74 -5
  76. package/src/system/split-styles.js +4 -2
  77. package/src/.DS_Store +0 -0
@@ -2,12 +2,42 @@ import React from 'react'
2
2
  import { render, fireEvent } from '@testing-library/react'
3
3
  import { Button } from './Button'
4
4
 
5
+ it(`should render as <button> with type="button" by default`, () => {
6
+ const { container } = render(<Button />)
7
+ const button = container.querySelector('button')
8
+ expect(button.getAttribute('type')).toEqual('button')
9
+ })
10
+
11
+ it(`should render as <button> with type="submit"`, () => {
12
+ const { container } = render(<Button type='submit' />)
13
+ const button = container.querySelector('button')
14
+ expect(button.getAttribute('type')).toEqual('submit')
15
+ })
16
+
5
17
  it(`should render title`, () => {
6
18
  const title = 'Hello'
7
19
  const { getByText } = render(<Button title={title} />)
8
20
  getByText(title)
9
21
  })
10
22
 
23
+ it(`should render <a> without a type if "to" is provided`, () => {
24
+ const { container } = render(<Button to='#' />)
25
+ const button = container.querySelector('a')
26
+ expect(button.getAttribute('type')).toBeNull()
27
+ })
28
+
29
+ it(`should render <a> without a type if "href" is provided`, () => {
30
+ const { container } = render(<Button href='#' />)
31
+ const button = container.querySelector('a')
32
+ expect(button.getAttribute('type')).toBeNull()
33
+ })
34
+
35
+ it(`should render <a> with "href" if "to" is specifed`, () => {
36
+ const { container } = render(<Button to='#' />)
37
+ const button = container.querySelector('a')
38
+ expect(button.getAttribute('href')).toEqual('#')
39
+ })
40
+
11
41
  it(`should render href and external link values`, () => {
12
42
  const title = 'Hello'
13
43
  const { getByText } = render(
@@ -86,6 +86,11 @@ type ButtonProps = {
86
86
  */
87
87
  to?: string
88
88
 
89
+ /**
90
+ * Where the browser should navigate to when pressed. If you need any element here other than `<a>`, remember to use the `as` prop.
91
+ */
92
+ href?: string
93
+
89
94
  /**
90
95
  * Wraps button in a [`<Tooltip />`](./tooltip). Accepts any valid Tooltip props.
91
96
  */
@@ -287,6 +292,14 @@ export function Button({
287
292
  restProps['href'] = to
288
293
  }
289
294
 
295
+ // remove `type` if either `to` or `href` is specified
296
+ if (to || (restProps as any).href) {
297
+ buttonProps = {
298
+ ...buttonProps,
299
+ type: null,
300
+ }
301
+ }
302
+
290
303
  // apply stroke defaults and higher z-index when hovering to show outline in group properly
291
304
  if (variant === 'outline') {
292
305
  buttonProps = {
@@ -342,7 +355,7 @@ export function Button({
342
355
  }
343
356
 
344
357
  if (
345
- type &&
358
+ buttonProps.type &&
346
359
  (restProps as any).as &&
347
360
  (restProps as any).as !== "button"
348
361
  ) {
@@ -242,7 +242,7 @@ export type Props = {
242
242
  onColumnSort?: Function,
243
243
 
244
244
  /**
245
- * `(rowData: any, rowIndex: number) => void`
245
+ * `(rowData: any, rowIndex: number, event: React.SyntheticEvent) => void`
246
246
  *
247
247
  * Callback when a row has been clicked.
248
248
  */
@@ -19,7 +19,7 @@ function BodyRow({
19
19
  } = useKeyboardShortcuts(keyboardShortcuts, { rowData, rowIndex })
20
20
  const props = {
21
21
  ref: innerRef,
22
- onClick: onRowClick && (() => onRowClick(rowData, rowIndex)),
22
+ onClick: (event) => onRowClick && onRowClick(rowData, rowIndex, event),
23
23
  onMouseEnter: bindKeyboardShortcuts,
24
24
  onMouseLeave: unbindKeyboardShortcuts,
25
25
  style: {
@@ -38,7 +38,10 @@ function BodyRows({
38
38
  key={columnIndex}
39
39
  ref={getColumnRef(`${columnIndex}.${rowIndex + 1}`)} // offset by 1 to account for header row
40
40
  role="cell"
41
- className={`tapestry-react-reset ${css([...cellVariantStyles, column.css])}`}
41
+ className={`tapestry-react-reset ${css([
42
+ ...cellVariantStyles,
43
+ column.css,
44
+ ])}`}
42
45
  >
43
46
  {getCell(column.cell, {
44
47
  columnIndex,
@@ -2,7 +2,6 @@ import React, { useCallback } from 'react'
2
2
 
3
3
  import Icon from '../../Icon'
4
4
 
5
-
6
5
  const iconPaths = {
7
6
  fill: 'tapestry.checkbox0',
8
7
  minus: 'tapestry.checkbox1',
@@ -99,7 +98,7 @@ function CheckboxCell({
99
98
  />
100
99
  <Icon.Path
101
100
  name={iconPaths.minus}
102
- className="tapestry-react-reset tapestry-react-Checkbox-Minus"
101
+ className="tapestry-react-reset tapestry-react-Checkbox-Fill"
103
102
  />
104
103
  <Icon.Path
105
104
  name={iconPaths.checked}
@@ -1,6 +1,6 @@
1
1
  import { createContext, useCallback, useContext, useEffect } from 'react'
2
2
  import useConstant from '../../hooks/useConstant'
3
- import create from 'zustand'
3
+ import { create } from 'zustand'
4
4
 
5
5
  import { range } from '../../utils'
6
6
 
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
 
3
3
  import Box from '../Box'
4
- import { BoxProps, ColorProp } from '../index'
4
+ import { BoxProps, ColorProp, StyleProps } from '../index'
5
5
 
6
6
  export type DividerProps = {
7
7
  /**
@@ -23,7 +23,7 @@ export type DividerProps = {
23
23
  * The size of the line in pixels
24
24
  */
25
25
  size?: number
26
- }
26
+ } & StyleProps
27
27
 
28
28
  /**
29
29
  * Used to break up content
@@ -9,6 +9,8 @@ import React, {
9
9
  import { createPortal } from 'react-dom'
10
10
  import * as RBDND from '@planningcenter/react-beautiful-dnd'
11
11
  import mitt from 'mitt'
12
+ import { getThemeDataAttribute } from '../ThemeProvider/styles'
13
+ import { useThemeValue } from '../system'
12
14
 
13
15
  import StackView from '../StackView'
14
16
 
@@ -33,9 +35,12 @@ function useDragDrop() {
33
35
 
34
36
  function Provider({ onDragStart, onDragEnd, ...props }) {
35
37
  const hasParentDragDrop = useDragDrop()
38
+ const themeId = useThemeValue('id')
36
39
  if (typeof document !== 'undefined' && portalNode === null) {
37
40
  portalNode = document.createElement('div')
38
41
  portalNode.id = 'rbd-portal'
42
+ portalNode.className = 'tapestry-react-reset'
43
+ portalNode.setAttribute(getThemeDataAttribute(themeId), 'true')
39
44
  document.body.appendChild(portalNode)
40
45
  }
41
46
  useEffect(() => {
@@ -9,7 +9,7 @@ import Popover from '../Popover'
9
9
  import { cloneChildren, generateId } from '../utils'
10
10
 
11
11
  import Item, { ITEM_DISPLAY_NAME } from './Item'
12
- import Link, { LINK_DISPLAY_NAME, LINK_DATA } from './Link'
12
+ import Link, { LINK_DISPLAY_NAME } from './Link'
13
13
 
14
14
  type Props = {
15
15
  children?: React.ReactNode,
@@ -125,7 +125,7 @@ class Dropdown extends Component<Props> {
125
125
  this.closePopover()
126
126
  this.popover.focusAnchor()
127
127
  }
128
- if (data === LINK_DATA) {
128
+ if (node.tagName === 'A' && event.type !== 'click') {
129
129
  node.click()
130
130
  } else if (this.props.onSelect) {
131
131
  this.props.onSelect(data)
@@ -215,18 +215,21 @@ class Dropdown extends Component<Props> {
215
215
  'aria-haspopup': true,
216
216
  'aria-expanded': isPopoverOpen,
217
217
  [arrowIconOnly ? 'icon' : 'iconRight']: {
218
- name: isPopoverOpen ? 'general.upCaret' : 'general.downCaret',
218
+ name: isPopoverOpen
219
+ ? 'general.upCaret'
220
+ : 'general.downCaret',
219
221
  size: 'sm',
220
222
  },
221
223
  title: arrowIconOnly ? 'arrow down' : restProps.title,
222
224
  tabIndex: 0,
223
225
  cursor: 'pointer',
224
226
  onBlur: requestBlur,
225
- onClick: () => {
227
+ onClick: (event) => {
226
228
  this.togglePopover()
227
229
  if (!isPopoverOpen) {
228
230
  this.popover.focusAnchor()
229
231
  }
232
+ onClick && onClick(event)
230
233
  },
231
234
  onKeyDown: (event) => {
232
235
  anchorProps.onKeyDown(event)
@@ -26,10 +26,10 @@ render(
26
26
  render(
27
27
  <Dropdown title="Links" size="sm" variant="outline">
28
28
  <Dropdown.Link to="http://planning.center" external>
29
- Planning Center
29
+ Planning Center (external)
30
30
  </Dropdown.Link>
31
- <Dropdown.Link to="https://reactjs.org" external>
32
- React Docs
31
+ <Dropdown.Link to="/button">
32
+ Tapestry React Button
33
33
  </Dropdown.Link>
34
34
  </Dropdown>
35
35
  )
@@ -4,7 +4,6 @@ import { ItemListItem } from '../ItemList'
4
4
  import Menu from '../Menu'
5
5
 
6
6
  export const LINK_DISPLAY_NAME = 'Dropdown.Link'
7
- export const LINK_DATA = 'link'
8
7
 
9
8
  class Link extends Component {
10
9
  // Graphql wasn't picking up the correct displayName when this was
@@ -17,12 +16,7 @@ class Link extends Component {
17
16
  restProps.target = '_blank'
18
17
  }
19
18
  return (
20
- <ItemListItem
21
- data={LINK_DATA}
22
- text={text}
23
- disabled={disabled}
24
- index={index}
25
- >
19
+ <ItemListItem data="link" text={text} disabled={disabled} index={index}>
26
20
  {({ id, highlight, highlighted, clearHighlight, select }) => (
27
21
  <Menu.Item
28
22
  as="a"
package/src/Icon/Icon.mdx CHANGED
@@ -29,35 +29,35 @@ An "icon set" groups product-specific icon paths together. By default, `tapestry
29
29
  Additional icon sets can be imported from [libraries like `@planningcenter/icons`](https://github.com/planningcenter/icons) and exported through your app's [ThemeProvider](/theming#themeprovider). (You can import all exports [by using an asterisk](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Import_an_entire_modules_contents).)
30
30
 
31
31
  ```jsx
32
+ {/* import icon sets in local theme definition */}
32
33
  import * as calendar from '@planningcenter/icons/paths/calendar'
33
34
  import * as giving from '@planningcenter/icons/paths/giving'
34
35
  import * as people from '@planningcenter/icons/paths/people'
35
36
 
36
37
  const theme = {
38
+ ...themeOptions,
37
39
  icons: {
38
40
  calendar,
39
41
  giving,
40
- people
42
+ people,
43
+ ...designSystem.defaultTheme.icons
41
44
  },
42
45
  }
43
46
 
44
- <ThemeProvider theme={theme}>
45
- <Icon name="calendar.reservationBlock" />
46
- <Icon name="giving.clockCircleO" />
47
- <Icon name="people.photoOutline" />
48
- </ThemeProvider>
47
+ {/* prefix Icon instance in component */}
48
+ <Icon name="calendar.reservationBlock" />
49
+ <Icon name="giving.clockCircleO" />
50
+ <Icon name="people.photoOutline" />
49
51
  ```
50
52
 
51
53
  ```jsx live
52
54
  render(() => {
53
55
  return (
54
- <ThemeProvider theme={{ icons }}>
55
- <StackView axis="horizontal" spacing={2}>
56
- <Icon name="calendar.reservationBlock" size="xl" />
57
- <Icon name="giving.clockCircleO" size="xl" />
58
- <Icon name="people.photoOutline" size="xl" />
59
- </StackView>
60
- </ThemeProvider>
56
+ <StackView axis="horizontal" spacing={2}>
57
+ <Icon name="calendar.reservationBlock" size="xl" />
58
+ <Icon name="giving.clockCircleO" size="xl" />
59
+ <Icon name="people.photoOutline" size="xl" />
60
+ </StackView>
61
61
  )
62
62
  })
63
63
  ```
@@ -72,42 +72,40 @@ render(() => {
72
72
  const [appName, setAppName] = React.useState('general')
73
73
 
74
74
  return (
75
- <ThemeProvider theme={{ icons }}>
76
- <StackView grow={1}>
77
- <StackView axis="horizontal" spacing={2}>
78
- <Select
79
- basis={26}
80
- emptyValue="Choose icon set"
81
- onChange={(event) => setAppName(event.value)}
82
- defaultValue={appName}
83
- >
84
- {appNames.map((appName) => (
85
- <Select.Option key={appName} value={appName}>
86
- {appName}
87
- </Select.Option>
88
- ))}
89
- </Select>
90
- <Input
91
- grow={1}
92
- autoFocus
93
- renderLeft={<Icon name="general.search" />}
94
- placeholder="Search by icon name"
95
- value={value}
96
- onChange={(e) => setValue(e.target.value)}
97
- />
98
- </StackView>
99
- <TileView minCellWidth={16} spacing={4} margin={4}>
100
- {matchSorter(Object.keys(icons[appName]), value).map((iconName) => (
101
- <StackView key={iconName} alignment="center" spacing={1}>
102
- <Icon key={iconName} name={`${appName}.${iconName}`} size="xl" />
103
- <Text fontSize={5} color="foregroundSecondary">
104
- {iconName}
105
- </Text>
106
- </StackView>
75
+ <StackView grow={1}>
76
+ <StackView axis="horizontal" spacing={2}>
77
+ <Select
78
+ basis={26}
79
+ emptyValue="Choose icon set"
80
+ onChange={(event) => setAppName(event.value)}
81
+ defaultValue={appName}
82
+ >
83
+ {appNames.map((appName) => (
84
+ <Select.Option key={appName} value={appName}>
85
+ {appName}
86
+ </Select.Option>
107
87
  ))}
108
- </TileView>
88
+ </Select>
89
+ <Input
90
+ grow={1}
91
+ autoFocus
92
+ renderLeft={<Icon name="general.search" />}
93
+ placeholder="Search by icon name"
94
+ value={value}
95
+ onChange={(e) => setValue(e.target.value)}
96
+ />
109
97
  </StackView>
110
- </ThemeProvider>
98
+ <TileView minCellWidth={16} spacing={4} margin={4}>
99
+ {matchSorter(Object.keys(icons[appName]), value).map((iconName) => (
100
+ <StackView key={iconName} alignment="center" spacing={1}>
101
+ <Icon key={iconName} name={`${appName}.${iconName}`} size="xl" />
102
+ <Text fontSize={5} color="foregroundSecondary">
103
+ {iconName}
104
+ </Text>
105
+ </StackView>
106
+ ))}
107
+ </TileView>
108
+ </StackView>
111
109
  )
112
110
  })
113
111
  ```
@@ -1,8 +1,9 @@
1
1
  // @flow
2
- import React, { Component } from 'react'
2
+ import React, { useCallback, useRef, useEffect } from 'react'
3
3
 
4
4
  import { getColor } from '../system'
5
5
  import Text from '../Text'
6
+ import { useThemeProps } from '../system'
6
7
 
7
8
  import { inputs, inputLabels } from './utils'
8
9
 
@@ -10,54 +11,56 @@ export type InputLabelProps = {
10
11
  /**
11
12
  * The `id` of the input to control. Compatible with all Tapestry-React form components.
12
13
  */
13
- controls: string,
14
+ controls?: string,
14
15
 
15
16
  /**
16
17
  * The current state of the label. Should match corresponding `Input`'s state prop.
17
18
  */
18
- state: 'warning' | 'error' | 'success',
19
+ state?: 'warning' | 'error' | 'success',
19
20
  }
20
21
 
21
- class InputLabel extends Component<InputLabelProps> {
22
- componentDidMount() {
23
- this.input = inputs[this.props.controls]
24
- inputLabels[this.props.controls] = true
25
- }
22
+ function InputLabel({ controls, state, ...restProps }: InputLabelProps) {
23
+ const { ...themeProps } = useThemeProps('inputLabel', restProps)
26
24
 
27
- componentWillUnmount() {
28
- delete inputLabels[this.props.controls]
29
- }
25
+ const input = useRef(null)
30
26
 
31
- focusInput = () => {
32
- this.input && this.input.focus()
27
+ if (controls) {
28
+ themeProps.id = `${controls}-label`
33
29
  }
34
-
35
- handleMouseOver = () => {
36
- this.input && this.input.setState({ isHovered: true })
30
+ if (state) {
31
+ themeProps.color = getColor(state)
37
32
  }
38
33
 
39
- handleMouseOut = () => {
40
- this.input && this.input.setState({ isHovered: false })
41
- }
34
+ useEffect(() => {
35
+ input.current = inputs[controls]
36
+ inputLabels[controls] = true
42
37
 
43
- render() {
44
- const { controls, state, ...restProps } = this.props
45
- if (controls) {
46
- restProps.id = `${controls}-label`
47
- }
48
- if (state) {
49
- restProps.color = getColor(state)
38
+ return () => {
39
+ delete inputLabels[controls]
50
40
  }
51
- return (
52
- <Text
53
- as="label"
54
- onMouseOver={this.handleMouseOver}
55
- onMouseOut={this.handleMouseOut}
56
- onClick={this.focusInput}
57
- {...restProps}
58
- />
59
- )
60
- }
41
+ }, [])
42
+
43
+ const focusInput = useCallback(() => {
44
+ input.current && input.current.focus()
45
+ }, [input])
46
+
47
+ const handleMouseOver = useCallback(() => {
48
+ input.current && input.current.setState({ isHovered: true })
49
+ }, [input])
50
+
51
+ const handleMouseOut = useCallback(() => {
52
+ input.current && input.current.setState({ isHovered: false })
53
+ }, [input])
54
+
55
+ return (
56
+ <Text
57
+ as="label"
58
+ onMouseOver={handleMouseOver}
59
+ onMouseOut={handleMouseOut}
60
+ onClick={focusInput}
61
+ {...themeProps}
62
+ />
63
+ )
61
64
  }
62
65
 
63
66
  InputLabel.displayName = 'Input.InputLabel'
@@ -4,6 +4,7 @@ category: Forms
4
4
  summary: Provides accessibility as well as usability improvements for mouse users by allowing the user to click the <InputLabel/> component to focus the respective control. This mimics the browsers native label tag, but allows use with custom components like <Select/>.
5
5
  propsSummary: Accepts [Text](/text) props.
6
6
  parent: Input
7
+ themeKey: inputLabel
7
8
  ---
8
9
 
9
10
  ```jsx live
@@ -5,6 +5,7 @@ import Box from '../Box'
5
5
  import Scrim from '../Scrim'
6
6
  import { useDocumentEvent } from '../hooks'
7
7
  import { trapFocus } from '../utils'
8
+ import { useThemeProps } from '../system'
8
9
 
9
10
  export type ModalProps = {
10
11
  children?: any,
@@ -23,6 +24,11 @@ export type ModalProps = {
23
24
  * Determines whether the modal is open or not.
24
25
  */
25
26
  open: boolean,
27
+
28
+ /**
29
+ * Props passed to the internal [`<Scrim/>`](/scrim) component.
30
+ */
31
+ scrimProps?: object,
26
32
  }
27
33
 
28
34
  function Modal({
@@ -32,6 +38,7 @@ function Modal({
32
38
  open,
33
39
  ...restProps
34
40
  }: ModalProps) {
41
+ const { scrimProps = {}, ...themeProps } = useThemeProps('modal', restProps)
35
42
  const modalRef = useRef(null)
36
43
 
37
44
  useLayoutEffect(() => {
@@ -58,16 +65,19 @@ function Modal({
58
65
  onRequestClose()
59
66
  }
60
67
  }}
68
+ {...scrimProps}
61
69
  >
62
70
  <Box
71
+ backgroundColor="surface"
72
+ boxShadow="0 0 20px rgba(0, 0, 0, 0.15)"
63
73
  innerRef={modalRef}
64
- width="100%"
65
- maxWidth={60}
66
- padding={2}
67
74
  margin={4}
68
- backgroundColor="surface"
69
- radius={3}
70
- {...restProps}
75
+ maxWidth={60}
76
+ padding={4}
77
+ paddingBottom={3}
78
+ radius={8}
79
+ width="100%"
80
+ {...themeProps}
71
81
  >
72
82
  {children}
73
83
  </Box>
@@ -2,6 +2,7 @@
2
2
  title: Modal
3
3
  category: Overlays
4
4
  propsSummary: Accepts [Box](/box) props.
5
+ themeKey: modal
5
6
  ---
6
7
 
7
8
  ```jsx live
@@ -16,7 +17,7 @@ function MyModal(props) {
16
17
  return () => setLazyComponent(null)
17
18
  }, [props.open])
18
19
  return (
19
- <Modal id="modal" closeOnOutsideClick height={200} {...props}>
20
+ <Modal id="modal" closeOnOutsideClick height={25} {...props}>
20
21
  <StackView spacing={2}>
21
22
  {lazyComponent || <Text>Loading lazy component...</Text>}
22
23
  <Select
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: Popover
3
3
  category: Overlays
4
+ themeKey: popover
4
5
  ---
5
6
 
6
7
  ```jsx live
@@ -7,6 +7,8 @@ import { lockScroll } from '../utils'
7
7
 
8
8
  import rewireTabOrder from './rewireTabOrder'
9
9
  import { getFixedParent, getModifiers } from './utils'
10
+ import { useThemeProps } from '../system'
11
+ import { useTheme } from '@emotion/react'
10
12
 
11
13
  type anchorCallbackProps = {
12
14
  innerRef(node: HTMLElement): void
@@ -116,6 +118,10 @@ export const Popover = React.forwardRef(
116
118
  }: PopoverProps,
117
119
  ref
118
120
  ) => {
121
+ const { zIndex = 10000, ...themeProps } = useThemeProps(
122
+ 'popover',
123
+ restProps
124
+ )
119
125
  const anchorRef = React.useRef(null)
120
126
  const popperRef = React.useRef(null)
121
127
  const unlockScroll = React.useRef(null)
@@ -253,8 +259,8 @@ export const Popover = React.forwardRef(
253
259
  }
254
260
  },
255
261
  // ideally this should be pulled out to something like ThemeProvider for predictable z indices
256
- style: { zIndex: 10000 },
257
- ...restProps,
262
+ style: { zIndex },
263
+ ...themeProps,
258
264
  })
259
265
  : null}
260
266
  </Portal>
@@ -1,5 +1,8 @@
1
1
  import * as React from 'react'
2
2
  import { createPortal } from 'react-dom'
3
+ import { getThemeDataAttribute } from '../ThemeProvider/styles'
4
+ import { ThemeContext } from '@emotion/react'
5
+ import { Theme } from '../index'
3
6
 
4
7
  export type PortalProps = {
5
8
  children: any
@@ -27,6 +30,9 @@ class Portal extends React.Component<PortalProps> {
27
30
  renderTo: null,
28
31
  }
29
32
 
33
+ static contextType = ThemeContext
34
+ context: React.ContextType<React.Context<Theme>>
35
+
30
36
  _portalNode: HTMLElement = null
31
37
  _needsUpdate = false
32
38
  _updated = false
@@ -105,6 +111,14 @@ class Portal extends React.Component<PortalProps> {
105
111
  Object.keys(restProps).forEach((key) => {
106
112
  this._portalNode.setAttribute(key, restProps[key])
107
113
  })
114
+
115
+ this._portalNode.classList.add('tapestry-react-reset')
116
+
117
+ this._portalNode.setAttribute(
118
+ getThemeDataAttribute(this.context.id),
119
+ 'true'
120
+ )
121
+
108
122
  if (style) {
109
123
  Object.keys(style).forEach((key) => {
110
124
  this._portalNode.style[key] = style[key]