@primer/components 31.0.2-rc.c7dafefb → 31.2.0-rc.2a086bac
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.
- package/.storybook/main.js +7 -9
- package/.storybook/preview.js +5 -1
- package/CHANGELOG.md +21 -1
- package/dist/browser.esm.js +623 -620
- package/dist/browser.esm.js.map +1 -1
- package/dist/browser.umd.js +164 -161
- package/dist/browser.umd.js.map +1 -1
- package/docs/content/ActionList2.mdx +354 -0
- package/docs/content/FilterList.md +2 -2
- package/docs/content/TextInputWithTokens.mdx +114 -0
- package/docs/content/theming.md +23 -0
- package/docs/src/@primer/gatsby-theme-doctocat/components/hero.js +1 -3
- package/docs/src/@primer/gatsby-theme-doctocat/components/live-preview-wrapper.js +1 -1
- package/docs/src/@primer/gatsby-theme-doctocat/live-code-scope.js +17 -0
- package/lib/ActionList/Header.js +1 -1
- package/lib/ActionList2/Description.d.ts +12 -0
- package/lib/ActionList2/Description.js +53 -0
- package/lib/ActionList2/Divider.d.ts +5 -0
- package/lib/ActionList2/Divider.js +35 -0
- package/lib/ActionList2/Group.d.ts +11 -0
- package/lib/ActionList2/Group.js +57 -0
- package/lib/ActionList2/Header.d.ts +26 -0
- package/lib/ActionList2/Header.js +55 -0
- package/lib/ActionList2/Item.d.ts +63 -0
- package/lib/ActionList2/Item.js +234 -0
- package/lib/ActionList2/LinkItem.d.ts +17 -0
- package/lib/ActionList2/LinkItem.js +57 -0
- package/lib/ActionList2/List.d.ts +26 -0
- package/lib/ActionList2/List.js +59 -0
- package/lib/ActionList2/Selection.d.ts +5 -0
- package/lib/ActionList2/Selection.js +70 -0
- package/lib/ActionList2/Visuals.d.ts +9 -0
- package/lib/ActionList2/Visuals.js +90 -0
- package/lib/ActionList2/index.d.ts +36 -0
- package/lib/ActionList2/index.js +47 -0
- package/lib/Autocomplete/Autocomplete.d.ts +4 -4
- package/lib/Autocomplete/AutocompleteInput.d.ts +4 -4
- package/lib/Button/Button.d.ts +5 -5
- package/lib/Button/ButtonBase.d.ts +1 -1
- package/lib/Button/ButtonClose.d.ts +3 -3
- package/lib/Button/ButtonDanger.d.ts +5 -5
- package/lib/Button/ButtonInvisible.d.ts +5 -5
- package/lib/Button/ButtonOutline.d.ts +5 -5
- package/lib/Button/ButtonPrimary.d.ts +5 -5
- package/lib/CircleBadge.d.ts +2 -2
- package/lib/CircleOcticon.d.ts +4 -4
- package/lib/Dialog.d.ts +4 -4
- package/lib/Dropdown.d.ts +16 -16
- package/lib/DropdownMenu/DropdownButton.d.ts +6 -6
- package/lib/FilterList.d.ts +3 -3
- package/lib/Flash.d.ts +1 -1
- package/lib/Label.d.ts +1 -1
- package/lib/Overlay.js +3 -1
- package/lib/Portal/Portal.js +3 -2
- package/lib/Position.d.ts +4 -4
- package/lib/ProgressBar.d.ts +1 -1
- package/lib/SelectMenu/SelectMenu.d.ts +24 -24
- package/lib/SelectMenu/SelectMenuItem.d.ts +1 -1
- package/lib/TextInputWithTokens.d.ts +8 -4
- package/lib/TextInputWithTokens.js +61 -8
- package/lib/Timeline.d.ts +4 -4
- package/lib/Token/AvatarToken.d.ts +1 -1
- package/lib/Token/IssueLabelToken.d.ts +1 -1
- package/lib/Token/Token.d.ts +1 -1
- package/lib/_TextInputWrapper.d.ts +1 -1
- package/lib/_TextInputWrapper.js +2 -2
- package/lib/__tests__/ActionList2.test.d.ts +1 -0
- package/lib/__tests__/ActionList2.test.js +53 -0
- package/lib/__tests__/AnchoredOverlay.test.js +4 -2
- package/lib/__tests__/KeyPaths.types.test.d.ts +11 -0
- package/lib/__tests__/KeyPaths.types.test.js +10 -0
- package/lib/__tests__/TextInputWithTokens.test.js +138 -7
- package/lib/__tests__/utils/createSlots.test.d.ts +1 -0
- package/lib/__tests__/utils/createSlots.test.js +75 -0
- package/lib/hooks/useAnchoredPosition.js +3 -2
- package/lib/hooks/useCombinedRefs.d.ts +2 -2
- package/lib/hooks/useCombinedRefs.js +4 -6
- package/lib/hooks/useResizeObserver.js +2 -2
- package/lib/stories/ActionList2.stories.js +875 -0
- package/lib/stories/TextInput.stories.js +144 -0
- package/lib/stories/TextInputWithTokens.stories.js +18 -1
- package/lib/stories/Token.stories.js +19 -2
- package/lib/sx.d.ts +10 -2
- package/lib/sx.js +8 -0
- package/lib/theme-preval.js +81 -2
- package/lib/theme.d.ts +78 -0
- package/lib/theme.js +3 -1
- package/lib/unreleased.d.ts +7 -0
- package/lib/unreleased.js +18 -0
- package/lib/utils/create-slots.d.ts +17 -0
- package/lib/utils/create-slots.js +105 -0
- package/lib/utils/testing.d.ts +14 -1
- package/lib/utils/types/KeyPaths.d.ts +3 -0
- package/lib/utils/types/KeyPaths.js +1 -0
- package/lib/utils/use-force-update.d.ts +1 -0
- package/lib/utils/use-force-update.js +19 -0
- package/lib/utils/useIsomorphicLayoutEffect.d.ts +3 -0
- package/lib/utils/useIsomorphicLayoutEffect.js +12 -0
- package/lib-esm/ActionList/Header.js +1 -1
- package/lib-esm/ActionList2/Description.d.ts +12 -0
- package/lib-esm/ActionList2/Description.js +37 -0
- package/lib-esm/ActionList2/Divider.d.ts +5 -0
- package/lib-esm/ActionList2/Divider.js +23 -0
- package/lib-esm/ActionList2/Group.d.ts +11 -0
- package/lib-esm/ActionList2/Group.js +40 -0
- package/lib-esm/ActionList2/Header.d.ts +26 -0
- package/lib-esm/ActionList2/Header.js +44 -0
- package/lib-esm/ActionList2/Item.d.ts +63 -0
- package/lib-esm/ActionList2/Item.js +201 -0
- package/lib-esm/ActionList2/LinkItem.d.ts +17 -0
- package/lib-esm/ActionList2/LinkItem.js +43 -0
- package/lib-esm/ActionList2/List.d.ts +26 -0
- package/lib-esm/ActionList2/List.js +37 -0
- package/lib-esm/ActionList2/Selection.d.ts +5 -0
- package/lib-esm/ActionList2/Selection.js +52 -0
- package/lib-esm/ActionList2/Visuals.d.ts +9 -0
- package/lib-esm/ActionList2/Visuals.js +68 -0
- package/lib-esm/ActionList2/index.d.ts +36 -0
- package/lib-esm/ActionList2/index.js +33 -0
- package/lib-esm/Autocomplete/Autocomplete.d.ts +4 -4
- package/lib-esm/Autocomplete/AutocompleteInput.d.ts +4 -4
- package/lib-esm/Button/Button.d.ts +5 -5
- package/lib-esm/Button/ButtonBase.d.ts +1 -1
- package/lib-esm/Button/ButtonClose.d.ts +3 -3
- package/lib-esm/Button/ButtonDanger.d.ts +5 -5
- package/lib-esm/Button/ButtonInvisible.d.ts +5 -5
- package/lib-esm/Button/ButtonOutline.d.ts +5 -5
- package/lib-esm/Button/ButtonPrimary.d.ts +5 -5
- package/lib-esm/CircleBadge.d.ts +2 -2
- package/lib-esm/CircleOcticon.d.ts +4 -4
- package/lib-esm/Dialog.d.ts +4 -4
- package/lib-esm/Dropdown.d.ts +16 -16
- package/lib-esm/DropdownMenu/DropdownButton.d.ts +6 -6
- package/lib-esm/FilterList.d.ts +3 -3
- package/lib-esm/Flash.d.ts +1 -1
- package/lib-esm/Label.d.ts +1 -1
- package/lib-esm/Overlay.js +2 -1
- package/lib-esm/Portal/Portal.js +2 -1
- package/lib-esm/Position.d.ts +4 -4
- package/lib-esm/ProgressBar.d.ts +1 -1
- package/lib-esm/SelectMenu/SelectMenu.d.ts +24 -24
- package/lib-esm/SelectMenu/SelectMenuItem.d.ts +1 -1
- package/lib-esm/TextInputWithTokens.d.ts +8 -4
- package/lib-esm/TextInputWithTokens.js +60 -8
- package/lib-esm/Timeline.d.ts +4 -4
- package/lib-esm/Token/AvatarToken.d.ts +1 -1
- package/lib-esm/Token/IssueLabelToken.d.ts +1 -1
- package/lib-esm/Token/Token.d.ts +1 -1
- package/lib-esm/_TextInputWrapper.d.ts +1 -1
- package/lib-esm/_TextInputWrapper.js +2 -2
- package/lib-esm/__tests__/ActionList2.test.d.ts +1 -0
- package/lib-esm/__tests__/ActionList2.test.js +41 -0
- package/lib-esm/__tests__/AnchoredOverlay.test.js +4 -2
- package/lib-esm/__tests__/KeyPaths.types.test.d.ts +11 -0
- package/lib-esm/__tests__/KeyPaths.types.test.js +3 -0
- package/lib-esm/__tests__/TextInputWithTokens.test.js +132 -8
- package/lib-esm/__tests__/utils/createSlots.test.d.ts +1 -0
- package/lib-esm/__tests__/utils/createSlots.test.js +67 -0
- package/lib-esm/hooks/useAnchoredPosition.js +2 -1
- package/lib-esm/hooks/useCombinedRefs.d.ts +2 -2
- package/lib-esm/hooks/useCombinedRefs.js +3 -2
- package/lib-esm/hooks/useResizeObserver.js +2 -2
- package/lib-esm/stories/ActionList2.stories.js +764 -0
- package/lib-esm/stories/TextInput.stories.js +117 -0
- package/lib-esm/stories/TextInputWithTokens.stories.js +14 -0
- package/lib-esm/stories/Token.stories.js +14 -1
- package/lib-esm/sx.d.ts +10 -2
- package/lib-esm/sx.js +3 -1
- package/lib-esm/theme-preval.js +81 -2
- package/lib-esm/theme.d.ts +78 -0
- package/lib-esm/theme.js +2 -1
- package/lib-esm/unreleased.d.ts +7 -0
- package/lib-esm/unreleased.js +8 -0
- package/lib-esm/utils/create-slots.d.ts +17 -0
- package/lib-esm/utils/create-slots.js +84 -0
- package/lib-esm/utils/testing.d.ts +14 -1
- package/lib-esm/utils/types/KeyPaths.d.ts +3 -0
- package/lib-esm/utils/types/KeyPaths.js +1 -0
- package/lib-esm/utils/use-force-update.d.ts +1 -0
- package/lib-esm/utils/use-force-update.js +6 -0
- package/lib-esm/utils/useIsomorphicLayoutEffect.d.ts +3 -0
- package/lib-esm/utils/useIsomorphicLayoutEffect.js +3 -0
- package/migrating.md +1 -1
- package/package-lock.json +38098 -45
- package/package.json +7 -3
- package/script/build +2 -0
- package/src/ActionList/Header.tsx +1 -1
- package/src/ActionList2/Description.tsx +49 -0
- package/src/ActionList2/Divider.tsx +24 -0
- package/src/ActionList2/Group.tsx +34 -0
- package/src/ActionList2/Header.tsx +58 -0
- package/src/ActionList2/Item.tsx +228 -0
- package/src/ActionList2/LinkItem.tsx +49 -0
- package/src/ActionList2/List.tsx +55 -0
- package/src/ActionList2/Selection.tsx +40 -0
- package/src/ActionList2/Visuals.tsx +76 -0
- package/src/ActionList2/index.ts +39 -0
- package/src/Overlay.tsx +2 -1
- package/src/Portal/Portal.tsx +2 -1
- package/src/TextInputWithTokens.tsx +64 -8
- package/src/_TextInputWrapper.tsx +8 -0
- package/src/__tests__/ActionList2.test.tsx +47 -0
- package/src/__tests__/AnchoredOverlay.test.tsx +2 -2
- package/src/__tests__/KeyPaths.types.test.ts +14 -0
- package/src/__tests__/TextInputWithTokens.test.tsx +123 -1
- package/src/__tests__/__snapshots__/ActionList2.test.tsx.snap +14 -0
- package/src/__tests__/__snapshots__/AnchoredOverlay.test.tsx.snap +35 -135
- package/src/__tests__/__snapshots__/Autocomplete.test.tsx.snap +7 -0
- package/src/__tests__/__snapshots__/TextInput.test.tsx.snap +6 -0
- package/src/__tests__/__snapshots__/TextInputWithTokens.test.tsx.snap +463 -0
- package/src/__tests__/utils/__snapshots__/createSlots.test.tsx.snap +55 -0
- package/src/__tests__/utils/createSlots.test.tsx +74 -0
- package/src/hooks/useAnchoredPosition.ts +2 -1
- package/src/hooks/useCombinedRefs.ts +3 -3
- package/src/hooks/useResizeObserver.ts +2 -2
- package/src/stories/ActionList2.stories.tsx +1279 -0
- package/src/stories/Button.stories.tsx +1 -1
- package/src/stories/TextInput.stories.tsx +113 -0
- package/src/stories/TextInputWithTokens.stories.tsx +9 -0
- package/src/stories/Token.stories.tsx +12 -1
- package/src/sx.ts +17 -2
- package/src/theme-preval.js +1 -0
- package/src/theme.ts +86 -0
- package/src/unreleased.ts +9 -0
- package/src/utils/create-slots.tsx +96 -0
- package/src/utils/types/KeyPaths.ts +10 -0
- package/src/utils/use-force-update.ts +7 -0
- package/src/utils/useIsomorphicLayoutEffect.ts +10 -0
- package/stats.html +1 -1
@@ -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,
|
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'
|
package/src/Portal/Portal.tsx
CHANGED
@@ -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
|
-
|
73
|
+
useLayoutEffect(() => {
|
73
74
|
let containerName = _containerName
|
74
75
|
if (containerName === undefined) {
|
75
76
|
containerName = DEFAULT_PORTAL_CONTAINER_NAME
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import React, {FocusEventHandler, KeyboardEventHandler, RefObject, useRef, useState} from 'react'
|
1
|
+
import React, {FocusEventHandler, KeyboardEventHandler, MouseEventHandler, RefObject, useRef, useState} from 'react'
|
2
2
|
import {omit} from '@styled-system/props'
|
3
3
|
import {FocusKeys} from './behaviors/focusZone'
|
4
4
|
import {useCombinedRefs} from './hooks/useCombinedRefs'
|
@@ -11,6 +11,7 @@ import {useProvidedRefOrCreate} from './hooks'
|
|
11
11
|
import UnstyledTextInput from './_UnstyledTextInput'
|
12
12
|
import TextInputWrapper from './_TextInputWrapper'
|
13
13
|
import Box from './Box'
|
14
|
+
import Text from './Text'
|
14
15
|
import {isFocusable} from './utils/iterateFocusableElements'
|
15
16
|
|
16
17
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
@@ -48,8 +49,19 @@ type TextInputWithTokensInternalProps<TokenComponentType extends AnyReactCompone
|
|
48
49
|
* Whether the remove buttons should be rendered in the tokens
|
49
50
|
*/
|
50
51
|
hideTokenRemoveButtons?: boolean
|
52
|
+
/**
|
53
|
+
* The number of tokens to display before truncating
|
54
|
+
*/
|
55
|
+
visibleTokenCount?: number
|
51
56
|
} & TextInputProps
|
52
57
|
|
58
|
+
const overflowCountFontSizeMap: Record<TokenSizeKeys, number> = {
|
59
|
+
small: 0,
|
60
|
+
medium: 1,
|
61
|
+
large: 1,
|
62
|
+
extralarge: 2
|
63
|
+
}
|
64
|
+
|
53
65
|
// using forwardRef is important so that other components (ex. Autocomplete) can use the ref
|
54
66
|
function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactComponent>(
|
55
67
|
{
|
@@ -71,6 +83,7 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
|
|
71
83
|
minWidth: minWidthProp,
|
72
84
|
maxWidth: maxWidthProp,
|
73
85
|
variant: variantProp,
|
86
|
+
visibleTokenCount,
|
74
87
|
...rest
|
75
88
|
}: TextInputWithTokensInternalProps<TokenComponentType> & {
|
76
89
|
selectedTokenIndex: number | undefined
|
@@ -78,11 +91,12 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
|
|
78
91
|
},
|
79
92
|
externalRef: React.ForwardedRef<HTMLInputElement>
|
80
93
|
) {
|
81
|
-
const {onFocus, onKeyDown, ...inputPropsRest} = omit(rest)
|
94
|
+
const {onBlur, onFocus, onKeyDown, ...inputPropsRest} = omit(rest)
|
82
95
|
const ref = useProvidedRefOrCreate<HTMLInputElement>(externalRef as React.RefObject<HTMLInputElement>)
|
83
96
|
const localInputRef = useRef<HTMLInputElement>(null)
|
84
97
|
const combinedInputRef = useCombinedRefs(localInputRef, ref)
|
85
98
|
const [selectedTokenIndex, setSelectedTokenIndex] = useState<number | undefined>()
|
99
|
+
const [tokensAreTruncated, setTokensAreTruncated] = useState<boolean>(Boolean(visibleTokenCount))
|
86
100
|
const {containerRef} = useFocusZone(
|
87
101
|
{
|
88
102
|
focusOutBehavior: 'wrap',
|
@@ -144,18 +158,42 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
|
|
144
158
|
|
145
159
|
const handleTokenBlur: FocusEventHandler = () => {
|
146
160
|
setSelectedTokenIndex(undefined)
|
161
|
+
|
162
|
+
// HACK: wait a tick and check the focused element before hiding truncated tokens
|
163
|
+
// this prevents the tokens from hiding when the user is moving focus between tokens,
|
164
|
+
// but still hides the tokens when the user blurs the token by tabbing out or clicking somewhere else on the page
|
165
|
+
setTimeout(() => {
|
166
|
+
if (!containerRef.current?.contains(document.activeElement) && visibleTokenCount) {
|
167
|
+
setTokensAreTruncated(true)
|
168
|
+
}
|
169
|
+
}, 0)
|
147
170
|
}
|
148
171
|
|
149
|
-
const handleTokenKeyUp: KeyboardEventHandler =
|
150
|
-
if (
|
172
|
+
const handleTokenKeyUp: KeyboardEventHandler = event => {
|
173
|
+
if (event.key === 'Escape') {
|
151
174
|
ref.current?.focus()
|
152
175
|
}
|
153
176
|
}
|
154
177
|
|
155
|
-
const handleInputFocus: FocusEventHandler =
|
156
|
-
onFocus && onFocus(
|
178
|
+
const handleInputFocus: FocusEventHandler = event => {
|
179
|
+
onFocus && onFocus(event)
|
157
180
|
setSelectedTokenIndex(undefined)
|
181
|
+
visibleTokenCount && setTokensAreTruncated(false)
|
182
|
+
}
|
183
|
+
|
184
|
+
const handleInputBlur: FocusEventHandler = event => {
|
185
|
+
onBlur && onBlur(event)
|
186
|
+
|
187
|
+
// HACK: wait a tick and check the focused element before hiding truncated tokens
|
188
|
+
// this prevents the tokens from hiding when the user is moving focus from the input to a token,
|
189
|
+
// but still hides the tokens when the user blurs the input by tabbing out or clicking somewhere else on the page
|
190
|
+
setTimeout(() => {
|
191
|
+
if (!containerRef.current?.contains(document.activeElement) && visibleTokenCount) {
|
192
|
+
setTokensAreTruncated(true)
|
193
|
+
}
|
194
|
+
}, 0)
|
158
195
|
}
|
196
|
+
|
159
197
|
const handleInputKeyDown: KeyboardEventHandler = e => {
|
160
198
|
if (onKeyDown) {
|
161
199
|
onKeyDown(e)
|
@@ -187,6 +225,16 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
|
|
187
225
|
}
|
188
226
|
}
|
189
227
|
|
228
|
+
const focusInput: MouseEventHandler = () => {
|
229
|
+
combinedInputRef.current?.focus()
|
230
|
+
}
|
231
|
+
|
232
|
+
const preventTokenClickPropagation: MouseEventHandler = event => {
|
233
|
+
event.stopPropagation()
|
234
|
+
}
|
235
|
+
|
236
|
+
const visibleTokens = tokensAreTruncated ? tokens.slice(0, visibleTokenCount) : tokens
|
237
|
+
|
190
238
|
return (
|
191
239
|
<TextInputWrapper
|
192
240
|
block={block}
|
@@ -199,6 +247,7 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
|
|
199
247
|
minWidth={minWidthProp}
|
200
248
|
maxWidth={maxWidthProp}
|
201
249
|
variant={variantProp}
|
250
|
+
onClick={focusInput}
|
202
251
|
sx={{
|
203
252
|
...(block
|
204
253
|
? {
|
@@ -251,19 +300,21 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
|
|
251
300
|
ref={combinedInputRef}
|
252
301
|
disabled={disabled}
|
253
302
|
onFocus={handleInputFocus}
|
303
|
+
onBlur={handleInputBlur}
|
254
304
|
onKeyDown={handleInputKeyDown}
|
255
305
|
type="text"
|
256
306
|
sx={{height: '100%'}}
|
257
307
|
{...inputPropsRest}
|
258
308
|
/>
|
259
309
|
</Box>
|
260
|
-
{
|
261
|
-
?
|
310
|
+
{TokenComponent
|
311
|
+
? visibleTokens.map(({id, ...tokenRest}, i) => (
|
262
312
|
<TokenComponent
|
263
313
|
key={id}
|
264
314
|
onFocus={handleTokenFocus(i)}
|
265
315
|
onBlur={handleTokenBlur}
|
266
316
|
onKeyUp={handleTokenKeyUp}
|
317
|
+
onClick={preventTokenClickPropagation}
|
267
318
|
isSelected={selectedTokenIndex === i}
|
268
319
|
onRemove={() => {
|
269
320
|
handleTokenRemove(id)
|
@@ -275,6 +326,11 @@ function TextInputWithTokensInnerComponent<TokenComponentType extends AnyReactCo
|
|
275
326
|
/>
|
276
327
|
))
|
277
328
|
: null}
|
329
|
+
{tokensAreTruncated ? (
|
330
|
+
<Text color="fg.muted" fontSize={size && overflowCountFontSizeMap[size]}>
|
331
|
+
+{tokens.length - visibleTokens.length}
|
332
|
+
</Text>
|
333
|
+
) : null}
|
278
334
|
</Box>
|
279
335
|
</TextInputWrapper>
|
280
336
|
)
|
@@ -45,6 +45,7 @@ const TextInputWrapper = styled.span<StyledWrapperProps>`
|
|
45
45
|
border-radius: ${get('radii.2')};
|
46
46
|
outline: none;
|
47
47
|
box-shadow: ${get('shadows.primer.shadow.inset')};
|
48
|
+
cursor: text;
|
48
49
|
|
49
50
|
${props => {
|
50
51
|
if (props.hasIcon) {
|
@@ -90,6 +91,13 @@ const TextInputWrapper = styled.span<StyledWrapperProps>`
|
|
90
91
|
display: block;
|
91
92
|
width: 100%;
|
92
93
|
`}
|
94
|
+
|
95
|
+
${props =>
|
96
|
+
props.block &&
|
97
|
+
props.hasIcon &&
|
98
|
+
css`
|
99
|
+
display: flex;
|
100
|
+
`}
|
93
101
|
|
94
102
|
// Ensures inputs don't zoom on mobile but are body-font size on desktop
|
95
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
|
148
|
-
expect(
|
147
|
+
const {container} = HTMLRender(<AnchoredOverlayTestComponent initiallyOpen={true} />)
|
148
|
+
expect(container).toMatchSnapshot()
|
149
149
|
})
|
150
150
|
})
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import {Union} from 'ts-toolbelt'
|
2
|
+
import {KeyPaths} from '../utils/types/KeyPaths'
|
3
|
+
|
4
|
+
type NestedObject = {
|
5
|
+
a: string
|
6
|
+
b: {
|
7
|
+
b1: string
|
8
|
+
b2: string
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
export function generatesKeyPathsFromObject(x: Union.Diff<KeyPaths<NestedObject>, 'a' | 'b.b1' | 'b.b2'>): never {
|
13
|
+
return x
|
14
|
+
}
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import React from 'react'
|
2
2
|
import {render} from '../utils/testing'
|
3
|
-
import {render as HTMLRender,
|
3
|
+
import {render as HTMLRender, fireEvent, act, cleanup} from '@testing-library/react'
|
4
4
|
import {axe, toHaveNoViolations} from 'jest-axe'
|
5
5
|
import 'babel-polyfill'
|
6
6
|
import {TokenSizeKeys, tokenSizes} from '../Token/TokenBase'
|
@@ -29,6 +29,8 @@ const LabelledTextInputWithTokens: React.FC<TextInputWithTokensProps> = ({onToke
|
|
29
29
|
</>
|
30
30
|
)
|
31
31
|
|
32
|
+
jest.useFakeTimers()
|
33
|
+
|
32
34
|
describe('TextInputWithTokens', () => {
|
33
35
|
it('renders without tokens', () => {
|
34
36
|
const onRemoveMock = jest.fn()
|
@@ -85,6 +87,13 @@ describe('TextInputWithTokens', () => {
|
|
85
87
|
).toMatchSnapshot()
|
86
88
|
})
|
87
89
|
|
90
|
+
it('renders a truncated set of tokens', () => {
|
91
|
+
const onRemoveMock = jest.fn()
|
92
|
+
expect(
|
93
|
+
render(<TextInputWithTokens tokens={mockTokens} onTokenRemove={onRemoveMock} visibleTokenCount={2} />)
|
94
|
+
).toMatchSnapshot()
|
95
|
+
})
|
96
|
+
|
88
97
|
it('focuses the previous token when keying ArrowLeft', () => {
|
89
98
|
const onRemoveMock = jest.fn()
|
90
99
|
const {getByLabelText, getByText} = HTMLRender(
|
@@ -165,6 +174,119 @@ describe('TextInputWithTokens', () => {
|
|
165
174
|
expect(document.activeElement?.id).toEqual(inputNode.id)
|
166
175
|
})
|
167
176
|
|
177
|
+
it('does not focus the input when clicking a token', () => {
|
178
|
+
const onRemoveMock = jest.fn()
|
179
|
+
const {getByLabelText, getByText} = HTMLRender(
|
180
|
+
<LabelledTextInputWithTokens tokens={mockTokens} onTokenRemove={onRemoveMock} visibleTokenCount={2} />
|
181
|
+
)
|
182
|
+
const inputNode = getByLabelText('Tokens')
|
183
|
+
const tokenNode = getByText(mockTokens[0].text)
|
184
|
+
|
185
|
+
expect(document.activeElement).not.toEqual(inputNode.id)
|
186
|
+
fireEvent.click(tokenNode)
|
187
|
+
expect(document.activeElement?.id).not.toEqual(inputNode.id)
|
188
|
+
})
|
189
|
+
|
190
|
+
it('focuses the input when clicking somewhere in the component besides the tokens', () => {
|
191
|
+
const onRemoveMock = jest.fn()
|
192
|
+
const {getByLabelText, getByText} = HTMLRender(
|
193
|
+
<LabelledTextInputWithTokens tokens={mockTokens} onTokenRemove={onRemoveMock} visibleTokenCount={2} />
|
194
|
+
)
|
195
|
+
const inputNode = getByLabelText('Tokens')
|
196
|
+
const truncatedTokenCount = getByText('+6')
|
197
|
+
|
198
|
+
expect(document.activeElement).not.toEqual(inputNode.id)
|
199
|
+
fireEvent.click(truncatedTokenCount)
|
200
|
+
expect(document.activeElement?.id).toEqual(inputNode.id)
|
201
|
+
})
|
202
|
+
|
203
|
+
it('shows all tokens when the input is focused and hides them when it is blurred (when visibleTokenCount is set)', () => {
|
204
|
+
const onRemoveMock = jest.fn()
|
205
|
+
const visibleTokenCount = 2
|
206
|
+
const {getByLabelText, getByText} = HTMLRender(
|
207
|
+
<>
|
208
|
+
<LabelledTextInputWithTokens
|
209
|
+
tokens={mockTokens}
|
210
|
+
onTokenRemove={onRemoveMock}
|
211
|
+
visibleTokenCount={visibleTokenCount}
|
212
|
+
/>
|
213
|
+
<button id="focusableOutsideComponent">Focus me</button>
|
214
|
+
</>
|
215
|
+
)
|
216
|
+
const inputNode = getByLabelText('Tokens')
|
217
|
+
const focusableOutsideComponentNode = getByText('Focus me')
|
218
|
+
const allTokenLabels = mockTokens.map(token => token.text)
|
219
|
+
const truncatedTokenCountNode = getByText('+6')
|
220
|
+
|
221
|
+
act(() => {
|
222
|
+
jest.runAllTimers()
|
223
|
+
fireEvent.focus(inputNode)
|
224
|
+
})
|
225
|
+
|
226
|
+
setTimeout(() => {
|
227
|
+
for (const tokenLabel of allTokenLabels) {
|
228
|
+
const tokenNode = getByText(tokenLabel)
|
229
|
+
expect(tokenNode).toBeDefined()
|
230
|
+
}
|
231
|
+
}, 0)
|
232
|
+
|
233
|
+
act(() => {
|
234
|
+
jest.runAllTimers()
|
235
|
+
// onBlur isn't called on input unless we specifically fire the "blur" event
|
236
|
+
// eslint-disable-next-line github/no-blur
|
237
|
+
fireEvent.blur(inputNode)
|
238
|
+
fireEvent.focus(focusableOutsideComponentNode)
|
239
|
+
})
|
240
|
+
|
241
|
+
setTimeout(() => {
|
242
|
+
expect(truncatedTokenCountNode).toBeDefined()
|
243
|
+
|
244
|
+
for (const tokenLabel of allTokenLabels) {
|
245
|
+
const tokenNode = getByText(tokenLabel)
|
246
|
+
if (allTokenLabels.indexOf(tokenLabel) > visibleTokenCount) {
|
247
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
248
|
+
expect(tokenNode).toBeDefined()
|
249
|
+
} else {
|
250
|
+
// eslint-disable-next-line jest/no-conditional-expect
|
251
|
+
expect(tokenNode).not.toBeDefined()
|
252
|
+
}
|
253
|
+
}
|
254
|
+
}, 0)
|
255
|
+
|
256
|
+
jest.useRealTimers()
|
257
|
+
})
|
258
|
+
|
259
|
+
it('does not hide tokens when blurring the input to focus within the component (when visibleTokenCount is set)', () => {
|
260
|
+
const onRemoveMock = jest.fn()
|
261
|
+
const visibleTokenCount = 2
|
262
|
+
const {getByLabelText, getByText} = HTMLRender(
|
263
|
+
<>
|
264
|
+
<LabelledTextInputWithTokens
|
265
|
+
tokens={mockTokens}
|
266
|
+
onTokenRemove={onRemoveMock}
|
267
|
+
visibleTokenCount={visibleTokenCount}
|
268
|
+
/>
|
269
|
+
<button id="focusableOutsideComponent">Focus me</button>
|
270
|
+
</>
|
271
|
+
)
|
272
|
+
const inputNode = getByLabelText('Tokens')
|
273
|
+
const firstTokenNode = getByText(mockTokens[visibleTokenCount - 1].text)
|
274
|
+
const allTokenLabels = mockTokens.map(token => token.text)
|
275
|
+
const truncatedTokenCountNode = getByText('+6')
|
276
|
+
|
277
|
+
act(() => {
|
278
|
+
fireEvent.focus(inputNode)
|
279
|
+
fireEvent.focus(firstTokenNode)
|
280
|
+
})
|
281
|
+
|
282
|
+
expect(truncatedTokenCountNode).toBeDefined()
|
283
|
+
|
284
|
+
for (const tokenLabel of allTokenLabels) {
|
285
|
+
const tokenNode = getByText(tokenLabel)
|
286
|
+
expect(tokenNode).toBeDefined()
|
287
|
+
}
|
288
|
+
})
|
289
|
+
|
168
290
|
it('focuses the first token when keying ArrowRight in the input', () => {
|
169
291
|
const onRemoveMock = jest.fn()
|
170
292
|
const {getByLabelText, getByText} = HTMLRender(
|
@@ -94,9 +94,7 @@ exports[`AnchoredOverlay renders consistently 1`] = `
|
|
94
94
|
`;
|
95
95
|
|
96
96
|
exports[`AnchoredOverlay should render consistently when open 1`] = `
|
97
|
-
|
98
|
-
"asFragment": [Function],
|
99
|
-
"baseElement": .c0 {
|
97
|
+
.c0 {
|
100
98
|
font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";
|
101
99
|
line-height: 1.5;
|
102
100
|
color: #24292f;
|
@@ -187,146 +185,48 @@ Object {
|
|
187
185
|
outline: none;
|
188
186
|
}
|
189
187
|
|
190
|
-
<
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
</button>
|
207
|
-
<div
|
208
|
-
id="__primerPortalRoot__"
|
209
|
-
style="position: absolute; top: 0px; left: 0px;"
|
210
|
-
>
|
211
|
-
<div
|
212
|
-
style="position: relative; z-index: 1;"
|
213
|
-
>
|
214
|
-
<div
|
215
|
-
class="c2"
|
216
|
-
data-focus-trap="active"
|
217
|
-
height="auto"
|
218
|
-
role="none"
|
219
|
-
style="top: 4px; left: 0px; --styled-overlay-visibility: visible;"
|
220
|
-
width="auto"
|
221
|
-
>
|
222
|
-
<button
|
223
|
-
class="focus-visible"
|
224
|
-
data-focus-visible-added=""
|
225
|
-
tabindex="0"
|
226
|
-
type="button"
|
227
|
-
>
|
228
|
-
Focusable Child
|
229
|
-
</button>
|
230
|
-
</div>
|
231
|
-
</div>
|
232
|
-
</div>
|
233
|
-
</div>
|
234
|
-
</div>
|
235
|
-
</body>,
|
236
|
-
"container": <div>
|
188
|
+
<div>
|
189
|
+
<div
|
190
|
+
class="c0"
|
191
|
+
color="fg.default"
|
192
|
+
data-portal-root="true"
|
193
|
+
font-family="normal"
|
194
|
+
>
|
195
|
+
<button
|
196
|
+
aria-haspopup="true"
|
197
|
+
aria-labelledby="react-aria-1"
|
198
|
+
class="c1"
|
199
|
+
id="react-aria-1"
|
200
|
+
tabindex="0"
|
201
|
+
>
|
202
|
+
Anchor Button
|
203
|
+
</button>
|
237
204
|
<div
|
238
|
-
|
239
|
-
|
240
|
-
data-portal-root="true"
|
241
|
-
font-family="normal"
|
205
|
+
id="__primerPortalRoot__"
|
206
|
+
style="position: absolute; top: 0px; left: 0px;"
|
242
207
|
>
|
243
|
-
<button
|
244
|
-
aria-haspopup="true"
|
245
|
-
aria-labelledby="react-aria-1"
|
246
|
-
class="ButtonBase-sc-181ps9o-0 Button-xjtz72-0 iRqJHc"
|
247
|
-
id="react-aria-1"
|
248
|
-
tabindex="0"
|
249
|
-
>
|
250
|
-
Anchor Button
|
251
|
-
</button>
|
252
208
|
<div
|
253
|
-
|
254
|
-
style="position: absolute; top: 0px; left: 0px;"
|
209
|
+
style="position: relative; z-index: 1;"
|
255
210
|
>
|
256
211
|
<div
|
257
|
-
|
212
|
+
class="c2"
|
213
|
+
data-focus-trap="active"
|
214
|
+
height="auto"
|
215
|
+
role="none"
|
216
|
+
style="top: 4px; left: 0px; --styled-overlay-visibility: visible;"
|
217
|
+
width="auto"
|
258
218
|
>
|
259
|
-
<
|
260
|
-
class="
|
261
|
-
data-focus-
|
262
|
-
|
263
|
-
|
264
|
-
style="top: 4px; left: 0px; --styled-overlay-visibility: visible;"
|
265
|
-
width="auto"
|
219
|
+
<button
|
220
|
+
class="focus-visible"
|
221
|
+
data-focus-visible-added=""
|
222
|
+
tabindex="0"
|
223
|
+
type="button"
|
266
224
|
>
|
267
|
-
|
268
|
-
|
269
|
-
data-focus-visible-added=""
|
270
|
-
tabindex="0"
|
271
|
-
type="button"
|
272
|
-
>
|
273
|
-
Focusable Child
|
274
|
-
</button>
|
275
|
-
</div>
|
225
|
+
Focusable Child
|
226
|
+
</button>
|
276
227
|
</div>
|
277
228
|
</div>
|
278
229
|
</div>
|
279
|
-
</div
|
280
|
-
|
281
|
-
"findAllByAltText": [Function],
|
282
|
-
"findAllByDisplayValue": [Function],
|
283
|
-
"findAllByLabelText": [Function],
|
284
|
-
"findAllByPlaceholderText": [Function],
|
285
|
-
"findAllByRole": [Function],
|
286
|
-
"findAllByTestId": [Function],
|
287
|
-
"findAllByText": [Function],
|
288
|
-
"findAllByTitle": [Function],
|
289
|
-
"findByAltText": [Function],
|
290
|
-
"findByDisplayValue": [Function],
|
291
|
-
"findByLabelText": [Function],
|
292
|
-
"findByPlaceholderText": [Function],
|
293
|
-
"findByRole": [Function],
|
294
|
-
"findByTestId": [Function],
|
295
|
-
"findByText": [Function],
|
296
|
-
"findByTitle": [Function],
|
297
|
-
"getAllByAltText": [Function],
|
298
|
-
"getAllByDisplayValue": [Function],
|
299
|
-
"getAllByLabelText": [Function],
|
300
|
-
"getAllByPlaceholderText": [Function],
|
301
|
-
"getAllByRole": [Function],
|
302
|
-
"getAllByTestId": [Function],
|
303
|
-
"getAllByText": [Function],
|
304
|
-
"getAllByTitle": [Function],
|
305
|
-
"getByAltText": [Function],
|
306
|
-
"getByDisplayValue": [Function],
|
307
|
-
"getByLabelText": [Function],
|
308
|
-
"getByPlaceholderText": [Function],
|
309
|
-
"getByRole": [Function],
|
310
|
-
"getByTestId": [Function],
|
311
|
-
"getByText": [Function],
|
312
|
-
"getByTitle": [Function],
|
313
|
-
"queryAllByAltText": [Function],
|
314
|
-
"queryAllByDisplayValue": [Function],
|
315
|
-
"queryAllByLabelText": [Function],
|
316
|
-
"queryAllByPlaceholderText": [Function],
|
317
|
-
"queryAllByRole": [Function],
|
318
|
-
"queryAllByTestId": [Function],
|
319
|
-
"queryAllByText": [Function],
|
320
|
-
"queryAllByTitle": [Function],
|
321
|
-
"queryByAltText": [Function],
|
322
|
-
"queryByDisplayValue": [Function],
|
323
|
-
"queryByLabelText": [Function],
|
324
|
-
"queryByPlaceholderText": [Function],
|
325
|
-
"queryByRole": [Function],
|
326
|
-
"queryByTestId": [Function],
|
327
|
-
"queryByText": [Function],
|
328
|
-
"queryByTitle": [Function],
|
329
|
-
"rerender": [Function],
|
330
|
-
"unmount": [Function],
|
331
|
-
}
|
230
|
+
</div>
|
231
|
+
</div>
|
332
232
|
`;
|