@graphprotocol/gds-react 0.2.0 → 0.2.2
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/dist/GDSContext.d.ts +13 -0
- package/dist/GDSContext.d.ts.map +1 -0
- package/dist/GDSContext.js +4 -0
- package/dist/GDSContext.js.map +1 -0
- package/dist/GDSProvider.d.ts +1 -9
- package/dist/GDSProvider.d.ts.map +1 -1
- package/dist/GDSProvider.js +4 -3
- package/dist/GDSProvider.js.map +1 -1
- package/dist/components/Avatar.d.ts.map +1 -1
- package/dist/components/Avatar.js +2 -2
- package/dist/components/Avatar.js.map +1 -1
- package/dist/components/Breadcrumbs.parts.js +1 -1
- package/dist/components/Breadcrumbs.parts.js.map +1 -1
- package/dist/components/Button.d.ts.map +1 -1
- package/dist/components/Button.js +69 -69
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Card.js +2 -2
- package/dist/components/Card.js.map +1 -1
- package/dist/components/CodeBlock.d.ts +1 -1
- package/dist/components/CodeBlock.parts.d.ts +1 -1
- package/dist/components/CopyButton.d.ts +1 -1
- package/dist/components/CopyButton.d.ts.map +1 -1
- package/dist/components/CopyButton.js +46 -19
- package/dist/components/CopyButton.js.map +1 -1
- package/dist/components/Input.js +2 -2
- package/dist/components/Input.js.map +1 -1
- package/dist/components/Link.js +2 -2
- package/dist/components/Link.js.map +1 -1
- package/dist/components/Menu.parts.d.ts +4 -5
- package/dist/components/Menu.parts.d.ts.map +1 -1
- package/dist/components/Menu.parts.js +52 -45
- package/dist/components/Menu.parts.js.map +1 -1
- package/dist/components/Modal.parts.d.ts.map +1 -1
- package/dist/components/Modal.parts.js +17 -21
- package/dist/components/Modal.parts.js.map +1 -1
- package/dist/components/Pane.d.ts +9 -0
- package/dist/components/Pane.d.ts.map +1 -0
- package/dist/components/Pane.js +8 -0
- package/dist/components/Pane.js.map +1 -0
- package/dist/components/Pane.meta.d.ts +20 -0
- package/dist/components/Pane.meta.d.ts.map +1 -0
- package/dist/components/Pane.meta.js +30 -0
- package/dist/components/Pane.meta.js.map +1 -0
- package/dist/components/Pane.parts.d.ts +77 -0
- package/dist/components/Pane.parts.d.ts.map +1 -0
- package/dist/components/Pane.parts.js +412 -0
- package/dist/components/Pane.parts.js.map +1 -0
- package/dist/components/Search.js +1 -1
- package/dist/components/Tooltip.parts.d.ts +13 -4
- package/dist/components/Tooltip.parts.d.ts.map +1 -1
- package/dist/components/Tooltip.parts.js +51 -63
- package/dist/components/Tooltip.parts.js.map +1 -1
- package/dist/components/base/ButtonOrLink.d.ts +1 -1
- package/dist/components/base/ButtonOrLink.d.ts.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.d.ts +10 -3
- package/dist/components/base/ButtonOrLink.parts.d.ts.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.js +27 -35
- package/dist/components/base/ButtonOrLink.parts.js.map +1 -1
- package/dist/components/base/MaybeButtonOrLink.d.ts +19 -2
- package/dist/components/base/MaybeButtonOrLink.d.ts.map +1 -1
- package/dist/components/base/MaybeButtonOrLink.js +5 -3
- package/dist/components/base/MaybeButtonOrLink.js.map +1 -1
- package/dist/components/base/Presence.d.ts +157 -0
- package/dist/components/base/Presence.d.ts.map +1 -0
- package/dist/components/base/Presence.js +808 -0
- package/dist/components/base/Presence.js.map +1 -0
- package/dist/components/base/index.d.ts +1 -0
- package/dist/components/base/index.d.ts.map +1 -1
- package/dist/components/base/index.js +1 -0
- package/dist/components/base/index.js.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +2 -0
- package/dist/components/index.js.map +1 -1
- package/dist/hooks/useCSSProp.js +1 -1
- package/dist/hooks/useCSSProp.js.map +1 -1
- package/dist/hooks/useControlled.d.ts.map +1 -1
- package/dist/hooks/useControlled.js +6 -4
- package/dist/hooks/useControlled.js.map +1 -1
- package/dist/hooks/useGDS.js +1 -1
- package/dist/hooks/useGDS.js.map +1 -1
- package/dist/hooks/useStyleObserver.js +1 -1
- package/dist/hooks/useStyleObserver.js.map +1 -1
- package/dist/tailwind-plugin.d.ts.map +1 -1
- package/dist/tailwind-plugin.js +3 -0
- package/dist/tailwind-plugin.js.map +1 -1
- package/dist/utils/InlineCounter.d.ts +3 -0
- package/dist/utils/InlineCounter.d.ts.map +1 -0
- package/dist/utils/InlineCounter.js +7 -0
- package/dist/utils/InlineCounter.js.map +1 -0
- package/dist/utils/RenderCount.d.ts +3 -0
- package/dist/utils/RenderCount.d.ts.map +1 -0
- package/dist/utils/RenderCount.js +7 -0
- package/dist/utils/RenderCount.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +14 -14
- package/src/GDSContext.ts +16 -0
- package/src/GDSProvider.tsx +20 -31
- package/src/components/Avatar.tsx +3 -2
- package/src/components/Breadcrumbs.parts.tsx +1 -1
- package/src/components/Button.tsx +113 -107
- package/src/components/Card.tsx +2 -2
- package/src/components/CopyButton.tsx +49 -25
- package/src/components/Input.tsx +1 -1
- package/src/components/Link.tsx +2 -2
- package/src/components/Menu.parts.tsx +78 -73
- package/src/components/Modal.parts.tsx +26 -31
- package/src/components/Pane.meta.ts +31 -0
- package/src/components/Pane.parts.tsx +713 -0
- package/src/components/Pane.tsx +17 -0
- package/src/components/Search.tsx +1 -1
- package/src/components/Tooltip.parts.tsx +95 -80
- package/src/components/base/ButtonOrLink.parts.tsx +71 -51
- package/src/components/base/ButtonOrLink.tsx +1 -0
- package/src/components/base/MaybeButtonOrLink.tsx +26 -5
- package/src/components/base/Presence.tsx +1375 -0
- package/src/components/base/index.ts +1 -0
- package/src/components/index.ts +10 -0
- package/src/hooks/useCSSProp.ts +1 -1
- package/src/hooks/useControlled.ts +16 -8
- package/src/hooks/useGDS.ts +1 -1
- package/src/hooks/useStyleObserver.ts +1 -1
- package/src/tailwind-plugin.ts +3 -0
- package/src/utils/InlineCounter.tsx +17 -0
- package/src/utils/RenderCount.tsx +7 -0
- package/src/utils/index.ts +2 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PaneContainer, PaneProvider, PaneRoot, PaneToggleButton } from './Pane.parts.tsx'
|
|
2
|
+
|
|
3
|
+
export const Pane = Object.assign(PaneRoot, {
|
|
4
|
+
Provider: PaneProvider,
|
|
5
|
+
Container: PaneContainer,
|
|
6
|
+
ToggleButton: PaneToggleButton,
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export { usePane } from './Pane.parts.tsx'
|
|
10
|
+
|
|
11
|
+
export type {
|
|
12
|
+
PaneContainerProps,
|
|
13
|
+
PaneProps,
|
|
14
|
+
PaneProviderProps,
|
|
15
|
+
PaneToggleButtonProps,
|
|
16
|
+
UsePaneOptions,
|
|
17
|
+
} from './Pane.parts.tsx'
|
|
@@ -102,7 +102,7 @@ export function Search({
|
|
|
102
102
|
data-size={cssProps.size}
|
|
103
103
|
data-layout={cssProps.layout}
|
|
104
104
|
className={cn(
|
|
105
|
-
`gds-search root-flex flex-col u:max-w-full
|
|
105
|
+
`gds-search root-flex flex-col u:h-max u:max-w-full
|
|
106
106
|
u:hover:state-hover
|
|
107
107
|
u:active:state-active
|
|
108
108
|
u:data-[layout=compact]:data-[size=medium]:min-w-10
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
createContext,
|
|
5
|
+
useCallback,
|
|
5
6
|
useContext,
|
|
6
7
|
useEffect,
|
|
7
8
|
useRef,
|
|
@@ -9,8 +10,10 @@ import {
|
|
|
9
10
|
type ComponentProps,
|
|
10
11
|
type ReactElement,
|
|
11
12
|
type ReactNode,
|
|
13
|
+
type Ref,
|
|
12
14
|
} from 'react'
|
|
13
|
-
import { Tooltip } from '@base-ui/react/tooltip'
|
|
15
|
+
import { Tooltip, type TooltipRoot as BaseUITooltipRoot } from '@base-ui/react/tooltip'
|
|
16
|
+
import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
|
|
14
17
|
import type { SetReturnType } from 'type-fest'
|
|
15
18
|
|
|
16
19
|
import { twToPx, type GDSComponentProps } from '@graphprotocol/gds-css'
|
|
@@ -23,12 +26,11 @@ import { TooltipMeta } from './Tooltip.meta.ts'
|
|
|
23
26
|
|
|
24
27
|
type Content = Exclude<ReactNode, undefined>
|
|
25
28
|
type SetDeferredContentFunction = (content: Content, priority?: number) => void
|
|
29
|
+
type OpenChangeReason = BaseUITooltipRoot.ChangeEventDetails['reason']
|
|
26
30
|
|
|
27
31
|
const TooltipContext = createContext<{
|
|
28
32
|
collectContent: SetDeferredContentFunction
|
|
29
|
-
inheritContent: SetDeferredContentFunction
|
|
30
33
|
disableDescendants: boolean
|
|
31
|
-
registerDescendantSetOpen: (descendantSetOpen: (newOpen: boolean) => void) => void
|
|
32
34
|
} | null>(null)
|
|
33
35
|
|
|
34
36
|
const OPEN_DELAY = 300
|
|
@@ -43,9 +45,15 @@ export interface TooltipProps
|
|
|
43
45
|
/** Force the tooltip to be open or closed. */
|
|
44
46
|
open?: boolean | undefined
|
|
45
47
|
/** Called when the tooltip is opened or closed. */
|
|
46
|
-
onOpenChange?: ((open: boolean) => void) | undefined
|
|
48
|
+
onOpenChange?: ((open: boolean, reason: OpenChangeReason) => void) | undefined
|
|
47
49
|
/** The element that triggers the tooltip. */
|
|
48
50
|
children: ReactElement
|
|
51
|
+
/**
|
|
52
|
+
* Props to merge with the trigger element. Set to `'forward'` to have the trigger automatically
|
|
53
|
+
* receive applicable props passed to the `Tooltip` itself (excluding tooltip-specific props like
|
|
54
|
+
* `content`, `side` and other positioning props, `open`, `disabled`, etc.).
|
|
55
|
+
*/
|
|
56
|
+
triggerProps?: ComponentProps<'button'> | 'forward' | undefined
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
export function TooltipRoot({
|
|
@@ -62,12 +70,19 @@ export function TooltipRoot({
|
|
|
62
70
|
className,
|
|
63
71
|
style,
|
|
64
72
|
children,
|
|
65
|
-
|
|
73
|
+
triggerProps: passedTriggerProps,
|
|
74
|
+
...passedProps
|
|
66
75
|
}: TooltipProps) {
|
|
67
76
|
const { dirProps } = useGDS()
|
|
68
77
|
|
|
69
78
|
const ancestorTooltip = useContext(TooltipContext)
|
|
70
|
-
|
|
79
|
+
|
|
80
|
+
const popupProps = passedTriggerProps !== 'forward' ? passedProps : {}
|
|
81
|
+
const popupRef = passedTriggerProps !== 'forward' ? passedRef : undefined
|
|
82
|
+
|
|
83
|
+
const triggerProps = passedTriggerProps === 'forward' ? passedProps : passedTriggerProps
|
|
84
|
+
const triggerDisabled =
|
|
85
|
+
triggerProps && 'disabled' in triggerProps ? triggerProps.disabled === true : false
|
|
71
86
|
|
|
72
87
|
const [cssPropsPolyfillRef, cssPropsPolyfillAttributes, cssProps] = useCSSPropsPolyfill(
|
|
73
88
|
TooltipMeta,
|
|
@@ -81,88 +96,76 @@ export function TooltipRoot({
|
|
|
81
96
|
)
|
|
82
97
|
|
|
83
98
|
const [setCollectedContent, collectedContent] = useDeferredContent()
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
const content =
|
|
87
|
-
passedContent !== undefined
|
|
88
|
-
? passedContent
|
|
89
|
-
: collectedContent !== undefined
|
|
90
|
-
? collectedContent
|
|
91
|
-
: inheritedContent
|
|
92
|
-
|
|
93
|
-
const triggerDisabled =
|
|
94
|
-
'props' in children &&
|
|
95
|
-
children.props &&
|
|
96
|
-
typeof children.props === 'object' &&
|
|
97
|
-
'disabled' in children.props
|
|
98
|
-
? children.props.disabled === true
|
|
99
|
-
: false
|
|
100
|
-
|
|
101
|
-
// Propagate the passed/collected content up for the first tooltip ancestor that needs them
|
|
102
|
-
if (passedContent !== undefined && !cssProps.disabled && !triggerDisabled) {
|
|
103
|
-
ancestorTooltip?.inheritContent(passedContent, 3)
|
|
104
|
-
}
|
|
99
|
+
const content = passedContent !== undefined ? passedContent : collectedContent
|
|
105
100
|
|
|
101
|
+
/**
|
|
102
|
+
* Propagate collected content up till it reaches `Tooltip.Collector`'s closest non-disabled
|
|
103
|
+
* tooltip ancestor with no passed content.
|
|
104
|
+
*/
|
|
106
105
|
const collectContent: SetDeferredContentFunction = (newCollectedContent, priority) => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const inheritContent: SetDeferredContentFunction = (newInheritedContent, priority) => {
|
|
112
|
-
void setInheritedContent(newInheritedContent, priority)
|
|
113
|
-
if (!triggerDisabled) ancestorTooltip?.inheritContent(newInheritedContent, 1)
|
|
106
|
+
if (passedContent === undefined || cssProps.disabled) {
|
|
107
|
+
void setCollectedContent(newCollectedContent, priority)
|
|
108
|
+
ancestorTooltip?.collectContent(newCollectedContent, priority)
|
|
109
|
+
}
|
|
114
110
|
}
|
|
115
111
|
|
|
116
112
|
const disabledByAncestor = ancestorTooltip?.disableDescendants ?? false
|
|
117
113
|
const disableDescendants =
|
|
118
114
|
disabledByAncestor ||
|
|
119
|
-
triggerDisabled ||
|
|
120
115
|
(content !== undefined && !cssProps.disabled && cssProps.overrideDescendants)
|
|
121
|
-
const enabled =
|
|
116
|
+
const enabled =
|
|
117
|
+
Boolean(content) &&
|
|
118
|
+
!cssProps.disabled &&
|
|
119
|
+
(!triggerDisabled || controlledOpen) &&
|
|
120
|
+
!disabledByAncestor
|
|
122
121
|
|
|
123
|
-
const
|
|
124
|
-
const
|
|
125
|
-
const registerDescendantSetOpen = (newDescendantSetOpen: (newOpen: boolean) => void) => {
|
|
126
|
-
descendantSetOpen.current = newDescendantSetOpen
|
|
127
|
-
}
|
|
128
|
-
const justOpenedRef = useRef(false)
|
|
129
|
-
const setOpen = (newOpen: boolean) => {
|
|
130
|
-
ownSetOpen(newOpen)
|
|
122
|
+
const triggerRef = useRef<HTMLButtonElement>(null)
|
|
123
|
+
const triggerPassedRef = useMergedRefs(
|
|
131
124
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* an enabled tooltip are disabled to prevent a lot of weirdness).
|
|
125
|
+
* Conditionally setting the ref to force the tooltip to refresh its position when `enabled`
|
|
126
|
+
* changes, preventing it from moving to the viewport's top-left corner in some cases (e.g. when
|
|
127
|
+
* `CopyButton` returns to its default tooltip).
|
|
136
128
|
*/
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
129
|
+
enabled ? triggerRef : undefined,
|
|
130
|
+
(passedTriggerProps === 'forward' ? passedRef : passedTriggerProps?.ref) as
|
|
131
|
+
| Ref<HTMLButtonElement>
|
|
132
|
+
| undefined,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
const [open, privateSetOpen] = useControlled(controlledOpen, false, onOpenChange)
|
|
136
|
+
const justOpenedRef = useRef(false)
|
|
137
|
+
const setOpen = useCallback(
|
|
138
|
+
(newOpen: boolean, reason: OpenChangeReason) => {
|
|
139
|
+
privateSetOpen(newOpen, reason)
|
|
140
|
+
if (newOpen) {
|
|
141
|
+
justOpenedRef.current = true
|
|
142
|
+
window.setTimeout(() => {
|
|
143
|
+
justOpenedRef.current = false
|
|
144
|
+
}, 0)
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[privateSetOpen],
|
|
148
|
+
)
|
|
148
149
|
|
|
149
150
|
const openTimeoutRef = useRef<number>(undefined)
|
|
150
|
-
const clearOpenTimeout = () => {
|
|
151
|
+
const clearOpenTimeout = useCallback(() => {
|
|
151
152
|
if (!openTimeoutRef.current) return
|
|
152
153
|
window.clearTimeout(openTimeoutRef.current)
|
|
153
154
|
openTimeoutRef.current = undefined
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
}, [])
|
|
156
|
+
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!enabled) {
|
|
159
|
+
clearOpenTimeout()
|
|
160
|
+
if (open) setOpen(false, 'disabled')
|
|
161
|
+
}
|
|
162
|
+
}, [enabled, open, setOpen, clearOpenTimeout])
|
|
158
163
|
|
|
159
164
|
return (
|
|
160
165
|
<TooltipContext.Provider
|
|
161
166
|
value={{
|
|
162
167
|
collectContent,
|
|
163
|
-
inheritContent,
|
|
164
168
|
disableDescendants,
|
|
165
|
-
registerDescendantSetOpen,
|
|
166
169
|
}}
|
|
167
170
|
>
|
|
168
171
|
<Tooltip.Root
|
|
@@ -175,7 +178,10 @@ export function TooltipRoot({
|
|
|
175
178
|
// Disable Base UI's default behavior of closing the tooltip when pressing the trigger
|
|
176
179
|
if (!newOpen && eventDetails.reason === 'trigger-press') {
|
|
177
180
|
// Ensure clicking on the trigger before the tooltip has a chance to open doesn't prevent it from opening
|
|
178
|
-
openTimeoutRef.current = window.setTimeout(
|
|
181
|
+
openTimeoutRef.current = window.setTimeout(
|
|
182
|
+
() => setOpen(true, eventDetails.reason),
|
|
183
|
+
OPEN_DELAY,
|
|
184
|
+
)
|
|
179
185
|
return
|
|
180
186
|
}
|
|
181
187
|
// Make `overrideDescendants={false}` work as expected (see the `NestedSimultaneous` story)
|
|
@@ -187,19 +193,27 @@ export function TooltipRoot({
|
|
|
187
193
|
) {
|
|
188
194
|
return
|
|
189
195
|
}
|
|
190
|
-
setOpen(newOpen)
|
|
196
|
+
setOpen(newOpen, eventDetails.reason)
|
|
191
197
|
}}
|
|
192
198
|
>
|
|
193
199
|
<Tooltip.Trigger
|
|
194
|
-
ref={triggerRef}
|
|
195
200
|
delay={OPEN_DELAY}
|
|
196
201
|
closeDelay={100}
|
|
197
|
-
|
|
198
|
-
|
|
202
|
+
{...(triggerProps as ComponentProps<typeof Tooltip.Trigger>)}
|
|
203
|
+
ref={triggerPassedRef}
|
|
204
|
+
disabled={undefined} // Not passed through for some reason, so we set it in `render` and override it to `undefined` here just in case
|
|
205
|
+
render={(renderProps) => (
|
|
206
|
+
<Render
|
|
207
|
+
render={children}
|
|
208
|
+
{...renderProps}
|
|
209
|
+
disabled={triggerDisabled}
|
|
210
|
+
onClick={triggerProps?.onClick} // Prevent the tooltip re-opening after clicking e.g. a `Menu` trigger
|
|
211
|
+
/>
|
|
199
212
|
)}
|
|
200
213
|
/>
|
|
201
214
|
<Tooltip.Portal>
|
|
202
215
|
<Tooltip.Positioner
|
|
216
|
+
key={getReactNodeKey(content)} // Refresh the tooltip's position when the content changes (see `WithDynamicContent` story)
|
|
203
217
|
side={
|
|
204
218
|
cssProps.side === 'start'
|
|
205
219
|
? 'inline-start'
|
|
@@ -211,19 +225,19 @@ export function TooltipRoot({
|
|
|
211
225
|
align={cssProps.align}
|
|
212
226
|
alignOffset={twToPx(cssProps.alignOffset)}
|
|
213
227
|
collisionPadding={8}
|
|
228
|
+
disableAnchorTracking // Prevent the tooltip from moving to the viewport's top-left corner when the trigger is unmounted
|
|
214
229
|
{...dirProps}
|
|
215
230
|
>
|
|
216
231
|
<Tooltip.Popup
|
|
217
|
-
ref={
|
|
218
|
-
data-side={cssProps.side}
|
|
232
|
+
ref={popupRef}
|
|
219
233
|
className={cn(
|
|
220
234
|
`gds-tooltip root-block u:max-w-(--available-width) u:rounded-4 u:bg-default u:px-2 u:py-1 u:text-12 u:transition
|
|
221
235
|
i:origin-(--transform-origin)
|
|
222
236
|
i:data-ending-style:opacity-0
|
|
223
237
|
i:data-starting-style:opacity-0
|
|
224
238
|
i:data-starting-style:data-[side=bottom]:-translate-y-1
|
|
225
|
-
i:data-starting-style:data-[side=end]:-translate-x-1
|
|
226
|
-
i:data-starting-style:data-[side=start]:translate-x-1
|
|
239
|
+
i:data-starting-style:data-[side=inline-end]:-translate-x-1
|
|
240
|
+
i:data-starting-style:data-[side=inline-start]:translate-x-1
|
|
227
241
|
i:data-starting-style:data-[side=top]:translate-y-1
|
|
228
242
|
i:rtl:data-starting-style:data-[side=end]:translate-x-1
|
|
229
243
|
i:rtl:data-starting-style:data-[side=start]:-translate-x-1`,
|
|
@@ -231,7 +245,7 @@ export function TooltipRoot({
|
|
|
231
245
|
)}
|
|
232
246
|
{...cssPropsAttributes}
|
|
233
247
|
{...cssPropsPolyfillAttributes}
|
|
234
|
-
{...
|
|
248
|
+
{...popupProps}
|
|
235
249
|
>
|
|
236
250
|
{content}
|
|
237
251
|
</Tooltip.Popup>
|
|
@@ -244,7 +258,7 @@ export function TooltipRoot({
|
|
|
244
258
|
className={cn('gds-tooltip i:invisible', className)}
|
|
245
259
|
{...cssPropsAttributes}
|
|
246
260
|
{...cssPropsPolyfillAttributes}
|
|
247
|
-
{...
|
|
261
|
+
{...popupProps}
|
|
248
262
|
/>
|
|
249
263
|
</Portal>
|
|
250
264
|
</Tooltip.Root>
|
|
@@ -288,8 +302,9 @@ function useDeferredContent() {
|
|
|
288
302
|
}
|
|
289
303
|
|
|
290
304
|
export interface TooltipContentProps {
|
|
291
|
-
render?: RenderProp | undefined
|
|
292
305
|
children: ReactNode
|
|
306
|
+
priority?: number | undefined
|
|
307
|
+
render?: RenderProp | undefined
|
|
293
308
|
}
|
|
294
309
|
|
|
295
310
|
/**
|
|
@@ -301,10 +316,10 @@ export interface TooltipContentProps {
|
|
|
301
316
|
* where the information it provides is not otherwise available to screen readers (i.e. different
|
|
302
317
|
* from the trigger's accessible name).
|
|
303
318
|
*/
|
|
304
|
-
export function TooltipContent({
|
|
319
|
+
export function TooltipContent({ children, priority, render }: TooltipContentProps) {
|
|
305
320
|
const ancestorTooltip = useContext(TooltipContext)
|
|
306
321
|
|
|
307
|
-
ancestorTooltip?.collectContent(children || null)
|
|
322
|
+
ancestorTooltip?.collectContent(children || null, priority)
|
|
308
323
|
|
|
309
324
|
return render ? <Render render={render}>{children}</Render> : children
|
|
310
325
|
}
|
|
@@ -20,7 +20,7 @@ import { useButton } from 'react-aria'
|
|
|
20
20
|
import type { Merge } from 'type-fest'
|
|
21
21
|
|
|
22
22
|
import { cn } from '../../utils/index.ts'
|
|
23
|
-
import { Render, type RenderFn } from './Render.tsx'
|
|
23
|
+
import { Render, type RenderFn, type RenderFnProps } from './Render.tsx'
|
|
24
24
|
|
|
25
25
|
interface LinkComponentObject {
|
|
26
26
|
component: ElementType
|
|
@@ -37,8 +37,7 @@ type LinkComponent =
|
|
|
37
37
|
| null
|
|
38
38
|
|
|
39
39
|
export type ButtonOrLinkState = {
|
|
40
|
-
|
|
41
|
-
category: 'button' | 'link' | 'other'
|
|
40
|
+
category: 'button' | 'link'
|
|
42
41
|
role: NonNullable<InternalButtonOrLinkProps['role']>
|
|
43
42
|
disabled: NonNullable<InternalButtonOrLinkProps['disabled']>
|
|
44
43
|
type?: InternalButtonOrLinkProps['type']
|
|
@@ -47,6 +46,16 @@ export type ButtonOrLinkState = {
|
|
|
47
46
|
target?: InternalButtonOrLinkProps['target']
|
|
48
47
|
}
|
|
49
48
|
|
|
49
|
+
export type ButtonOrLinkRenderState = ButtonOrLinkState & {
|
|
50
|
+
Element: ElementType<ButtonOrLinkRenderElementProps>
|
|
51
|
+
elementProps: ButtonOrLinkRenderElementProps
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ButtonOrLinkRenderElementProps {
|
|
55
|
+
buttonOrLinkState: ButtonOrLinkState
|
|
56
|
+
[key: string]: unknown
|
|
57
|
+
}
|
|
58
|
+
|
|
50
59
|
export declare namespace InternalButtonOrLinkProps {
|
|
51
60
|
interface DisableableProps {
|
|
52
61
|
disabled?: boolean | 'focusable' | undefined
|
|
@@ -62,7 +71,7 @@ export declare namespace InternalButtonOrLinkProps {
|
|
|
62
71
|
*/
|
|
63
72
|
inline?: boolean | undefined
|
|
64
73
|
/** Custom render function to control the rendered element. */
|
|
65
|
-
render?: RenderFn<
|
|
74
|
+
render?: RenderFn<ButtonOrLinkRenderState> | undefined
|
|
66
75
|
/** Custom component to use for rendering links (e.g. Next.js Link) */
|
|
67
76
|
linkComponent?: LinkComponent | undefined
|
|
68
77
|
}
|
|
@@ -116,12 +125,14 @@ export function useAncestorButtonOrLink() {
|
|
|
116
125
|
return useContext(ButtonOrLinkContext)
|
|
117
126
|
}
|
|
118
127
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Creating "with context" components as opposed to wrapping `ButtonOrLink`'s returned element in a
|
|
130
|
+
* context provider to avoid a "Trying to render a nested `ButtonOrLink`" error when rendering
|
|
131
|
+
* `ButtonOrLink` siblings in the `render` function (like `Card` does with `interactiveContent`).
|
|
132
|
+
*/
|
|
122
133
|
|
|
123
134
|
type WithContextComponent<T extends ElementType> = (
|
|
124
|
-
props: Merge<ComponentProps<T>,
|
|
135
|
+
props: Merge<ComponentProps<T>, ButtonOrLinkRenderElementProps>,
|
|
125
136
|
) => ReactElement
|
|
126
137
|
|
|
127
138
|
const memoizedWithContextComponents = new Map<ElementType, WithContextComponent<ElementType>>()
|
|
@@ -188,7 +199,7 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
|
|
|
188
199
|
disabled = false,
|
|
189
200
|
inline = false,
|
|
190
201
|
linkComponent: passedLinkComponent,
|
|
191
|
-
render,
|
|
202
|
+
render: passedRender,
|
|
192
203
|
className: passedClassName,
|
|
193
204
|
children,
|
|
194
205
|
...nonBaseProps
|
|
@@ -205,6 +216,25 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
|
|
|
205
216
|
onKeyUp: undefined,
|
|
206
217
|
}
|
|
207
218
|
|
|
219
|
+
const render = (
|
|
220
|
+
Element: ElementType<ButtonOrLinkRenderElementProps>,
|
|
221
|
+
renderProps: RenderFnProps,
|
|
222
|
+
buttonOrLinkState: ButtonOrLinkState,
|
|
223
|
+
extraElementProps?: Record<string, unknown>,
|
|
224
|
+
) => {
|
|
225
|
+
const elementProps: ButtonOrLinkRenderElementProps = { buttonOrLinkState, ...extraElementProps }
|
|
226
|
+
const renderState: ButtonOrLinkRenderState = {
|
|
227
|
+
...buttonOrLinkState,
|
|
228
|
+
Element,
|
|
229
|
+
elementProps,
|
|
230
|
+
}
|
|
231
|
+
return passedRender ? (
|
|
232
|
+
passedRender(renderProps, renderState)
|
|
233
|
+
) : (
|
|
234
|
+
<Element {...renderProps} {...elementProps} />
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
208
238
|
const className = cn('gds-button-or-link', passedClassName)
|
|
209
239
|
|
|
210
240
|
if (props.href === undefined) {
|
|
@@ -225,17 +255,6 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
|
|
|
225
255
|
)
|
|
226
256
|
}
|
|
227
257
|
|
|
228
|
-
const Element = inline ? SpanButtonWithContext : ButtonWithContext
|
|
229
|
-
|
|
230
|
-
const state: ButtonOrLinkState = {
|
|
231
|
-
Element,
|
|
232
|
-
category: 'button',
|
|
233
|
-
role,
|
|
234
|
-
disabled,
|
|
235
|
-
type,
|
|
236
|
-
checked,
|
|
237
|
-
}
|
|
238
|
-
|
|
239
258
|
const checkedAttribute =
|
|
240
259
|
checked === undefined
|
|
241
260
|
? {}
|
|
@@ -254,7 +273,7 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
|
|
|
254
273
|
return { 'aria-checked': checkedOrMixed }
|
|
255
274
|
})(checked)
|
|
256
275
|
|
|
257
|
-
const buttonProps: ComponentProps<
|
|
276
|
+
const buttonProps: ComponentProps<'button'> = {
|
|
258
277
|
role,
|
|
259
278
|
type: disabled === 'focusable' ? 'button' : type,
|
|
260
279
|
disabled: disabled === true,
|
|
@@ -262,12 +281,23 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
|
|
|
262
281
|
...checkedAttribute,
|
|
263
282
|
className,
|
|
264
283
|
children,
|
|
265
|
-
buttonOrLinkState: state,
|
|
266
284
|
...(remainingProps as ComponentProps<'button'>),
|
|
267
285
|
...((disabled === 'focusable' || (disabled && inline)) && manuallyDisabledEventProps),
|
|
268
286
|
}
|
|
269
287
|
|
|
270
|
-
|
|
288
|
+
const buttonOrLinkState: ButtonOrLinkState = {
|
|
289
|
+
category: 'button',
|
|
290
|
+
role,
|
|
291
|
+
disabled,
|
|
292
|
+
type,
|
|
293
|
+
checked,
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return render(
|
|
297
|
+
inline ? SpanButtonWithContext : ButtonWithContext,
|
|
298
|
+
buttonProps,
|
|
299
|
+
buttonOrLinkState,
|
|
300
|
+
)
|
|
271
301
|
}
|
|
272
302
|
|
|
273
303
|
const {
|
|
@@ -281,15 +311,6 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
|
|
|
281
311
|
...remainingProps
|
|
282
312
|
} = nonBaseProps
|
|
283
313
|
|
|
284
|
-
const state: ButtonOrLinkState = {
|
|
285
|
-
Element: 'a',
|
|
286
|
-
category: 'link',
|
|
287
|
-
role,
|
|
288
|
-
disabled,
|
|
289
|
-
href,
|
|
290
|
-
target,
|
|
291
|
-
}
|
|
292
|
-
|
|
293
314
|
let linkProps: ComponentProps<'a'> = {
|
|
294
315
|
role: disabled || role !== 'link' ? role : undefined,
|
|
295
316
|
href: !disabled ? href : undefined,
|
|
@@ -317,29 +338,28 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
|
|
|
317
338
|
}
|
|
318
339
|
}
|
|
319
340
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
render: linkComponent,
|
|
327
|
-
buttonOrLinkState: state,
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return render ? render(elementProps, state) : <Element {...elementProps} />
|
|
341
|
+
const buttonOrLinkState: ButtonOrLinkState = {
|
|
342
|
+
category: 'link',
|
|
343
|
+
role,
|
|
344
|
+
disabled,
|
|
345
|
+
href,
|
|
346
|
+
target,
|
|
331
347
|
}
|
|
332
348
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
349
|
+
if (isValidElement(linkComponent)) {
|
|
350
|
+
return render(
|
|
351
|
+
RenderWithContext as ButtonOrLinkRenderState['Element'],
|
|
352
|
+
linkProps,
|
|
353
|
+
buttonOrLinkState,
|
|
354
|
+
{ render: linkComponent },
|
|
355
|
+
)
|
|
340
356
|
}
|
|
341
357
|
|
|
342
|
-
return render
|
|
358
|
+
return render(
|
|
359
|
+
linkComponent && linkComponent !== 'a' ? withContext(linkComponent) : AnchorWithContext,
|
|
360
|
+
linkProps,
|
|
361
|
+
buttonOrLinkState,
|
|
362
|
+
)
|
|
343
363
|
}
|
|
344
364
|
ButtonOrLinkRoot.displayName = 'ButtonOrLink'
|
|
345
365
|
|
|
@@ -4,30 +4,49 @@ import type { ComponentProps, ElementType } from 'react'
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
ButtonOrLink,
|
|
7
|
-
type
|
|
7
|
+
type ButtonOrLinkRenderState,
|
|
8
8
|
type InternalButtonOrLinkProps,
|
|
9
9
|
type OmitInternalButtonOrLinkProps,
|
|
10
10
|
} from './ButtonOrLink.tsx'
|
|
11
|
+
import type { RenderFn } from './Render.tsx'
|
|
12
|
+
|
|
13
|
+
type OtherElementRenderState = {
|
|
14
|
+
category: 'other'
|
|
15
|
+
role: ButtonOrLinkRenderState['role']
|
|
16
|
+
disabled: false
|
|
17
|
+
type?: undefined
|
|
18
|
+
checked?: undefined
|
|
19
|
+
href?: undefined
|
|
20
|
+
target?: undefined
|
|
21
|
+
Element: ButtonOrLinkRenderState['Element']
|
|
22
|
+
elementProps: ButtonOrLinkRenderState['elementProps']
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type MaybeButtonOrLinkRenderState = ButtonOrLinkRenderState | OtherElementRenderState
|
|
11
26
|
|
|
12
27
|
export declare namespace MaybeButtonOrLinkProps {
|
|
13
28
|
export type DisableableProps = InternalButtonOrLinkProps.DisableableProps
|
|
14
29
|
export type ButtonProps = OmitInternalButtonOrLinkProps<InternalButtonOrLinkProps.ButtonProps> & {
|
|
15
30
|
onClick: NonNullable<InternalButtonOrLinkProps.ButtonProps['onClick']>
|
|
16
31
|
as?: undefined
|
|
32
|
+
render?: RenderFn<MaybeButtonOrLinkRenderState> | undefined
|
|
17
33
|
}
|
|
18
34
|
export type ToggleButtonProps =
|
|
19
35
|
OmitInternalButtonOrLinkProps<InternalButtonOrLinkProps.ToggleButtonProps> & {
|
|
20
36
|
onClick: NonNullable<InternalButtonOrLinkProps.ToggleButtonProps['onClick']>
|
|
21
37
|
as?: undefined
|
|
38
|
+
render?: RenderFn<MaybeButtonOrLinkRenderState> | undefined
|
|
22
39
|
}
|
|
23
40
|
export type LinkProps = OmitInternalButtonOrLinkProps<InternalButtonOrLinkProps.LinkProps> & {
|
|
24
41
|
as?: undefined
|
|
42
|
+
render?: RenderFn<MaybeButtonOrLinkRenderState> | undefined
|
|
25
43
|
}
|
|
26
44
|
export type OtherElementProps =
|
|
27
45
|
OmitInternalButtonOrLinkProps<InternalButtonOrLinkProps.BaseProps> &
|
|
28
46
|
ComponentProps<'span'> & {
|
|
29
47
|
onClick?: InternalButtonOrLinkProps.ButtonProps['onClick'] // Not technically correct, but prevents the event from being `any` when `as` is not specified
|
|
30
48
|
as?: ElementType | undefined
|
|
49
|
+
render?: RenderFn<MaybeButtonOrLinkRenderState> | undefined
|
|
31
50
|
disabled?: undefined
|
|
32
51
|
type?: undefined
|
|
33
52
|
checked?: undefined
|
|
@@ -44,7 +63,7 @@ export type MaybeButtonOrLinkProps =
|
|
|
44
63
|
|
|
45
64
|
type InternalMaybeButtonOrLinkProps = MaybeButtonOrLinkProps &
|
|
46
65
|
InternalButtonOrLinkProps.DisableableProps &
|
|
47
|
-
InternalButtonOrLinkProps.BaseProps
|
|
66
|
+
Omit<InternalButtonOrLinkProps.BaseProps, 'render'>
|
|
48
67
|
|
|
49
68
|
/**
|
|
50
69
|
* Renders a `ButtonOrLink` if one of `href` or `onClick` is passed along with no `as`. Otherwise,
|
|
@@ -67,13 +86,15 @@ export function MaybeButtonOrLink(props: InternalMaybeButtonOrLinkProps) {
|
|
|
67
86
|
...otherElementProps
|
|
68
87
|
} = props
|
|
69
88
|
|
|
70
|
-
const
|
|
71
|
-
Element,
|
|
89
|
+
const renderState: OtherElementRenderState = {
|
|
72
90
|
category: 'other',
|
|
73
91
|
role: props.role ?? 'none',
|
|
74
92
|
disabled: false,
|
|
93
|
+
// The types lie to ensure there's a type error if consumers forget to spread `elementProps` on `Element`
|
|
94
|
+
Element: Element as OtherElementRenderState['Element'],
|
|
95
|
+
elementProps: {} as OtherElementRenderState['elementProps'],
|
|
75
96
|
}
|
|
76
97
|
|
|
77
|
-
return render ? render(otherElementProps,
|
|
98
|
+
return render ? render(otherElementProps, renderState) : <Element {...otherElementProps} />
|
|
78
99
|
}
|
|
79
100
|
}
|