@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.
Files changed (129) hide show
  1. package/dist/GDSContext.d.ts +13 -0
  2. package/dist/GDSContext.d.ts.map +1 -0
  3. package/dist/GDSContext.js +4 -0
  4. package/dist/GDSContext.js.map +1 -0
  5. package/dist/GDSProvider.d.ts +1 -9
  6. package/dist/GDSProvider.d.ts.map +1 -1
  7. package/dist/GDSProvider.js +4 -3
  8. package/dist/GDSProvider.js.map +1 -1
  9. package/dist/components/Avatar.d.ts.map +1 -1
  10. package/dist/components/Avatar.js +2 -2
  11. package/dist/components/Avatar.js.map +1 -1
  12. package/dist/components/Breadcrumbs.parts.js +1 -1
  13. package/dist/components/Breadcrumbs.parts.js.map +1 -1
  14. package/dist/components/Button.d.ts.map +1 -1
  15. package/dist/components/Button.js +69 -69
  16. package/dist/components/Button.js.map +1 -1
  17. package/dist/components/Card.js +2 -2
  18. package/dist/components/Card.js.map +1 -1
  19. package/dist/components/CodeBlock.d.ts +1 -1
  20. package/dist/components/CodeBlock.parts.d.ts +1 -1
  21. package/dist/components/CopyButton.d.ts +1 -1
  22. package/dist/components/CopyButton.d.ts.map +1 -1
  23. package/dist/components/CopyButton.js +46 -19
  24. package/dist/components/CopyButton.js.map +1 -1
  25. package/dist/components/Input.js +2 -2
  26. package/dist/components/Input.js.map +1 -1
  27. package/dist/components/Link.js +2 -2
  28. package/dist/components/Link.js.map +1 -1
  29. package/dist/components/Menu.parts.d.ts +4 -5
  30. package/dist/components/Menu.parts.d.ts.map +1 -1
  31. package/dist/components/Menu.parts.js +52 -45
  32. package/dist/components/Menu.parts.js.map +1 -1
  33. package/dist/components/Modal.parts.d.ts.map +1 -1
  34. package/dist/components/Modal.parts.js +17 -21
  35. package/dist/components/Modal.parts.js.map +1 -1
  36. package/dist/components/Pane.d.ts +9 -0
  37. package/dist/components/Pane.d.ts.map +1 -0
  38. package/dist/components/Pane.js +8 -0
  39. package/dist/components/Pane.js.map +1 -0
  40. package/dist/components/Pane.meta.d.ts +20 -0
  41. package/dist/components/Pane.meta.d.ts.map +1 -0
  42. package/dist/components/Pane.meta.js +30 -0
  43. package/dist/components/Pane.meta.js.map +1 -0
  44. package/dist/components/Pane.parts.d.ts +77 -0
  45. package/dist/components/Pane.parts.d.ts.map +1 -0
  46. package/dist/components/Pane.parts.js +412 -0
  47. package/dist/components/Pane.parts.js.map +1 -0
  48. package/dist/components/Search.js +1 -1
  49. package/dist/components/Tooltip.parts.d.ts +13 -4
  50. package/dist/components/Tooltip.parts.d.ts.map +1 -1
  51. package/dist/components/Tooltip.parts.js +51 -63
  52. package/dist/components/Tooltip.parts.js.map +1 -1
  53. package/dist/components/base/ButtonOrLink.d.ts +1 -1
  54. package/dist/components/base/ButtonOrLink.d.ts.map +1 -1
  55. package/dist/components/base/ButtonOrLink.parts.d.ts +10 -3
  56. package/dist/components/base/ButtonOrLink.parts.d.ts.map +1 -1
  57. package/dist/components/base/ButtonOrLink.parts.js +27 -35
  58. package/dist/components/base/ButtonOrLink.parts.js.map +1 -1
  59. package/dist/components/base/MaybeButtonOrLink.d.ts +19 -2
  60. package/dist/components/base/MaybeButtonOrLink.d.ts.map +1 -1
  61. package/dist/components/base/MaybeButtonOrLink.js +5 -3
  62. package/dist/components/base/MaybeButtonOrLink.js.map +1 -1
  63. package/dist/components/base/Presence.d.ts +157 -0
  64. package/dist/components/base/Presence.d.ts.map +1 -0
  65. package/dist/components/base/Presence.js +808 -0
  66. package/dist/components/base/Presence.js.map +1 -0
  67. package/dist/components/base/index.d.ts +1 -0
  68. package/dist/components/base/index.d.ts.map +1 -1
  69. package/dist/components/base/index.js +1 -0
  70. package/dist/components/base/index.js.map +1 -1
  71. package/dist/components/index.d.ts +2 -0
  72. package/dist/components/index.d.ts.map +1 -1
  73. package/dist/components/index.js +2 -0
  74. package/dist/components/index.js.map +1 -1
  75. package/dist/hooks/useCSSProp.js +1 -1
  76. package/dist/hooks/useCSSProp.js.map +1 -1
  77. package/dist/hooks/useControlled.d.ts.map +1 -1
  78. package/dist/hooks/useControlled.js +6 -4
  79. package/dist/hooks/useControlled.js.map +1 -1
  80. package/dist/hooks/useGDS.js +1 -1
  81. package/dist/hooks/useGDS.js.map +1 -1
  82. package/dist/hooks/useStyleObserver.js +1 -1
  83. package/dist/hooks/useStyleObserver.js.map +1 -1
  84. package/dist/tailwind-plugin.d.ts.map +1 -1
  85. package/dist/tailwind-plugin.js +3 -0
  86. package/dist/tailwind-plugin.js.map +1 -1
  87. package/dist/utils/InlineCounter.d.ts +3 -0
  88. package/dist/utils/InlineCounter.d.ts.map +1 -0
  89. package/dist/utils/InlineCounter.js +7 -0
  90. package/dist/utils/InlineCounter.js.map +1 -0
  91. package/dist/utils/RenderCount.d.ts +3 -0
  92. package/dist/utils/RenderCount.d.ts.map +1 -0
  93. package/dist/utils/RenderCount.js +7 -0
  94. package/dist/utils/RenderCount.js.map +1 -0
  95. package/dist/utils/index.d.ts +2 -0
  96. package/dist/utils/index.d.ts.map +1 -1
  97. package/dist/utils/index.js +2 -0
  98. package/dist/utils/index.js.map +1 -1
  99. package/package.json +14 -14
  100. package/src/GDSContext.ts +16 -0
  101. package/src/GDSProvider.tsx +20 -31
  102. package/src/components/Avatar.tsx +3 -2
  103. package/src/components/Breadcrumbs.parts.tsx +1 -1
  104. package/src/components/Button.tsx +113 -107
  105. package/src/components/Card.tsx +2 -2
  106. package/src/components/CopyButton.tsx +49 -25
  107. package/src/components/Input.tsx +1 -1
  108. package/src/components/Link.tsx +2 -2
  109. package/src/components/Menu.parts.tsx +78 -73
  110. package/src/components/Modal.parts.tsx +26 -31
  111. package/src/components/Pane.meta.ts +31 -0
  112. package/src/components/Pane.parts.tsx +713 -0
  113. package/src/components/Pane.tsx +17 -0
  114. package/src/components/Search.tsx +1 -1
  115. package/src/components/Tooltip.parts.tsx +95 -80
  116. package/src/components/base/ButtonOrLink.parts.tsx +71 -51
  117. package/src/components/base/ButtonOrLink.tsx +1 -0
  118. package/src/components/base/MaybeButtonOrLink.tsx +26 -5
  119. package/src/components/base/Presence.tsx +1375 -0
  120. package/src/components/base/index.ts +1 -0
  121. package/src/components/index.ts +10 -0
  122. package/src/hooks/useCSSProp.ts +1 -1
  123. package/src/hooks/useControlled.ts +16 -8
  124. package/src/hooks/useGDS.ts +1 -1
  125. package/src/hooks/useStyleObserver.ts +1 -1
  126. package/src/tailwind-plugin.ts +3 -0
  127. package/src/utils/InlineCounter.tsx +17 -0
  128. package/src/utils/RenderCount.tsx +7 -0
  129. package/src/utils/index.ts +2 -0
@@ -87,117 +87,123 @@ export function Button({
87
87
  const replaceChildrenWithLoading = loading && !addonBefore && !addonAfter
88
88
 
89
89
  return (
90
- <Tooltip {...(isReactNode(tooltip) ? { content: tooltip } : tooltip)}>
91
- <ButtonOrLink
92
- ref={cssPropsPolyfillStatePassedRef}
93
- disabled={disabled || (loading ? 'focusable' : false)}
94
- aria-busy={loading || undefined}
95
- data-variant={cssProps.variant}
96
- data-hide-label={cssProps.hideLabel || undefined}
97
- className={cn(
98
- `gds-button root-flex flex-col u:size-max u:max-w-full u:outline-0
99
- u:checked:state-checked
100
- u:hover:state-hover
101
- u:focus-visible:state-focus
102
- u:active:state-active
103
- u:disabled:state-disabled
104
- u:disabled:state-idle
105
- ${/* Determine whether the button is "addon-only" (`0` means no and `auto` means yes; we can improve this when `if()` is supported, e.g. `min-w-[if(style(--gds-state-addon-only):auto;else:0)]`) */ ''}
106
- state-[addon-only=0]
107
- data-hide-label:state-[addon-only=auto]
108
- not-has-nested/button-children:state-[addon-only=auto]
109
- has-nested-has-addon-compatible-only/button-children:state-[addon-only=auto]
110
- ${/* Ensure an addon-only button does not get squished in a flex container (like a button group) */ ''}
111
- u:min-w-(--gds-state-addon-only)
112
- ${/* Ensure a button in a horizontal button group grows by default, except if it is addon-only */ ''}
113
- u:@prop-orientation-horizontal/button-group:has-nested-not-has-addon-compatible-only/button-children:grow
114
- ${/* But if the button group is `fullWidth`, then all the buttons it contains should be equal width, regardless of their content */ ''}
115
- u:@prop-orientation-horizontal/button-group:@prop-full-width-true/button-group:grow
116
- u:@prop-orientation-horizontal/button-group:@prop-full-width-true/button-group:basis-0
117
- ${/* In a button group, the group itself should be the stacking context */ ''}
118
- u:in-button-group:isolation-auto
119
- ${/* Extend the tap area of a naked button */ ''}
120
- data-[variant=naked]:before:absolute
121
- data-[variant=naked]:before:-inset-2`,
122
- className,
123
- )}
124
- {...state.polyfillAttributes}
125
- {...getCSSPropsAttributes(ButtonMeta, { variant, size, hideLabel }, style)}
126
- {...cssPropsPolyfillAttributes}
127
- {...props}
128
- >
129
- {/* Prevent the extended tap area of a naked button from overlapping the tap area of another naked button in the same group */}
130
- <span
131
- className={`
132
- absolute inset-[calc(-1*var(--gds-button-group-gap)/2)] z-10
133
- not-in-button-group:hidden
134
- @prop-not-variant-naked/button:hidden
135
- `}
136
- />
137
- <span
138
- className={`
139
- nested/button-paint pointer-events-none flex h-(--height) max-w-full min-w-full grow items-center justify-center
140
- rounded-(--gds-button-radius) border-(length:--border-width) border-(--gds-button-border)
141
- bg-(--gds-button-bg) text-(--gds-button-fg) transition [--border-width:1px]
142
- @state-[addon-only=auto]/button:w-(--height)
143
- @prop-variant-naked/button:transition-[color]
144
- @prop-variant-naked/button:[--border-width:0px]
145
- @prop-size-xsmall/button:px-1.25
146
- @prop-size-xsmall/button:text-12
147
- @prop-size-xsmall/button:[--height:--spacing(7)]
148
- @prop-size-small/button:px-1.75
149
- @prop-size-small/button:text-12
150
- @prop-size-small/button:[--height:--spacing(8)]
151
- @prop-size-medium/button:px-1.75
152
- @prop-size-medium/button:text-14
153
- @prop-size-medium/button:[--height:--spacing(10)]
154
- @prop-size-large/button:px-2.75
155
- @prop-size-large/button:text-16
156
- @prop-size-large/button:[--height:--spacing(12)]
157
- u:*:transition-opacity
158
- u:*:@state-disabled/button:opacity-disabled
159
- u:*:@state-disabled/button:grayscale
160
- u:@prop-size-xsmall/button:addon-small
161
- u:@prop-size-small/button:addon-small
162
- u:@prop-size-medium/button:addon-medium
163
- u:@prop-size-large/button:addon-large
164
- i:@state-active/button:transition-none
165
- i:@prop-variant-naked/button:px-0
166
- i:@prop-variant-naked/button:[--height:1lh]
167
- `}
90
+ <ButtonOrLink
91
+ ref={cssPropsPolyfillStatePassedRef}
92
+ disabled={disabled || (loading ? 'focusable' : false)}
93
+ aria-busy={loading || undefined}
94
+ data-variant={cssProps.variant}
95
+ data-hide-label={cssProps.hideLabel || undefined}
96
+ className={cn(
97
+ `gds-button root-flex flex-col u:size-max u:max-w-full u:outline-0
98
+ u:checked:state-checked
99
+ u:hover:state-hover
100
+ u:focus-visible:state-focus
101
+ u:active:state-active
102
+ u:disabled:state-disabled
103
+ u:disabled:state-idle
104
+ ${/* Determine whether the button is "addon-only" (`0` means no and `auto` means yes; we can improve this when `if()` is supported, e.g. `min-w-[if(style(--gds-state-addon-only):auto;else:0)]`) */ ''}
105
+ state-[addon-only=0]
106
+ data-hide-label:state-[addon-only=auto]
107
+ not-has-nested/button-children:state-[addon-only=auto]
108
+ has-nested-has-addon-compatible-only/button-children:state-[addon-only=auto]
109
+ ${/* Ensure an addon-only button does not get squished in a flex container (like a button group) */ ''}
110
+ u:min-w-(--gds-state-addon-only)
111
+ ${/* Ensure a button in a horizontal button group grows by default, except if it is addon-only */ ''}
112
+ u:@prop-orientation-horizontal/button-group:has-nested-not-has-addon-compatible-only/button-children:grow
113
+ ${/* But if the button group is `fullWidth`, then all the buttons it contains should be equal width, regardless of their content */ ''}
114
+ u:@prop-orientation-horizontal/button-group:@prop-full-width-true/button-group:grow
115
+ u:@prop-orientation-horizontal/button-group:@prop-full-width-true/button-group:basis-0
116
+ ${/* In a button group, the group itself should be the stacking context */ ''}
117
+ u:in-button-group:isolation-auto
118
+ ${/* Extend the tap area of a naked button */ ''}
119
+ data-[variant=naked]:before:absolute
120
+ data-[variant=naked]:before:-inset-2`,
121
+ className,
122
+ )}
123
+ {...state.polyfillAttributes}
124
+ {...getCSSPropsAttributes(ButtonMeta, { variant, size, hideLabel }, style)}
125
+ {...cssPropsPolyfillAttributes}
126
+ {...props}
127
+ render={(renderProps, { Element, elementProps }) => (
128
+ <Tooltip
129
+ {...(isReactNode(tooltip) ? { content: tooltip } : tooltip)}
130
+ triggerProps={renderProps}
168
131
  >
169
- {/* Focus ring (rendered separately for better spacing in naked buttons) */}
170
- <span
171
- className={`
172
- absolute -inset-px rounded-inherit opacity-100 filter-none
173
- @state-focus/button:outline
174
- @prop-variant-naked/button:-inset-0.5
175
- `}
176
- />
177
- {addonBefore ? <ButtonAddon side="before">{addonBefore}</ButtonAddon> : null}
178
- {children ? (
132
+ <Element {...elementProps}>
133
+ {/* Prevent the extended tap area of a naked button from overlapping the tap area of another naked button in the same group */}
134
+ <span
135
+ className={`
136
+ absolute inset-[calc(-1*var(--gds-button-group-gap)/2)] z-10
137
+ not-in-button-group:hidden
138
+ @prop-not-variant-naked/button:hidden
139
+ `}
140
+ />
179
141
  <span
180
- data-loading={replaceChildrenWithLoading || undefined}
181
- className={cn(
182
- `nested/button-children
183
- truncate
184
- data-loading:invisible
185
- @prop-hide-label-true/button:not-has-addon-compatible:sr-only
186
- u:@prop-size-xsmall/button:px-0.5
187
- u:@prop-size-small/button:px-0.5
188
- u:@prop-size-medium/button:px-1
189
- u:@prop-size-large/button:px-1
190
- u:i:@state-[addon-only=auto]/button:px-0`,
191
- )}
142
+ className={`
143
+ nested/button-paint pointer-events-none flex h-(--height) max-w-full min-w-full grow items-center justify-center
144
+ rounded-(--gds-button-radius) border-(length:--border-width) border-(--gds-button-border)
145
+ bg-(--gds-button-bg) text-(--gds-button-fg) transition [--border-width:1px]
146
+ @state-[addon-only=auto]/button:w-(--height)
147
+ @prop-variant-naked/button:transition-[color]
148
+ @prop-variant-naked/button:[--border-width:0px]
149
+ @prop-size-xsmall/button:px-1.25
150
+ @prop-size-xsmall/button:text-12
151
+ @prop-size-xsmall/button:[--height:--spacing(7)]
152
+ @prop-size-small/button:px-1.75
153
+ @prop-size-small/button:text-12
154
+ @prop-size-small/button:[--height:--spacing(8)]
155
+ @prop-size-medium/button:px-1.75
156
+ @prop-size-medium/button:text-14
157
+ @prop-size-medium/button:[--height:--spacing(10)]
158
+ @prop-size-large/button:px-2.75
159
+ @prop-size-large/button:text-16
160
+ @prop-size-large/button:[--height:--spacing(12)]
161
+ u:*:transition-opacity
162
+ u:*:@state-disabled/button:opacity-disabled
163
+ u:*:@state-disabled/button:grayscale
164
+ u:@prop-size-xsmall/button:addon-small
165
+ u:@prop-size-small/button:addon-small
166
+ u:@prop-size-medium/button:addon-medium
167
+ u:@prop-size-large/button:addon-large
168
+ i:@state-active/button:transition-none
169
+ i:@prop-variant-naked/button:px-0
170
+ i:@prop-variant-naked/button:[--height:1lh]
171
+ `}
192
172
  >
193
- <Tooltip.Collector>{children}</Tooltip.Collector>
173
+ {/* Focus ring (rendered separately for better spacing in naked buttons) */}
174
+ <span
175
+ className={`
176
+ absolute -inset-px rounded-inherit opacity-100 filter-none
177
+ @state-focus/button:outline
178
+ @prop-variant-naked/button:-inset-0.5
179
+ `}
180
+ />
181
+ {addonBefore ? <ButtonAddon side="before">{addonBefore}</ButtonAddon> : null}
182
+ {children ? (
183
+ <span
184
+ data-loading={replaceChildrenWithLoading || undefined}
185
+ className={cn(
186
+ `nested/button-children
187
+ truncate
188
+ data-loading:invisible
189
+ @prop-hide-label-true/button:not-has-addon-compatible:sr-only
190
+ u:@prop-size-xsmall/button:px-0.5
191
+ u:@prop-size-small/button:px-0.5
192
+ u:@prop-size-medium/button:px-1
193
+ u:@prop-size-large/button:px-1
194
+ u:i:@state-[addon-only=auto]/button:px-0`,
195
+ )}
196
+ >
197
+ <Tooltip.Collector>{children}</Tooltip.Collector>
198
+ </span>
199
+ ) : null}
200
+ {replaceChildrenWithLoading ? loadingIcon : null}
201
+ {addonAfter ? <ButtonAddon side="after">{addonAfter}</ButtonAddon> : null}
194
202
  </span>
195
- ) : null}
196
- {replaceChildrenWithLoading ? loadingIcon : null}
197
- {addonAfter ? <ButtonAddon side="after">{addonAfter}</ButtonAddon> : null}
198
- </span>
199
- </ButtonOrLink>
200
- </Tooltip>
203
+ </Element>
204
+ </Tooltip>
205
+ )}
206
+ />
201
207
  )
202
208
  }
203
209
 
@@ -122,7 +122,7 @@ export function Card({
122
122
  {...state.polyfillAttributes}
123
123
  {...nestedProps}
124
124
  {...(as !== undefined && ({ as: 'span' } as Record<string, unknown>))}
125
- render={(renderProps, { Element, category, target }) => {
125
+ render={(renderProps, { Element, elementProps, category, target }) => {
126
126
  const renderChildrenOutside = interactiveContent === true && category !== 'other'
127
127
  const contentId =
128
128
  renderChildrenOutside && !('aria-label' in props || 'aria-labelledby' in props)
@@ -130,7 +130,7 @@ export function Card({
130
130
  : undefined
131
131
  return (
132
132
  <>
133
- <Element aria-labelledby={contentId} {...renderProps}>
133
+ <Element aria-labelledby={contentId} {...renderProps} {...elementProps}>
134
134
  {/**
135
135
  * This `span` ensures that the rounded corners are always hoverable/clickable, instead of only when
136
136
  * `children` includes an absolutely-positioned element that fills the card, and not even
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { useRef, useState } from 'react'
4
+ import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
4
5
 
5
6
  import type { GDSComponentProps } from '@graphprotocol/gds-css'
6
7
  import { CopyInteractiveIcon } from '@graphprotocol/gds-react/icons'
@@ -9,7 +10,6 @@ import { useGDS } from '../hooks/index.ts'
9
10
  import { cn, isReactNode } from '../utils/index.ts'
10
11
  import { Button, type ButtonProps } from './Button.tsx'
11
12
  import type { CopyButtonMeta } from './CopyButton.meta.ts'
12
- import { TooltipMeta } from './Tooltip.meta.ts'
13
13
  import { Tooltip } from './Tooltip.tsx'
14
14
 
15
15
  export interface CopyButtonProps
@@ -22,57 +22,81 @@ export interface CopyButtonProps
22
22
  }
23
23
 
24
24
  export function CopyButton({
25
+ ref: passedRef,
25
26
  value,
26
27
  addonAfter,
27
28
  tooltip,
28
29
  onCopy,
29
- onClick: passedOnClick,
30
30
  className,
31
31
  children,
32
32
  ...props
33
33
  }: CopyButtonProps) {
34
34
  useGDS()
35
35
 
36
- const [status, privateSetStatus] = useState<'idle' | 'copied' | 'copied-exit'>('idle')
37
- const timeoutRef = useRef<number>(undefined)
36
+ const buttonRef = useRef<HTMLButtonElement>(null)
37
+ const buttonPassedRef = useMergedRefs(buttonRef, passedRef)
38
38
 
39
- const setStatus = (newStatus: typeof status) => {
40
- window.clearTimeout(timeoutRef.current)
41
- privateSetStatus(newStatus)
42
- if (newStatus === 'copied') {
43
- timeoutRef.current = window.setTimeout(() => setStatus('copied-exit'), 1000)
44
- } else if (newStatus === 'copied-exit') {
45
- timeoutRef.current = window.setTimeout(() => setStatus('idle'), 150)
39
+ const [copied, privateSetCopied] = useState(false)
40
+ const copiedTimeoutRef = useRef<number>(undefined)
41
+
42
+ const setCopied = (newCopied: boolean) => {
43
+ window.clearTimeout(copiedTimeoutRef.current)
44
+ privateSetCopied(newCopied)
45
+ if (newCopied) {
46
+ setTooltipOpen(true)
47
+ copiedTimeoutRef.current = window.setTimeout(() => setCopied(false), 1000)
48
+ } else {
49
+ // Ensure the original tooltip is shown again when leaving the button and coming back while copied
50
+ copiedTimeoutRef.current = window.setTimeout(() => {
51
+ if (!tooltipOpenRef.current && buttonRef.current?.matches(':hover, :focus-visible')) {
52
+ setTooltipOpen(true)
53
+ }
54
+ }, 150)
46
55
  }
47
56
  }
48
57
 
49
58
  const hasChildren = children != null
50
- const copyIcon = <CopyInteractiveIcon copied={status === 'copied'} />
51
- const tooltipCSSProps =
52
- tooltip && !isReactNode(tooltip)
53
- ? Object.fromEntries(
54
- Object.entries(tooltip).filter(([propName]) => propName in TooltipMeta.cssProps),
55
- )
56
- : {}
59
+ const copyIcon = <CopyInteractiveIcon copied={copied} />
60
+ const tooltipProps = isReactNode(tooltip) ? { content: tooltip } : tooltip
61
+ const [tooltipOpen, setTooltipOpen] = useState(false)
62
+ const tooltipOpenRef = useRef(tooltipOpen)
63
+ tooltipOpenRef.current = tooltipOpen
57
64
 
58
65
  return (
59
66
  <Tooltip
67
+ {...tooltipProps}
60
68
  content="Copied!"
61
- {...(status !== 'idle' ? { open: status === 'copied' } : { disabled: true })}
62
- {...tooltipCSSProps}
69
+ {...(copied
70
+ ? {
71
+ open: true,
72
+ onOpenChange: (newOpen, reason) => {
73
+ if (reason === 'disabled') return
74
+ setTooltipOpen(newOpen)
75
+ },
76
+ className: cn('i:prop-disabled-false', tooltipProps?.className),
77
+ }
78
+ : { disabled: true })}
63
79
  >
64
80
  <Button
81
+ ref={buttonPassedRef}
65
82
  addonAfter={addonAfter !== undefined ? addonAfter : hasChildren ? copyIcon : undefined}
66
- tooltip={tooltip}
83
+ tooltip={{
84
+ open: tooltipOpen,
85
+ onOpenChange: (newOpen) => {
86
+ if (copied) return
87
+ setTooltipOpen(newOpen)
88
+ },
89
+ ...tooltipProps,
90
+ }}
91
+ className={cn('gds-copy-button', className)}
92
+ {...props}
67
93
  onClick={async (event) => {
68
- passedOnClick?.(event)
94
+ props.onClick?.(event)
69
95
  if (event.defaultPrevented) return
70
96
  await navigator.clipboard.writeText(value)
71
97
  onCopy?.(value)
72
- setStatus('copied')
98
+ setCopied(true)
73
99
  }}
74
- className={cn('gds-copy-button', className)}
75
- {...props}
76
100
  >
77
101
  {hasChildren ? children : copyIcon}
78
102
  </Button>
@@ -184,12 +184,12 @@ export function Input({
184
184
 
185
185
  return (
186
186
  <Render
187
- {...renderProps}
188
187
  render={renderInput ?? <Field.Box>{inputWithUnitAndAction}</Field.Box>}
189
188
  state={{
190
189
  inputProps,
191
190
  inputWithUnitAndAction,
192
191
  }}
192
+ {...renderProps}
193
193
  />
194
194
  )
195
195
  }}
@@ -84,7 +84,7 @@ export function Link({
84
84
  {...getCSSPropsAttributes(LinkMeta, { variant }, style)}
85
85
  {...cssPropsPolyfillAttributes}
86
86
  {...props}
87
- render={(renderProps, { Element, target }) => {
87
+ render={(renderProps, { Element, elementProps, target }) => {
88
88
  const addonAfter: AddonValue =
89
89
  passedAddonAfter !== undefined ? (
90
90
  passedAddonAfter
@@ -92,7 +92,7 @@ export function Link({
92
92
  <ArrowUpRightInteractiveIcon />
93
93
  ) : undefined
94
94
  return (
95
- <Element {...renderProps}>
95
+ <Element {...renderProps} {...elementProps}>
96
96
  <span
97
97
  className={`
98
98
  nested/link-paint -mx-1 rounded-[max(--spacing(1),0.25em)] p-1 whitespace-nowrap transition