@graphprotocol/gds-react 0.1.0 → 0.1.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/README.md +10 -11
  2. package/dist/GDSProvider.d.ts +6 -3
  3. package/dist/GDSProvider.d.ts.map +1 -1
  4. package/dist/GDSProvider.js +14 -6
  5. package/dist/GDSProvider.js.map +1 -1
  6. package/dist/components/Address.d.ts.map +1 -1
  7. package/dist/components/Address.js +8 -6
  8. package/dist/components/Address.js.map +1 -1
  9. package/dist/components/Avatar.d.ts +4 -2
  10. package/dist/components/Avatar.d.ts.map +1 -1
  11. package/dist/components/Avatar.js +12 -5
  12. package/dist/components/Avatar.js.map +1 -1
  13. package/dist/components/Breadcrumbs.parts.d.ts.map +1 -1
  14. package/dist/components/Breadcrumbs.parts.js +4 -4
  15. package/dist/components/Breadcrumbs.parts.js.map +1 -1
  16. package/dist/components/Button.js +11 -7
  17. package/dist/components/Button.js.map +1 -1
  18. package/dist/components/Card.js +3 -3
  19. package/dist/components/Card.js.map +1 -1
  20. package/dist/components/Checkbox.parts.js +1 -1
  21. package/dist/components/Checkbox.parts.js.map +1 -1
  22. package/dist/components/Chip.d.ts +6 -0
  23. package/dist/components/Chip.d.ts.map +1 -0
  24. package/dist/components/Chip.js +5 -0
  25. package/dist/components/Chip.js.map +1 -0
  26. package/dist/components/Chip.meta.d.ts +17 -0
  27. package/dist/components/Chip.meta.d.ts.map +1 -0
  28. package/dist/components/Chip.meta.js +22 -0
  29. package/dist/components/Chip.meta.js.map +1 -0
  30. package/dist/components/Chip.parts.d.ts +52 -0
  31. package/dist/components/Chip.parts.d.ts.map +1 -0
  32. package/dist/components/Chip.parts.js +122 -0
  33. package/dist/components/Chip.parts.js.map +1 -0
  34. package/dist/components/CodeBlock.parts.js +1 -1
  35. package/dist/components/CodeBlock.parts.js.map +1 -1
  36. package/dist/components/CopyButton.js +1 -1
  37. package/dist/components/CopyButton.js.map +1 -1
  38. package/dist/components/DescriptionList.parts.js +1 -1
  39. package/dist/components/Keyboard.js +1 -1
  40. package/dist/components/Label.d.ts.map +1 -1
  41. package/dist/components/Label.js +1 -1
  42. package/dist/components/Label.js.map +1 -1
  43. package/dist/components/Link.d.ts.map +1 -1
  44. package/dist/components/Link.js +1 -1
  45. package/dist/components/Link.js.map +1 -1
  46. package/dist/components/Menu.parts.d.ts.map +1 -1
  47. package/dist/components/Menu.parts.js +15 -10
  48. package/dist/components/Menu.parts.js.map +1 -1
  49. package/dist/components/Modal.parts.js +1 -1
  50. package/dist/components/Modal.parts.js.map +1 -1
  51. package/dist/components/OTCInput.js +1 -1
  52. package/dist/components/OTCInput.js.map +1 -1
  53. package/dist/components/Search.d.ts +16 -0
  54. package/dist/components/Search.d.ts.map +1 -0
  55. package/dist/components/Search.js +129 -0
  56. package/dist/components/Search.js.map +1 -0
  57. package/dist/components/Search.meta.d.ts +15 -0
  58. package/dist/components/Search.meta.d.ts.map +1 -0
  59. package/dist/components/Search.meta.js +24 -0
  60. package/dist/components/Search.meta.js.map +1 -0
  61. package/dist/components/SegmentedControl.parts.d.ts.map +1 -1
  62. package/dist/components/SegmentedControl.parts.js +16 -28
  63. package/dist/components/SegmentedControl.parts.js.map +1 -1
  64. package/dist/components/TabSet.parts.js +3 -3
  65. package/dist/components/TabSet.parts.js.map +1 -1
  66. package/dist/components/ToggleButton.d.ts +0 -2
  67. package/dist/components/ToggleButton.d.ts.map +1 -1
  68. package/dist/components/ToggleButton.js.map +1 -1
  69. package/dist/components/Tooltip.parts.d.ts.map +1 -1
  70. package/dist/components/Tooltip.parts.js +9 -5
  71. package/dist/components/Tooltip.parts.js.map +1 -1
  72. package/dist/components/base/ButtonOrLink.parts.d.ts +8 -5
  73. package/dist/components/base/ButtonOrLink.parts.d.ts.map +1 -1
  74. package/dist/components/base/ButtonOrLink.parts.js +42 -28
  75. package/dist/components/base/ButtonOrLink.parts.js.map +1 -1
  76. package/dist/components/base/Render.d.ts +1 -1
  77. package/dist/components/base/Render.d.ts.map +1 -1
  78. package/dist/components/base/Render.js +1 -1
  79. package/dist/components/base/Render.js.map +1 -1
  80. package/dist/components/index.d.ts +4 -0
  81. package/dist/components/index.d.ts.map +1 -1
  82. package/dist/components/index.js +4 -0
  83. package/dist/components/index.js.map +1 -1
  84. package/dist/hooks/useCSSProp.js +1 -1
  85. package/dist/hooks/useCSSProp.js.map +1 -1
  86. package/dist/hooks/useEffectWithRefDeps.js +1 -1
  87. package/dist/hooks/useEffectWithRefDeps.js.map +1 -1
  88. package/dist/hooks/useGDS.d.ts +1 -1
  89. package/dist/hooks/useNumberInput.js +1 -1
  90. package/dist/hooks/useNumberInput.js.map +1 -1
  91. package/dist/hooks/useStyleObserver.js +1 -1
  92. package/dist/hooks/useStyleObserver.js.map +1 -1
  93. package/dist/tailwind-plugin.d.ts.map +1 -1
  94. package/dist/tailwind-plugin.js +5 -0
  95. package/dist/tailwind-plugin.js.map +1 -1
  96. package/package.json +19 -18
  97. package/src/GDSProvider.tsx +31 -14
  98. package/src/components/Address.tsx +11 -6
  99. package/src/components/Avatar.tsx +25 -13
  100. package/src/components/Breadcrumbs.parts.tsx +4 -5
  101. package/src/components/Button.tsx +16 -6
  102. package/src/components/Card.tsx +3 -3
  103. package/src/components/Checkbox.parts.tsx +1 -1
  104. package/src/components/Chip.meta.ts +23 -0
  105. package/src/components/Chip.parts.tsx +329 -0
  106. package/src/components/Chip.tsx +7 -0
  107. package/src/components/CodeBlock.parts.tsx +3 -3
  108. package/src/components/CopyButton.tsx +1 -1
  109. package/src/components/DescriptionList.parts.tsx +1 -1
  110. package/src/components/Keyboard.tsx +1 -1
  111. package/src/components/Label.tsx +2 -1
  112. package/src/components/Link.tsx +2 -1
  113. package/src/components/Menu.parts.tsx +20 -24
  114. package/src/components/Modal.parts.tsx +1 -1
  115. package/src/components/OTCInput.tsx +1 -1
  116. package/src/components/Search.meta.ts +24 -0
  117. package/src/components/Search.tsx +238 -0
  118. package/src/components/SegmentedControl.parts.tsx +38 -40
  119. package/src/components/TabSet.parts.tsx +3 -3
  120. package/src/components/ToggleButton.tsx +0 -2
  121. package/src/components/Tooltip.parts.tsx +14 -4
  122. package/src/components/base/ButtonOrLink.parts.tsx +65 -34
  123. package/src/components/base/Render.tsx +1 -1
  124. package/src/components/index.ts +4 -0
  125. package/src/hooks/useCSSProp.ts +1 -1
  126. package/src/hooks/useEffectWithRefDeps.ts +1 -1
  127. package/src/hooks/useNumberInput.ts +1 -1
  128. package/src/hooks/useStyleObserver.ts +1 -1
  129. package/src/tailwind-plugin.ts +5 -0
@@ -0,0 +1,238 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useRef, type ComponentProps } from 'react'
4
+ import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
5
+
6
+ import type { GDSComponentProps } from '@graphprotocol/gds-css'
7
+ import { MagnifyingGlassIcon, XIcon } from '@graphprotocol/gds-react/icons'
8
+
9
+ import { useControlled, useCSSPropsPolyfill, useCSSState, useGDS } from '../hooks/index.ts'
10
+ import { cn, getCSSPropsAttributes, splitProps } from '../utils/index.ts'
11
+ import { Button } from './Button.tsx'
12
+ import { Keyboard } from './Keyboard.js'
13
+ import { SearchMeta } from './Search.meta.ts'
14
+
15
+ export interface SearchProps
16
+ extends
17
+ Omit<ComponentProps<'input'>, 'disabled' | 'onChange' | 'size' | 'type'>,
18
+ GDSComponentProps<typeof SearchMeta> {
19
+ value?: string | undefined
20
+ defaultValue?: string | undefined
21
+ onChange?: ((value: string) => void) | undefined
22
+ /**
23
+ * Key that focuses the search input when pressed. Set to `false` to disable.
24
+ *
25
+ * @default '/'
26
+ */
27
+ focusKey?: string | false | undefined
28
+ }
29
+
30
+ export function Search({
31
+ ref: passedRef,
32
+ size,
33
+ layout,
34
+ placeholder = 'Search...',
35
+ value: controlledValue,
36
+ defaultValue,
37
+ onChange,
38
+ focusKey = '/',
39
+ className,
40
+ style,
41
+ ...props
42
+ }: SearchProps) {
43
+ useGDS()
44
+
45
+ const { rootProps, nestedProps } = splitProps(props)
46
+
47
+ const inputRef = useRef<HTMLInputElement>(null)
48
+ const inputPassedRef = useMergedRefs(inputRef, passedRef)
49
+ const previousActiveElementRef = useRef<HTMLElement | null>(null)
50
+ const [value, setValue] = useControlled(controlledValue, defaultValue ?? '', onChange)
51
+
52
+ const [stateRef, state] = useCSSState({
53
+ pointer: undefined,
54
+ focus: undefined,
55
+ blank: value === '',
56
+ open: value !== '',
57
+ })
58
+ const [cssPropsPolyfillStateRef, cssPropsPolyfillAttributes, cssProps] = useCSSPropsPolyfill(
59
+ SearchMeta,
60
+ { size, layout },
61
+ { ref: stateRef, returnPropValues: { size, layout } },
62
+ )
63
+
64
+ useEffect(() => {
65
+ if (!focusKey) return
66
+
67
+ function handleKeyDown(event: KeyboardEvent) {
68
+ if (event.key !== focusKey) return
69
+
70
+ const input = inputRef.current
71
+ if (!input) return
72
+
73
+ // Don't focus if already focused on an interactive element
74
+ const activeElement = document.activeElement
75
+ if (
76
+ activeElement instanceof HTMLInputElement ||
77
+ activeElement instanceof HTMLTextAreaElement ||
78
+ (activeElement instanceof HTMLElement && activeElement.isContentEditable)
79
+ ) {
80
+ return
81
+ }
82
+
83
+ // Don't focus if the input has an inert/hidden ancestor
84
+ if (input.closest('[inert], [data-base-ui-inert], [aria-hidden="true"]')) {
85
+ return
86
+ }
87
+
88
+ event.preventDefault()
89
+ // Store the currently focused element before focusing the input
90
+ previousActiveElementRef.current = activeElement instanceof HTMLElement ? activeElement : null
91
+ input.focus()
92
+ input.select()
93
+ }
94
+
95
+ document.addEventListener('keydown', handleKeyDown)
96
+ return () => document.removeEventListener('keydown', handleKeyDown)
97
+ }, [focusKey])
98
+
99
+ return (
100
+ <div
101
+ ref={cssPropsPolyfillStateRef}
102
+ data-size={cssProps.size}
103
+ data-layout={cssProps.layout}
104
+ className={cn(
105
+ `gds-search root-flex flex-col u:max-w-full
106
+ u:hover:state-hover
107
+ u:active:state-active
108
+ u:data-[layout=compact]:data-[size=medium]:min-w-10
109
+ u:data-[layout=compact]:data-[size=small]:min-w-8
110
+ u:has-nested-not-blank/search-ref:expose-open
111
+ u:has-nested-blank/search-ref:expose-blank
112
+ u:has-nested-focus-visible/search-ref:expose-focus
113
+ u:has-nested-focus-visible/search-ref:expose-open
114
+ u:has-nested-active/search-clear-button:state-focus`,
115
+ className,
116
+ )}
117
+ {...state.exposedAttributes}
118
+ {...state.polyfillAttributes}
119
+ {...getCSSPropsAttributes(SearchMeta, { size, layout }, style)}
120
+ {...cssPropsPolyfillAttributes}
121
+ {...rootProps}
122
+ >
123
+ <div
124
+ className={`
125
+ flex h-(--height) grow overflow-clip text-form-text duration-300
126
+ @prop-size-small/search:[--height:--spacing(8)]
127
+ @prop-size-small/search:[--padding:--spacing(2)]
128
+ @prop-size-medium/search:[--height:--spacing(10)]
129
+ @prop-size-medium/search:[--padding:--spacing(2.5)]
130
+ @prop-layout-compact/search:transition-[width]
131
+ @prop-layout-compact/search:@state-not-open/search:w-(--height)
132
+ @prop-layout-compact/search:@prop-size-small/search:animate-[width-1_1ms]
133
+ @prop-layout-compact/search:@prop-size-medium/search:animate-[width-2_1ms]
134
+ `}
135
+ >
136
+ <div
137
+ className={`
138
+ h-full w-(--gds-search-input-width) grow border border-form-idle transition
139
+ @state-hover/search:border-form-hover
140
+ @state-focus/search:border-focus
141
+ @state-active/search:transition-none
142
+ @prop-size-small/search:rounded-6
143
+ @prop-size-medium/search:rounded-8
144
+ `}
145
+ />
146
+ <input
147
+ ref={inputPassedRef}
148
+ type="search"
149
+ placeholder={placeholder}
150
+ aria-label={placeholder || 'Search'}
151
+ value={value}
152
+ onChange={(event) => setValue(event.target.value)}
153
+ onKeyDown={(event) => {
154
+ nestedProps.onKeyDown?.(event)
155
+ if (event.defaultPrevented) return
156
+ if (event.key === 'Escape' && value === '') {
157
+ event.preventDefault()
158
+ inputRef.current?.blur()
159
+ // Restore focus to the previous element if it still exists in the DOM
160
+ if (
161
+ previousActiveElementRef.current &&
162
+ document.contains(previousActiveElementRef.current)
163
+ ) {
164
+ previousActiveElementRef.current.focus()
165
+ previousActiveElementRef.current = null
166
+ }
167
+ }
168
+ }}
169
+ className={`
170
+ nested/search-ref absolute end-0 top-0 size-full ps-(--padding) pe-(--height) outline-0
171
+ @state-not-focus/search:@state-blank/search:ps-[calc(var(--height)+var(--padding))]
172
+ @state-focus/search:placeholder:text-transparent
173
+ @prop-size-small/search:text-12
174
+ @prop-size-medium/search:text-14
175
+ @prop-layout-compact/search:@state-not-open/search:w-screen
176
+ @prop-layout-compact/search:@state-not-open/search:opacity-0
177
+ `}
178
+ {...nestedProps}
179
+ />
180
+ <div
181
+ className={`
182
+ pointer-events-none absolute inset-y-0 start-0 flex w-(--height) items-center justify-center
183
+ border-e border-form-idle transition
184
+ @state-hover/search:border-form-hover
185
+ @state-focus/search:hidden
186
+ @prop-layout-compact/search:@state-not-open/search:border-0
187
+ @prop-layout-compact/search:@state-open/search:@state-not-blank/search:hidden
188
+ @prop-layout-full/search:@state-not-blank/search:hidden
189
+ `}
190
+ >
191
+ <MagnifyingGlassIcon
192
+ alt=""
193
+ className={`
194
+ @prop-size-small/search:prop-size-3.5
195
+ @prop-size-medium/search:prop-size-4
196
+ `}
197
+ />
198
+ </div>
199
+ <div
200
+ className={`
201
+ pointer-events-none absolute inset-y-0 end-0 grid w-(--height) place-items-center
202
+ *:col-span-full *:row-span-full
203
+ `}
204
+ >
205
+ <Button
206
+ variant="tertiary"
207
+ size="xsmall"
208
+ tooltip={null}
209
+ tabIndex={-1}
210
+ onClick={() => {
211
+ setValue('')
212
+ inputRef.current?.focus()
213
+ }}
214
+ className={`
215
+ nested/search-clear-button pointer-events-auto size-6 var-[radius=var(--radius-4)]
216
+ @state-blank/search:hidden
217
+ @prop-layout-compact/search:@state-not-open/search:hidden
218
+ `}
219
+ >
220
+ <XIcon alt="Clear" />
221
+ </Button>
222
+ {focusKey ? (
223
+ <Keyboard
224
+ size="large"
225
+ className={`
226
+ @state-not-blank/search:hidden
227
+ @state-focus/search:hidden
228
+ @prop-layout-compact/search:@state-not-open/search:hidden
229
+ `}
230
+ >
231
+ {focusKey}
232
+ </Keyboard>
233
+ ) : null}
234
+ </div>
235
+ </div>
236
+ </div>
237
+ )
238
+ }
@@ -8,9 +8,9 @@ import {
8
8
  type KeyboardEvent,
9
9
  type MouseEvent,
10
10
  } from 'react'
11
- import { Radio } from '@base-ui-components/react/radio'
12
- import { RadioGroup } from '@base-ui-components/react/radio-group'
13
- import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'
11
+ import { Radio } from '@base-ui/react/radio'
12
+ import { RadioGroup } from '@base-ui/react/radio-group'
13
+ import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
14
14
 
15
15
  import type { GDSComponentProps } from '@graphprotocol/gds-css'
16
16
 
@@ -53,6 +53,8 @@ export function SegmentedControlRoot<T extends OptionValue>({
53
53
  children,
54
54
  ...props
55
55
  }: SegmentedControlProps<T>) {
56
+ useGDS()
57
+
56
58
  if (props['aria-label'] === undefined && props['aria-labelledby'] === undefined) {
57
59
  // oxlint-disable-next-line no-console
58
60
  console.warn(
@@ -140,22 +142,20 @@ export function SegmentedControlOption<T extends OptionValue>({
140
142
  children,
141
143
  ...props
142
144
  }: SegmentedControlOptionProps<T>) {
143
- useGDS()
145
+ const { rootProps, nestedProps } = splitProps(props)
144
146
 
145
147
  const buttonRef = useRef<HTMLSpanElement>(null)
146
148
  const buttonPassedRef = useMergedRefs(buttonRef, passedRef)
147
149
 
150
+ const autoValue = useAutoValue(children)
151
+ const value = passedValue !== undefined ? passedValue : autoValue
152
+
148
153
  const [stateRef, state] = useCSSState({
149
154
  pointer: undefined,
150
155
  checked: undefined,
151
156
  'is-toggle': undefined,
152
157
  })
153
158
 
154
- const autoValue = useAutoValue(children)
155
- const value = passedValue !== undefined ? passedValue : autoValue
156
-
157
- const { rootProps, nestedProps } = splitProps(props)
158
-
159
159
  const { onCollect, collectedContent } = useCollectedTooltip()
160
160
  let tooltipProps: Omit<TooltipProps, 'children'> = { content: collectedContent }
161
161
  if (tooltip !== undefined) {
@@ -187,33 +187,32 @@ export function SegmentedControlOption<T extends OptionValue>({
187
187
  }
188
188
 
189
189
  return (
190
- /**
191
- * This wrapper is necessary to ensure this component returns a single element (which some
192
- * Tailwind classes might assume) because `Radio.Root` renders a `<input type="radio">` as a
193
- * sibling of the button (which is a `<span>`, see https://github.com/mui/base-ui/pull/3205).
194
- * Ideally, we would make `Radio.Root` the root and it would render an internal component (e.g.
195
- * `SegmentedControlOptionButton`) that would itself render a wrapper `<div>` around the button
196
- * and the input (as well as calling `useCSSState` with a proper initial `checked` value, from
197
- * the `render` state), but that is not possible currently (see
198
- * https://github.com/mui/base-ui/issues/3143).
199
- */
200
- <div
201
- ref={stateRef}
202
- className={cn(
203
- `gds-segmented-control-option root-block
204
- state-[is-toggle=false]
205
- group-[:has([data-checked]):has(>:nth-child(2)):not(:has(>:nth-child(3)))]/segmented-control-options:state-[is-toggle]
206
- u:group-has-nested-hover/segmented-control--segmented-control-toggle:state-hover
207
- u:group-has-nested-active/segmented-control--segmented-control-toggle:state-active
208
- u:has-nested-hover/segmented-control-option-ref:state-hover
209
- u:has-nested-active/segmented-control-option-ref:state-active
210
- u:has-nested-[[data-checked]]/segmented-control-option-ref:state-checked`,
211
- className,
212
- )}
213
- {...state.polyfillAttributes}
214
- {...rootProps}
215
- >
216
- <Tooltip {...tooltipProps}>
190
+ <Tooltip {...tooltipProps}>
191
+ {/**
192
+ * This wrapper is necessary to ensure this component returns a single element (which some Tailwind
193
+ * classes might assume) because `Radio.Root` renders a `<input type="radio">` as a sibling of the
194
+ * button (actually a `<span>`). Ideally, we would make `Radio.Root` the root and it would render an
195
+ * internal component (e.g. `SegmentedControlOptionButton`) that would itself render a wrapper
196
+ * `<div>` around the button and the input (as well as calling `useCSSState` with a proper initial
197
+ * `checked` value, from the `render` state), but that is not possible currently (see
198
+ * https://github.com/mui/base-ui/issues/3143).
199
+ */}
200
+ <div
201
+ ref={stateRef}
202
+ className={cn(
203
+ `gds-segmented-control-option root-block
204
+ state-[is-toggle=false]
205
+ group-[:has([data-checked]):has(>:nth-child(2)):not(:has(>:nth-child(3)))]/segmented-control-options:state-[is-toggle]
206
+ u:group-has-nested-hover/segmented-control--segmented-control-toggle:state-hover
207
+ u:group-has-nested-active/segmented-control--segmented-control-toggle:state-active
208
+ u:has-nested-hover/segmented-control-option-ref:state-hover
209
+ u:has-nested-active/segmented-control-option-ref:state-active
210
+ u:has-nested-[[data-checked]]/segmented-control-option-ref:state-checked`,
211
+ className,
212
+ )}
213
+ {...state.polyfillAttributes}
214
+ {...rootProps}
215
+ >
217
216
  <Radio.Root
218
217
  ref={buttonPassedRef}
219
218
  value={value}
@@ -224,8 +223,7 @@ export function SegmentedControlOption<T extends OptionValue>({
224
223
  }
225
224
  }}
226
225
  className={`
227
- nested/segmented-control-option-ref block w-full cursor-pointer outline-0
228
- @state-checked/segmented-control-option:cursor-default
226
+ nested/segmented-control-option-ref block w-full outline-0
229
227
  @state-checked/segmented-control-option:[anchor-name:--gds-segmented-control-checked]
230
228
  `}
231
229
  {...nestedProps}
@@ -271,8 +269,8 @@ export function SegmentedControlOption<T extends OptionValue>({
271
269
  `}
272
270
  />
273
271
  </Radio.Root>
274
- </Tooltip>
275
- </div>
272
+ </div>
273
+ </Tooltip>
276
274
  )
277
275
  }
278
276
  SegmentedControlOption.displayName = 'SegmentedControl.Option'
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { createContext, useContext, type ComponentProps } from 'react'
4
- import { Tabs } from '@base-ui-components/react/tabs'
4
+ import { Tabs } from '@base-ui/react/tabs'
5
5
  import flattenChildren from 'react-keyed-flatten-children'
6
6
 
7
7
  import type { GDSComponentProps } from '@graphprotocol/gds-css'
@@ -118,8 +118,8 @@ export function TabSetTabs({
118
118
  className={`
119
119
  absolute inset-x-[anchor(inside)] bottom-0 h-px bg-(--border-color-brand-default) transition-[inset] duration-300
120
120
  [position-anchor:--gds-tab-set-underline]
121
- not-group-hover/tab-set-tabs:duration-0
122
121
  not-supports-[position-anchor:--foo]:hidden
122
+ group-not-hover/tab-set-tabs:duration-0
123
123
  group-has-focus-visible/tab-set-tabs:hidden
124
124
  `}
125
125
  />
@@ -265,7 +265,7 @@ export function TabSetPanels({
265
265
  u:group-data-[activation-direction=right]/tab-set:var-[exit-translate-x=-32px]
266
266
  u:has-nested-focus-visible/tab-set-panel:animate
267
267
  u:has-nested-focus-visible/tab-set-panel:outline
268
- u:has-nested-focus-visible/tab-set-panel:animate-outline-from-(--border-color-focus)`,
268
+ u:has-nested-focus-visible/tab-set-panel:animate-outline-from-focus`,
269
269
  transition && typeof transition === 'object' ? transition.className : undefined,
270
270
  className,
271
271
  )}
@@ -17,8 +17,6 @@ export interface ToggleButtonProps
17
17
  extends
18
18
  Omit<ButtonProps.ToggleButtonProps, 'onChange' | 'variant'>,
19
19
  GDSComponentProps<typeof ToggleButtonMeta> {
20
- checked?: boolean | undefined
21
- defaultChecked?: boolean | undefined
22
20
  onChange?: ((checked: boolean) => void) | undefined
23
21
  }
24
22
 
@@ -2,15 +2,17 @@
2
2
 
3
3
  import {
4
4
  createContext,
5
+ useCallback,
5
6
  useContext,
6
7
  useEffect,
8
+ useMemo,
7
9
  useRef,
8
10
  useState,
9
11
  type ComponentProps,
10
12
  type ReactElement,
11
13
  type ReactNode,
12
14
  } from 'react'
13
- import { Tooltip } from '@base-ui-components/react/tooltip'
15
+ import { Tooltip } from '@base-ui/react/tooltip'
14
16
 
15
17
  import { twToPx, type GDSComponentProps } from '@graphprotocol/gds-css'
16
18
 
@@ -83,9 +85,9 @@ export function TooltipRoot({
83
85
  const enabled = !cssProps.disabled && !hasEnabledTooltipAncestor && Boolean(content)
84
86
  const [open, ownSetOpen] = useControlled(controlledOpen, false, onOpenChange)
85
87
  const nestedSetOpen = useRef<(newOpen: boolean) => void>(null)
86
- const registerNestedSetOpen = (newNestedSetOpen: (newOpen: boolean) => void) => {
88
+ const registerNestedSetOpen = useCallback((newNestedSetOpen: (newOpen: boolean) => void) => {
87
89
  nestedSetOpen.current = newNestedSetOpen
88
- }
90
+ }, [])
89
91
  const setOpen = (newOpen: boolean) => {
90
92
  ownSetOpen(newOpen)
91
93
  /**
@@ -112,8 +114,16 @@ export function TooltipRoot({
112
114
  clearOpenTimeout()
113
115
  }
114
116
 
117
+ const tooltipContextValue = useMemo(
118
+ () => ({
119
+ enabled,
120
+ registerNestedSetOpen,
121
+ }),
122
+ [enabled, registerNestedSetOpen],
123
+ )
124
+
115
125
  return (
116
- <TooltipContext.Provider value={{ enabled, registerNestedSetOpen }}>
126
+ <TooltipContext.Provider value={tooltipContextValue}>
117
127
  <TooltipCollector onCollect={onCollect}>
118
128
  <Tooltip.Root
119
129
  disabled={!enabled}
@@ -4,6 +4,7 @@ import {
4
4
  createContext,
5
5
  isValidElement,
6
6
  useContext,
7
+ useMemo,
7
8
  useRef,
8
9
  useState,
9
10
  type ComponentProps,
@@ -13,7 +14,7 @@ import {
13
14
  type ReactElement,
14
15
  type ReactNode,
15
16
  } from 'react'
16
- import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'
17
+ import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
17
18
  import { mergeProps } from '@react-aria/utils'
18
19
  import { useButton } from 'react-aria'
19
20
 
@@ -67,19 +68,26 @@ export declare namespace InternalButtonOrLinkProps {
67
68
  linkComponent?: LinkComponent | undefined
68
69
  }
69
70
  interface ButtonProps
70
- extends BaseProps, DisableableProps, Omit<ComponentProps<'button'>, 'disabled'> {
71
+ extends
72
+ BaseProps,
73
+ DisableableProps,
74
+ Omit<ComponentProps<'button'>, 'defaultValue' | 'disabled'> {
71
75
  href?: undefined
72
76
  target?: undefined
73
77
  checked?: undefined
78
+ defaultChecked?: undefined
74
79
  }
75
- interface ToggleButtonProps extends Omit<ButtonProps, 'checked'> {
76
- /** Whether the button is in a checked/selected/pressed state. */
77
- checked?: boolean | 'indeterminate' | undefined
80
+ interface ToggleButtonProps extends Omit<ButtonProps, 'checked' | 'defaultChecked'> {
81
+ /** Whether the button is in a pressed/selected state. */
82
+ checked?: boolean | undefined
83
+ defaultChecked?: boolean | undefined
78
84
  }
79
- interface LinkProps extends BaseProps, DisableableProps, ComponentProps<'a'> {
85
+ interface LinkProps
86
+ extends BaseProps, DisableableProps, Omit<ComponentProps<'a'>, 'defaultValue'> {
80
87
  href: string
81
88
  type?: undefined
82
89
  checked?: undefined
90
+ defaultChecked?: undefined
83
91
  }
84
92
  }
85
93
 
@@ -191,11 +199,19 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
191
199
  role = 'button',
192
200
  type = 'button',
193
201
  checked,
202
+ defaultChecked,
194
203
  href: _href,
195
204
  target: _target,
196
205
  ...remainingProps
197
206
  } = nonBaseProps
198
207
 
208
+ if (defaultChecked !== undefined) {
209
+ // oxlint-disable-next-line no-console
210
+ console.warn(
211
+ '[ButtonOrLink] `defaultChecked` is not supported; use a controlled `checked` prop instead, or wrap `ButtonOrLink` in a component that manages the uncontrolled state',
212
+ )
213
+ }
214
+
199
215
  const Element = inline ? SpanButtonWithContext : ButtonWithContext
200
216
 
201
217
  const state: ButtonOrLinkState = {
@@ -207,22 +223,23 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
207
223
  checked,
208
224
  }
209
225
 
210
- const checkedAttribute = (() => {
211
- if (checked === undefined) {
212
- return {}
213
- }
214
- if (
215
- (role === 'option' || role === 'tab' || role === 'row' || role === 'gridcell') &&
216
- checked !== 'indeterminate'
217
- ) {
218
- return { 'aria-selected': checked }
219
- }
220
- const checkedOrMixed = checked === 'indeterminate' ? ('mixed' as const) : checked
221
- if (role === 'button') {
222
- return { 'aria-pressed': checkedOrMixed }
223
- }
224
- return { 'aria-checked': checkedOrMixed }
225
- })()
226
+ const checkedAttribute =
227
+ checked === undefined
228
+ ? {}
229
+ : ((checked: unknown) => {
230
+ if (
231
+ (role === 'option' || role === 'tab' || role === 'row' || role === 'gridcell') &&
232
+ checked !== 'indeterminate'
233
+ ) {
234
+ return { 'aria-selected': Boolean(checked) }
235
+ }
236
+ const checkedOrMixed =
237
+ checked === 'indeterminate' ? ('mixed' as const) : Boolean(checked)
238
+ if (role === 'button') {
239
+ return { 'aria-pressed': checkedOrMixed }
240
+ }
241
+ return { 'aria-checked': checkedOrMixed }
242
+ })(checked)
226
243
 
227
244
  let buttonProps: ComponentProps<typeof Element> = {
228
245
  role,
@@ -250,6 +267,7 @@ export const ButtonOrLinkRoot = (passedProps: InternalButtonOrLinkProps) => {
250
267
  rel = target === '_blank' ? 'noopener noreferrer' : undefined,
251
268
  type: _type,
252
269
  checked: _checked,
270
+ defaultChecked: _defaultChecked,
253
271
  ...remainingProps
254
272
  } = nonBaseProps
255
273
 
@@ -337,25 +355,38 @@ export const ButtonOrLinkConfig = ({
337
355
  children,
338
356
  }: ButtonOrLinkConfigProps) => {
339
357
  const ancestorConfig = useContext(ButtonOrLinkConfigContext)
358
+
340
359
  const onClick =
341
360
  passedOnClick && ancestorConfig?.onClick
342
- ? (
343
- event: MouseEvent<HTMLButtonElement & HTMLAnchorElement>,
344
- props: InternalButtonOrLinkProps,
345
- ) => {
346
- passedOnClick(event, props)
347
- if (event.isPropagationStopped()) return
348
- ancestorConfig.onClick!(event, props)
349
- }
361
+ ? (() => {
362
+ const ancestorOnClick = ancestorConfig.onClick
363
+ return (
364
+ event: MouseEvent<HTMLButtonElement & HTMLAnchorElement>,
365
+ props: InternalButtonOrLinkProps,
366
+ ) => {
367
+ passedOnClick(event, props)
368
+ if (event.isPropagationStopped()) return
369
+ ancestorOnClick(event, props)
370
+ }
371
+ })()
350
372
  : (passedOnClick ?? ancestorConfig?.onClick)
351
373
  const transformProps =
352
374
  passedTransformProps && ancestorConfig?.transformProps
353
- ? (props: InternalButtonOrLinkProps) => {
354
- return ancestorConfig.transformProps!(passedTransformProps(props))
355
- }
375
+ ? (() => {
376
+ const ancestorTransformProps = ancestorConfig.transformProps
377
+ return (props: InternalButtonOrLinkProps) => {
378
+ return ancestorTransformProps(passedTransformProps(props))
379
+ }
380
+ })()
356
381
  : (passedTransformProps ?? ancestorConfig?.transformProps)
382
+
383
+ const buttonOrLinkConfigContextValue = useMemo(() => {
384
+ if (!onClick && !transformProps) return null
385
+ return { onClick, transformProps }
386
+ }, [onClick, transformProps])
387
+
357
388
  return (
358
- <ButtonOrLinkConfigContext.Provider value={{ onClick, transformProps }}>
389
+ <ButtonOrLinkConfigContext.Provider value={buttonOrLinkConfigContextValue}>
359
390
  {children}
360
391
  </ButtonOrLinkConfigContext.Provider>
361
392
  )
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import type { ComponentProps, ElementType } from 'react'
4
- import { useRender } from '@base-ui-components/react/use-render'
4
+ import { useRender } from '@base-ui/react/use-render'
5
5
 
6
6
  export type RenderProps<
7
7
  T extends ElementType = 'span',
@@ -14,6 +14,8 @@ export { ButtonGroup, type ButtonGroupProps } from './ButtonGroup.tsx'
14
14
  export { ButtonGroupMeta } from './ButtonGroup.meta.ts'
15
15
  export { Card, type CardProps } from './Card.tsx'
16
16
  export { CardMeta } from './Card.meta.ts'
17
+ export { Chip, type ChipProps, type ChipGroupProps } from './Chip.tsx'
18
+ export { ChipMeta, ChipGroupMeta } from './Chip.meta.ts'
17
19
  export { Cluster, type ClusterProps } from './Cluster.tsx'
18
20
  export { ClusterMeta } from './Cluster.meta.ts'
19
21
  export { CodeBlock, type CodeBlockProps, type CodeBlockTabsProps } from './CodeBlock.tsx'
@@ -63,6 +65,8 @@ export { OTCInput, type OTCInputProps } from './OTCInput.tsx'
63
65
  export { OTCInputMeta } from './OTCInput.meta.ts'
64
66
  export { Radio, type RadioAreaProps, type RadioGroupProps, type RadioProps } from './Radio.tsx'
65
67
  export { RadioMeta } from './Radio.meta.ts'
68
+ export { Search, type SearchProps } from './Search.tsx'
69
+ export { SearchMeta } from './Search.meta.ts'
66
70
  export {
67
71
  SegmentedControl,
68
72
  type SegmentedControlOptionProps,
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useRef, type Ref, type RefCallback } from 'react'
2
- import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'
2
+ import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
3
3
 
4
4
  import {
5
5
  parseCSSPropValue,
@@ -6,7 +6,7 @@ import {
6
6
  type Ref,
7
7
  type RefCallback,
8
8
  } from 'react'
9
- import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'
9
+ import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
10
10
  import { useCustomCompareEffect, useCustomCompareMemo, type EffectHook } from '@react-hookz/web'
11
11
  import { isEqual } from '@ver0/deep-equal'
12
12
 
@@ -1,5 +1,5 @@
1
1
  import { useEffect, useMemo, useRef, useState, type KeyboardEvent, type Ref } from 'react'
2
- import { useMergedRefs } from '@base-ui-components/utils/useMergedRefs'
2
+ import { useMergedRefs } from '@base-ui/utils/useMergedRefs'
3
3
 
4
4
  import {
5
5
  bigIntToNumber,