@primer/components 31.2.0-rc.5ccefd7d → 31.2.0-rc.6afeaa37

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 (143) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/browser.esm.js +622 -620
  3. package/dist/browser.esm.js.map +1 -1
  4. package/dist/browser.umd.js +163 -161
  5. package/dist/browser.umd.js.map +1 -1
  6. package/docs/content/ActionList2.mdx +354 -0
  7. package/docs/content/getting-started.md +1 -1
  8. package/docs/src/@primer/gatsby-theme-doctocat/components/hero.js +1 -3
  9. package/docs/src/@primer/gatsby-theme-doctocat/components/live-preview-wrapper.js +1 -1
  10. package/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js +17 -0
  11. package/lib/ActionList2/Description.d.ts +12 -0
  12. package/lib/ActionList2/Description.js +53 -0
  13. package/lib/ActionList2/Divider.d.ts +5 -0
  14. package/lib/ActionList2/Divider.js +35 -0
  15. package/lib/ActionList2/Group.d.ts +11 -0
  16. package/lib/ActionList2/Group.js +57 -0
  17. package/lib/ActionList2/Header.d.ts +26 -0
  18. package/lib/ActionList2/Header.js +55 -0
  19. package/lib/ActionList2/Item.d.ts +63 -0
  20. package/lib/ActionList2/Item.js +244 -0
  21. package/lib/ActionList2/LinkItem.d.ts +17 -0
  22. package/lib/ActionList2/LinkItem.js +57 -0
  23. package/lib/ActionList2/List.d.ts +26 -0
  24. package/lib/ActionList2/List.js +59 -0
  25. package/lib/ActionList2/Selection.d.ts +5 -0
  26. package/lib/ActionList2/Selection.js +70 -0
  27. package/lib/ActionList2/Visuals.d.ts +9 -0
  28. package/lib/ActionList2/Visuals.js +90 -0
  29. package/lib/ActionList2/index.d.ts +36 -0
  30. package/lib/ActionList2/index.js +47 -0
  31. package/lib/Overlay.js +3 -1
  32. package/lib/Portal/Portal.js +3 -2
  33. package/lib/_TextInputWrapper.js +2 -2
  34. package/lib/__tests__/ActionList2.test.d.ts +1 -0
  35. package/lib/__tests__/ActionList2.test.js +53 -0
  36. package/lib/__tests__/AnchoredOverlay.test.js +4 -2
  37. package/lib/__tests__/TextInputWithTokens.test.js +1 -10
  38. package/lib/__tests__/utils/createSlots.test.d.ts +1 -0
  39. package/lib/__tests__/utils/createSlots.test.js +75 -0
  40. package/lib/hooks/useAnchoredPosition.js +3 -2
  41. package/lib/hooks/useCombinedRefs.d.ts +2 -2
  42. package/lib/hooks/useCombinedRefs.js +4 -6
  43. package/lib/hooks/useResizeObserver.js +2 -2
  44. package/lib/stories/ActionList2.stories.js +907 -0
  45. package/lib/stories/TextInput.stories.js +144 -0
  46. package/lib/stories/Token.stories.js +19 -2
  47. package/lib/sx.d.ts +2 -0
  48. package/lib/sx.js +8 -0
  49. package/lib/theme-preval.js +81 -2
  50. package/lib/unreleased.d.ts +7 -0
  51. package/lib/unreleased.js +18 -0
  52. package/lib/utils/create-slots.d.ts +17 -0
  53. package/lib/utils/create-slots.js +105 -0
  54. package/lib/utils/testing.d.ts +14 -1
  55. package/lib/utils/use-force-update.d.ts +1 -0
  56. package/lib/utils/use-force-update.js +19 -0
  57. package/lib/utils/useIsomorphicLayoutEffect.d.ts +3 -0
  58. package/lib/utils/useIsomorphicLayoutEffect.js +12 -0
  59. package/lib-esm/ActionList2/Description.d.ts +12 -0
  60. package/lib-esm/ActionList2/Description.js +37 -0
  61. package/lib-esm/ActionList2/Divider.d.ts +5 -0
  62. package/lib-esm/ActionList2/Divider.js +23 -0
  63. package/lib-esm/ActionList2/Group.d.ts +11 -0
  64. package/lib-esm/ActionList2/Group.js +40 -0
  65. package/lib-esm/ActionList2/Header.d.ts +26 -0
  66. package/lib-esm/ActionList2/Header.js +44 -0
  67. package/lib-esm/ActionList2/Item.d.ts +63 -0
  68. package/lib-esm/ActionList2/Item.js +210 -0
  69. package/lib-esm/ActionList2/LinkItem.d.ts +17 -0
  70. package/lib-esm/ActionList2/LinkItem.js +43 -0
  71. package/lib-esm/ActionList2/List.d.ts +26 -0
  72. package/lib-esm/ActionList2/List.js +37 -0
  73. package/lib-esm/ActionList2/Selection.d.ts +5 -0
  74. package/lib-esm/ActionList2/Selection.js +52 -0
  75. package/lib-esm/ActionList2/Visuals.d.ts +9 -0
  76. package/lib-esm/ActionList2/Visuals.js +68 -0
  77. package/lib-esm/ActionList2/index.d.ts +36 -0
  78. package/lib-esm/ActionList2/index.js +33 -0
  79. package/lib-esm/Overlay.js +2 -1
  80. package/lib-esm/Portal/Portal.js +2 -1
  81. package/lib-esm/_TextInputWrapper.js +2 -2
  82. package/lib-esm/__tests__/ActionList2.test.d.ts +1 -0
  83. package/lib-esm/__tests__/ActionList2.test.js +41 -0
  84. package/lib-esm/__tests__/AnchoredOverlay.test.js +4 -2
  85. package/lib-esm/__tests__/TextInputWithTokens.test.js +1 -10
  86. package/lib-esm/__tests__/utils/createSlots.test.d.ts +1 -0
  87. package/lib-esm/__tests__/utils/createSlots.test.js +67 -0
  88. package/lib-esm/hooks/useAnchoredPosition.js +2 -1
  89. package/lib-esm/hooks/useCombinedRefs.d.ts +2 -2
  90. package/lib-esm/hooks/useCombinedRefs.js +3 -2
  91. package/lib-esm/hooks/useResizeObserver.js +2 -2
  92. package/lib-esm/stories/ActionList2.stories.js +796 -0
  93. package/lib-esm/stories/TextInput.stories.js +117 -0
  94. package/lib-esm/stories/Token.stories.js +14 -1
  95. package/lib-esm/sx.d.ts +2 -0
  96. package/lib-esm/sx.js +3 -1
  97. package/lib-esm/theme-preval.js +81 -2
  98. package/lib-esm/unreleased.d.ts +7 -0
  99. package/lib-esm/unreleased.js +8 -0
  100. package/lib-esm/utils/create-slots.d.ts +17 -0
  101. package/lib-esm/utils/create-slots.js +84 -0
  102. package/lib-esm/utils/testing.d.ts +14 -1
  103. package/lib-esm/utils/use-force-update.d.ts +1 -0
  104. package/lib-esm/utils/use-force-update.js +6 -0
  105. package/lib-esm/utils/useIsomorphicLayoutEffect.d.ts +3 -0
  106. package/lib-esm/utils/useIsomorphicLayoutEffect.js +3 -0
  107. package/migrating.md +1 -1
  108. package/package-lock.json +146 -7
  109. package/package.json +5 -3
  110. package/script/build +2 -0
  111. package/src/ActionList2/Description.tsx +49 -0
  112. package/src/ActionList2/Divider.tsx +24 -0
  113. package/src/ActionList2/Group.tsx +34 -0
  114. package/src/ActionList2/Header.tsx +58 -0
  115. package/src/ActionList2/Item.tsx +245 -0
  116. package/src/ActionList2/LinkItem.tsx +49 -0
  117. package/src/ActionList2/List.tsx +55 -0
  118. package/src/ActionList2/Selection.tsx +40 -0
  119. package/src/ActionList2/Visuals.tsx +76 -0
  120. package/src/ActionList2/index.ts +39 -0
  121. package/src/Overlay.tsx +2 -1
  122. package/src/Portal/Portal.tsx +2 -1
  123. package/src/_TextInputWrapper.tsx +7 -0
  124. package/src/__tests__/ActionList2.test.tsx +47 -0
  125. package/src/__tests__/AnchoredOverlay.test.tsx +2 -2
  126. package/src/__tests__/TextInputWithTokens.test.tsx +0 -10
  127. package/src/__tests__/__snapshots__/ActionList2.test.tsx.snap +14 -0
  128. package/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap +35 -135
  129. package/src/__tests__/utils/__snapshots__/createSlots.test.tsx.snap +55 -0
  130. package/src/__tests__/utils/createSlots.test.tsx +74 -0
  131. package/src/hooks/useAnchoredPosition.ts +2 -1
  132. package/src/hooks/useCombinedRefs.ts +3 -3
  133. package/src/hooks/useResizeObserver.ts +2 -2
  134. package/src/stories/ActionList2.stories.tsx +1288 -0
  135. package/src/stories/TextInput.stories.tsx +113 -0
  136. package/src/stories/Token.stories.tsx +12 -1
  137. package/src/sx.ts +3 -0
  138. package/src/theme-preval.js +1 -0
  139. package/src/unreleased.ts +9 -0
  140. package/src/utils/create-slots.tsx +96 -0
  141. package/src/utils/use-force-update.ts +7 -0
  142. package/src/utils/useIsomorphicLayoutEffect.ts +10 -0
  143. package/stats.html +1 -1
@@ -0,0 +1,245 @@
1
+ import React from 'react'
2
+ import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
3
+ import {useSSRSafeId} from '@react-aria/ssr'
4
+ import styled from 'styled-components'
5
+ import {useTheme} from '../ThemeProvider'
6
+ import Box, {BoxProps} from '../Box'
7
+ import sx, {SxProp, merge} from '../sx'
8
+ import createSlots from '../utils/create-slots'
9
+ import {AriaRole} from '../utils/types'
10
+ import {ListContext} from './List'
11
+ import {Selection} from './Selection'
12
+
13
+ export const getVariantStyles = (variant: ItemProps['variant'], disabled: ItemProps['disabled']) => {
14
+ if (disabled) {
15
+ return {
16
+ color: 'fg.muted',
17
+ iconColor: 'fg.muted',
18
+ annotationColor: 'fg.muted'
19
+ }
20
+ }
21
+
22
+ switch (variant) {
23
+ case 'danger':
24
+ return {
25
+ color: 'danger.fg',
26
+ iconColor: 'danger.fg',
27
+ annotationColor: 'fg.muted',
28
+ hoverColor: 'actionListItem.danger.hoverText'
29
+ }
30
+ default:
31
+ return {
32
+ color: 'fg.default',
33
+ iconColor: 'fg.muted',
34
+ annotationColor: 'fg.muted',
35
+ hoverColor: 'fg.default'
36
+ }
37
+ }
38
+ }
39
+
40
+ export type ItemProps = {
41
+ /**
42
+ * Primary content for an Item
43
+ */
44
+ children?: React.ReactNode
45
+ /**
46
+ * Callback that will trigger both on click selection and keyboard selection.
47
+ */
48
+ onSelect?: (event: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>) => void
49
+ /**
50
+ * Is the `Item` is currently selected?
51
+ */
52
+ selected?: boolean
53
+ /**
54
+ * Style variations associated with various `Item` types.
55
+ *
56
+ * - `"default"` - An action `Item`.
57
+ * - `"danger"` - A destructive action `Item`.
58
+ */
59
+ variant?: 'default' | 'danger'
60
+ /**
61
+ * Items that are disabled can not be clicked, selected, or navigated through.
62
+ */
63
+ disabled?: boolean
64
+ /**
65
+ * The ARIA role describing the function of `Item` component. `option` is a common value.
66
+ */
67
+ role?: AriaRole
68
+ /**
69
+ * id to attach to the root element of the Item
70
+ */
71
+ id?: string
72
+ /**
73
+ * Private API for use internally only. Used by LinkItem to wrap contents in an anchor
74
+ */
75
+ _PrivateItemWrapper?: React.FC
76
+ } & SxProp
77
+
78
+ const {Slots, Slot} = createSlots(['LeadingVisual', 'InlineDescription', 'BlockDescription', 'TrailingVisual'])
79
+ export {Slot}
80
+ export type ItemContext = Pick<ItemProps, 'variant' | 'disabled'> & {
81
+ inlineDescriptionId: string
82
+ blockDescriptionId: string
83
+ }
84
+
85
+ const LiBox = styled.li<SxProp>(sx)
86
+ export const TEXT_ROW_HEIGHT = '20px' // custom value off the scale
87
+
88
+ export const Item = React.forwardRef<HTMLLIElement, ItemProps>(
89
+ (
90
+ {
91
+ variant = 'default',
92
+ disabled = false,
93
+ selected = undefined,
94
+ onSelect = () => null,
95
+ sx: sxProp = {},
96
+ id,
97
+ _PrivateItemWrapper,
98
+ ...props
99
+ },
100
+ forwardedRef
101
+ ): JSX.Element => {
102
+ const {variant: listVariant, showDividers} = React.useContext(ListContext)
103
+
104
+ const {theme} = useTheme()
105
+
106
+ const styles = {
107
+ display: 'flex',
108
+ paddingX: 2,
109
+ fontSize: 1,
110
+ paddingY: '6px', // custom value off the scale
111
+ lineHeight: TEXT_ROW_HEIGHT,
112
+ minHeight: 5,
113
+ marginX: listVariant === 'inset' ? 2 : 0,
114
+ borderRadius: listVariant === 'inset' ? 2 : 0,
115
+ transition: 'background 33.333ms linear',
116
+ color: getVariantStyles(variant, disabled).color,
117
+ textDecoration: 'none', // for as="a"
118
+ ':not([aria-disabled])': {cursor: 'pointer'},
119
+ '@media (hover: hover) and (pointer: fine)': {
120
+ ':hover:not([aria-disabled])': {
121
+ backgroundColor: `actionListItem.${variant}.hoverBg`,
122
+ color: getVariantStyles(variant, disabled).hoverColor
123
+ },
124
+ ':focus:not([data-focus-visible-added])': {
125
+ backgroundColor: `actionListItem.${variant}.selectedBg`,
126
+ color: getVariantStyles(variant, disabled).hoverColor,
127
+ outline: 'none'
128
+ },
129
+ '&[data-focus-visible-added]': {
130
+ // we don't use :focus-visible because not all browsers (safari) have it yet
131
+ outline: 'none',
132
+ border: `2 solid`,
133
+ boxShadow: `0 0 0 2px ${theme?.colors.accent.emphasis}`
134
+ },
135
+ ':active:not([aria-disabled])': {
136
+ backgroundColor: `actionListItem.${variant}.activeBg`,
137
+ color: getVariantStyles(variant, disabled).hoverColor
138
+ }
139
+ },
140
+
141
+ /** Divider styles */
142
+ '[data-component="ActionList.Item--DividerContainer"]': {
143
+ position: 'relative'
144
+ },
145
+ '[data-component="ActionList.Item--DividerContainer"]::before': {
146
+ content: '" "',
147
+ display: 'block',
148
+ position: 'absolute',
149
+ width: '100%',
150
+ top: '-7px',
151
+ border: '0 solid',
152
+ borderTopWidth: showDividers ? `1px` : '0',
153
+ borderColor: 'var(--divider-color, transparent)'
154
+ },
155
+ // show between 2 items
156
+ ':not(:first-of-type)': {'--divider-color': theme?.colors.actionListItem.inlineDivider},
157
+ // hide divider after dividers & group header, with higher importance!
158
+ '[data-component="ActionList.Divider"] + &': {'--divider-color': 'transparent !important'},
159
+ // hide border on current and previous item
160
+ '&:hover:not([aria-disabled]), &:focus:not([aria-disabled]), &[data-focus-visible-added]:not([aria-disabled])': {
161
+ '--divider-color': 'transparent'
162
+ },
163
+ '&:hover:not([aria-disabled]) + &, &:focus:not([aria-disabled]) + &, &[data-focus-visible-added] + li': {
164
+ '--divider-color': 'transparent'
165
+ }
166
+ }
167
+
168
+ const clickHandler = React.useCallback(
169
+ event => {
170
+ if (disabled) return
171
+ if (!event.defaultPrevented) onSelect(event)
172
+ },
173
+ [onSelect, disabled]
174
+ )
175
+
176
+ const keyPressHandler = React.useCallback(
177
+ event => {
178
+ if (disabled) return
179
+
180
+ if (!event.defaultPrevented && [' ', 'Enter'].includes(event.key)) {
181
+ onSelect(event)
182
+ }
183
+ },
184
+ [onSelect, disabled]
185
+ )
186
+
187
+ // use props.id if provided, otherwise generate one.
188
+ const labelId = useSSRSafeId(id)
189
+ const inlineDescriptionId = useSSRSafeId(id && `${id}--inline-description`)
190
+ const blockDescriptionId = useSSRSafeId(id && `${id}--block-description`)
191
+
192
+ const ItemWrapper = _PrivateItemWrapper || React.Fragment
193
+
194
+ return (
195
+ <Slots context={{variant, disabled, inlineDescriptionId, blockDescriptionId}}>
196
+ {slots => (
197
+ <LiBox
198
+ ref={forwardedRef}
199
+ sx={merge(styles, sxProp as SxProp)}
200
+ onClick={clickHandler}
201
+ onKeyPress={keyPressHandler}
202
+ aria-selected={selected}
203
+ aria-disabled={disabled ? true : undefined}
204
+ tabIndex={disabled || _PrivateItemWrapper ? undefined : 0}
205
+ aria-labelledby={`${labelId} ${slots.InlineDescription ? inlineDescriptionId : ''}`}
206
+ aria-describedby={slots.BlockDescription ? blockDescriptionId : undefined}
207
+ {...props}
208
+ >
209
+ <ItemWrapper>
210
+ <Selection selected={selected} disabled={disabled} />
211
+ {slots.LeadingVisual}
212
+ <Box
213
+ data-component="ActionList.Item--DividerContainer"
214
+ sx={{display: 'flex', flexDirection: 'column', flexGrow: 1, minWidth: 0}}
215
+ >
216
+ <ConditionalBox if={Boolean(slots.TrailingVisual)} sx={{display: 'flex', flexGrow: 1}}>
217
+ <ConditionalBox
218
+ if={Boolean(slots.InlineDescription)}
219
+ sx={{display: 'flex', flexGrow: 1, alignItems: 'baseline', minWidth: 0}}
220
+ >
221
+ <Box as="span" id={labelId} sx={{flexGrow: slots.InlineDescription ? 0 : 1}}>
222
+ {props.children}
223
+ </Box>
224
+ {slots.InlineDescription}
225
+ </ConditionalBox>
226
+ {slots.TrailingVisual}
227
+ </ConditionalBox>
228
+ {slots.BlockDescription}
229
+ </Box>
230
+ </ItemWrapper>
231
+ </LiBox>
232
+ )}
233
+ </Slots>
234
+ )
235
+ }
236
+ ) as PolymorphicForwardRefComponent<'li', ItemProps>
237
+
238
+ Item.displayName = 'ActionList.Item'
239
+
240
+ const ConditionalBox: React.FC<{if: boolean} & BoxProps> = props => {
241
+ const {if: condition, ...rest} = props
242
+
243
+ if (condition) return <Box {...rest}>{props.children}</Box>
244
+ else return <>{props.children}</>
245
+ }
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
3
+ import Link from '../Link'
4
+ import {SxProp, merge} from '../sx'
5
+ import {Item, ItemProps} from './Item'
6
+
7
+ // adopted from React.AnchorHTMLAttributes
8
+ type LinkProps = {
9
+ download?: string
10
+ href?: string
11
+ hrefLang?: string
12
+ media?: string
13
+ ping?: string
14
+ rel?: string
15
+ target?: string
16
+ type?: string
17
+ referrerPolicy?: React.AnchorHTMLAttributes<HTMLAnchorElement>['referrerPolicy']
18
+ }
19
+
20
+ // LinkItem does not support selected, variants, etc.
21
+ type LinkItemProps = Pick<ItemProps, 'children' | 'sx'> & LinkProps
22
+
23
+ export const LinkItem = React.forwardRef(({sx = {}, as: Component, ...props}, forwardedRef) => {
24
+ const styles = {
25
+ // occupy full size of Item
26
+ paddingX: 2,
27
+ paddingY: '6px', // custom value off the scale
28
+ display: 'flex',
29
+ flexGrow: 1, // full width
30
+ borderRadius: 2,
31
+
32
+ // inherit Item styles
33
+ color: 'inherit',
34
+ '&:hover': {color: 'inherit', textDecoration: 'none'}
35
+ }
36
+
37
+ return (
38
+ <Item
39
+ sx={{paddingY: 0, paddingX: 0}}
40
+ _PrivateItemWrapper={({children}) => (
41
+ <Link as={Component} sx={merge(styles, sx as SxProp)} {...props} ref={forwardedRef}>
42
+ {children}
43
+ </Link>
44
+ )}
45
+ >
46
+ {props.children}
47
+ </Item>
48
+ )
49
+ }) as PolymorphicForwardRefComponent<'a', LinkItemProps>
@@ -0,0 +1,55 @@
1
+ import React from 'react'
2
+ import {ForwardRefComponent as PolymorphicForwardRefComponent} from '@radix-ui/react-polymorphic'
3
+ import styled from 'styled-components'
4
+ import sx, {SxProp, merge} from '../sx'
5
+ import {AriaRole} from '../utils/types'
6
+
7
+ export type ListProps = {
8
+ /**
9
+ * `inset` children are offset (vertically and horizontally) from `List`’s edges, `full` children are flush (vertically and horizontally) with `List` edges
10
+ */
11
+ variant?: 'inset' | 'full'
12
+ /**
13
+ * Whether multiple Items or a single Item can be selected.
14
+ */
15
+ selectionVariant?: 'single' | 'multiple'
16
+ /**
17
+ * Display a divider above each `Item` in this `List` when it does not follow a `Header` or `Divider`.
18
+ */
19
+ showDividers?: boolean
20
+ /**
21
+ * The ARIA role describing the function of `List` component. `listbox` or `menu` are a common values.
22
+ */
23
+ role?: AriaRole
24
+ } & SxProp
25
+
26
+ type ContextProps = Omit<ListProps, 'sx'>
27
+ export const ListContext = React.createContext<ContextProps>({})
28
+
29
+ const ListBox = styled.ul<SxProp>(sx)
30
+
31
+ export const List = React.forwardRef<HTMLUListElement, ListProps>(
32
+ (
33
+ {variant = 'inset', selectionVariant, showDividers = false, sx: sxProp = {}, ...props},
34
+ forwardedRef
35
+ ): JSX.Element => {
36
+ const styles = {
37
+ margin: 0,
38
+ paddingInlineStart: 0, // reset ul styles
39
+ paddingY: variant === 'inset' ? 2 : 0
40
+ }
41
+
42
+ return (
43
+ <ListBox
44
+ sx={merge(styles, sxProp as SxProp)}
45
+ aria-multiselectable={selectionVariant === 'multiple' ? true : undefined}
46
+ {...props}
47
+ ref={forwardedRef}
48
+ >
49
+ <ListContext.Provider value={{variant, selectionVariant, showDividers}}>{props.children}</ListContext.Provider>
50
+ </ListBox>
51
+ )
52
+ }
53
+ ) as PolymorphicForwardRefComponent<'ul', ListProps>
54
+
55
+ List.displayName = 'ActionList'
@@ -0,0 +1,40 @@
1
+ import React from 'react'
2
+ import {CheckIcon} from '@primer/octicons-react'
3
+ import {ListContext} from './List'
4
+ import {GroupContext} from './Group'
5
+ import {ItemProps} from './Item'
6
+ import {LeadingVisualContainer} from './Visuals'
7
+
8
+ type SelectionProps = Pick<ItemProps, 'selected' | 'disabled'>
9
+ export const Selection: React.FC<SelectionProps> = ({selected, disabled}) => {
10
+ const {selectionVariant: listSelectionVariant} = React.useContext(ListContext)
11
+ const {selectionVariant: groupSelectionVariant} = React.useContext(GroupContext)
12
+
13
+ /** selectionVariant in Group can override the selectionVariant in List root */
14
+ const selectionVariant = typeof groupSelectionVariant !== 'undefined' ? groupSelectionVariant : listSelectionVariant
15
+
16
+ // if selectionVariant is not set on List, don't show selection
17
+ if (!selectionVariant) {
18
+ // to avoid confusion, fail loudly instead of silently ignoring
19
+ if (selected)
20
+ throw new Error(
21
+ 'For Item to be selected, ActionList or ActionList.Group needs to have a selectionVariant defined'
22
+ )
23
+ return null
24
+ }
25
+
26
+ if (selectionVariant === 'single') {
27
+ return <LeadingVisualContainer>{selected && <CheckIcon />}</LeadingVisualContainer>
28
+ }
29
+
30
+ /**
31
+ * selectionVariant is multiple
32
+ * readOnly is required because we are doing a one-way bind to `checked`
33
+ * aria-readonly="false" tells screen that they can still interact with the checkbox
34
+ */
35
+ return (
36
+ <LeadingVisualContainer sx={{input: {margin: 0, pointerEvents: 'none'}}}>
37
+ <input type="checkbox" checked={selected} disabled={disabled} tabIndex={-1} readOnly aria-readonly="false" />
38
+ </LeadingVisualContainer>
39
+ )
40
+ }
@@ -0,0 +1,76 @@
1
+ import React from 'react'
2
+ import Box from '../Box'
3
+ import {SxProp, merge} from '../sx'
4
+ import {get} from '../constants'
5
+ import {getVariantStyles, Slot, ItemContext, TEXT_ROW_HEIGHT} from './Item'
6
+
7
+ type VisualProps = SxProp & React.HTMLAttributes<HTMLSpanElement>
8
+
9
+ export const LeadingVisualContainer: React.FC<VisualProps> = ({sx = {}, ...props}) => {
10
+ return (
11
+ <Box
12
+ as="span"
13
+ sx={merge(
14
+ {
15
+ height: TEXT_ROW_HEIGHT, // match height of text row
16
+ minWidth: get('space.3'),
17
+ maxWidth: TEXT_ROW_HEIGHT, // square (same as height)
18
+ display: 'flex',
19
+ justifyContent: 'center',
20
+ alignItems: 'center',
21
+ flexShrink: 0,
22
+ marginRight: 2
23
+ },
24
+ sx as SxProp
25
+ )}
26
+ {...props}
27
+ />
28
+ )
29
+ }
30
+
31
+ export type LeadingVisualProps = VisualProps
32
+ export const LeadingVisual: React.FC<VisualProps> = ({sx = {}, ...props}) => {
33
+ return (
34
+ <Slot name="LeadingVisual">
35
+ {({variant, disabled}: ItemContext) => (
36
+ <LeadingVisualContainer
37
+ sx={merge(
38
+ {
39
+ color: getVariantStyles(variant, disabled).iconColor,
40
+ svg: {fontSize: 0}
41
+ },
42
+ sx as SxProp
43
+ )}
44
+ {...props}
45
+ >
46
+ {props.children}
47
+ </LeadingVisualContainer>
48
+ )}
49
+ </Slot>
50
+ )
51
+ }
52
+
53
+ export type TrailingVisualProps = VisualProps
54
+ export const TrailingVisual: React.FC<VisualProps> = ({sx = {}, ...props}) => {
55
+ return (
56
+ <Slot name="TrailingVisual">
57
+ {({variant, disabled}: ItemContext) => (
58
+ <Box
59
+ as="span"
60
+ sx={merge(
61
+ {
62
+ height: '20px', // match height of text row
63
+ flexShrink: 0,
64
+ color: getVariantStyles(variant, disabled).annotationColor,
65
+ marginLeft: 2
66
+ },
67
+ sx as SxProp
68
+ )}
69
+ {...props}
70
+ >
71
+ {props.children}
72
+ </Box>
73
+ )}
74
+ </Slot>
75
+ )
76
+ }
@@ -0,0 +1,39 @@
1
+ import {List} from './List'
2
+ import {Group} from './Group'
3
+ import {Item} from './Item'
4
+ import {LinkItem} from './LinkItem'
5
+ import {Divider} from './Divider'
6
+ import {Description} from './Description'
7
+ import {LeadingVisual, TrailingVisual} from './Visuals'
8
+
9
+ export type {ListProps as ActionListProps} from './List'
10
+ export type {GroupProps} from './Group'
11
+ export type {ItemProps} from './Item'
12
+ export type {DescriptionProps} from './Description'
13
+ export type {LeadingVisualProps, TrailingVisualProps} from './Visuals'
14
+
15
+ /**
16
+ * Collection of list-related components.
17
+ */
18
+ export const ActionList = Object.assign(List, {
19
+ /** Collects related `Items` in an `ActionList`. */
20
+ Group,
21
+
22
+ /** An actionable or selectable `Item` */
23
+ Item,
24
+
25
+ /** A `Item` that renders a full-size anchor inside ListItem */
26
+ LinkItem,
27
+
28
+ /** Visually separates `Item`s or `Group`s in an `ActionList`. */
29
+ Divider,
30
+
31
+ /** Secondary text which provides additional information about an `Item`. */
32
+ Description,
33
+
34
+ /** Icon (or similar) positioned before `Item` text. */
35
+ LeadingVisual,
36
+
37
+ /** Icon (or similar) positioned after `Item` text. */
38
+ TrailingVisual
39
+ })
package/src/Overlay.tsx CHANGED
@@ -1,7 +1,8 @@
1
1
  import styled from 'styled-components'
2
- import React, {ReactElement, useEffect, useLayoutEffect, useRef} from 'react'
2
+ import React, {ReactElement, useEffect, useRef} from 'react'
3
3
  import {get, COMMON, SystemPositionProps, SystemCommonProps} from './constants'
4
4
  import {ComponentProps} from './utils/types'
5
+ import useLayoutEffect from './utils/useIsomorphicLayoutEffect'
5
6
  import {useOverlay, TouchOrMouseEvent} from './hooks'
6
7
  import Portal from './Portal'
7
8
  import sx, {SxProp} from './sx'
@@ -1,5 +1,6 @@
1
1
  import React from 'react'
2
2
  import {createPortal} from 'react-dom'
3
+ import useLayoutEffect from '../utils/useIsomorphicLayoutEffect'
3
4
 
4
5
  const PRIMER_PORTAL_ROOT_ID = '__primerPortalRoot__'
5
6
  const DEFAULT_PORTAL_CONTAINER_NAME = '__default__'
@@ -69,7 +70,7 @@ export const Portal: React.FC<PortalProps> = ({children, onMount, containerName:
69
70
  hostElement.style.zIndex = '1'
70
71
  const elementRef = React.useRef(hostElement)
71
72
 
72
- React.useLayoutEffect(() => {
73
+ useLayoutEffect(() => {
73
74
  let containerName = _containerName
74
75
  if (containerName === undefined) {
75
76
  containerName = DEFAULT_PORTAL_CONTAINER_NAME
@@ -91,6 +91,13 @@ const TextInputWrapper = styled.span<StyledWrapperProps>`
91
91
  display: block;
92
92
  width: 100%;
93
93
  `}
94
+
95
+ ${props =>
96
+ props.block &&
97
+ props.hasIcon &&
98
+ css`
99
+ display: flex;
100
+ `}
94
101
 
95
102
  // Ensures inputs don't zoom on mobile but are body-font size on desktop
96
103
  @media (min-width: ${get('breakpoints.1')}) {
@@ -0,0 +1,47 @@
1
+ import {cleanup, render as HTMLRender} from '@testing-library/react'
2
+ import 'babel-polyfill'
3
+ import {axe, toHaveNoViolations} from 'jest-axe'
4
+ import React from 'react'
5
+ import theme from '../theme'
6
+ import {ActionList} from '../ActionList2'
7
+ import {behavesAsComponent, checkExports} from '../utils/testing'
8
+ import {BaseStyles, ThemeProvider, SSRProvider} from '..'
9
+ expect.extend(toHaveNoViolations)
10
+
11
+ function SimpleActionList(): JSX.Element {
12
+ return (
13
+ <ThemeProvider theme={theme}>
14
+ <SSRProvider>
15
+ <BaseStyles>
16
+ <ActionList>
17
+ <ActionList.Item>New file</ActionList.Item>
18
+ <ActionList.Divider />
19
+ <ActionList.Item>Copy link</ActionList.Item>
20
+ <ActionList.Item>Edit file</ActionList.Item>
21
+ <ActionList.Item variant="danger">Delete file</ActionList.Item>
22
+ </ActionList>
23
+ </BaseStyles>
24
+ </SSRProvider>
25
+ </ThemeProvider>
26
+ )
27
+ }
28
+
29
+ describe('ActionList', () => {
30
+ behavesAsComponent({
31
+ Component: ActionList,
32
+ options: {skipAs: true, skipSx: true},
33
+ toRender: () => <ActionList />
34
+ })
35
+
36
+ checkExports('ActionList2', {
37
+ default: undefined,
38
+ ActionList
39
+ })
40
+
41
+ it('should have no axe violations', async () => {
42
+ const {container} = HTMLRender(<SimpleActionList />)
43
+ const results = await axe(container)
44
+ expect(results).toHaveNoViolations()
45
+ cleanup()
46
+ })
47
+ })
@@ -144,7 +144,7 @@ describe('AnchoredOverlay', () => {
144
144
  })
145
145
 
146
146
  it('should render consistently when open', () => {
147
- const anchoredOverlay = HTMLRender(<AnchoredOverlayTestComponent initiallyOpen={true} />)
148
- expect(anchoredOverlay).toMatchSnapshot()
147
+ const {container} = HTMLRender(<AnchoredOverlayTestComponent initiallyOpen={true} />)
148
+ expect(container).toMatchSnapshot()
149
149
  })
150
150
  })
@@ -29,16 +29,6 @@ const LabelledTextInputWithTokens: React.FC<TextInputWithTokensProps> = ({onToke
29
29
  </>
30
30
  )
31
31
 
32
- // describe('axe test', () => {
33
- // it('should have no axe violations', async () => {
34
- // const onRemoveMock = jest.fn()
35
- // const {container} = HTMLRender(<LabelledTextInputWithTokens tokens={mockTokens} onTokenRemove={onRemoveMock} />)
36
- // const results = await axe(container)
37
- // expect(results).toHaveNoViolations()
38
- // cleanup()
39
- // })
40
- // })
41
-
42
32
  jest.useFakeTimers()
43
33
 
44
34
  describe('TextInputWithTokens', () => {
@@ -0,0 +1,14 @@
1
+ // Jest Snapshot v1, https://goo.gl/fbAQLP
2
+
3
+ exports[`ActionList renders consistently 1`] = `
4
+ .c0 {
5
+ margin: 0;
6
+ padding-inline-start: 0;
7
+ padding-top: 8px;
8
+ padding-bottom: 8px;
9
+ }
10
+
11
+ <ul
12
+ className="c0"
13
+ />
14
+ `;