@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.
- package/README.md +10 -11
- package/dist/GDSProvider.d.ts +6 -3
- package/dist/GDSProvider.d.ts.map +1 -1
- package/dist/GDSProvider.js +14 -6
- package/dist/GDSProvider.js.map +1 -1
- package/dist/components/Address.d.ts.map +1 -1
- package/dist/components/Address.js +8 -6
- package/dist/components/Address.js.map +1 -1
- package/dist/components/Avatar.d.ts +4 -2
- package/dist/components/Avatar.d.ts.map +1 -1
- package/dist/components/Avatar.js +12 -5
- package/dist/components/Avatar.js.map +1 -1
- package/dist/components/Breadcrumbs.parts.d.ts.map +1 -1
- package/dist/components/Breadcrumbs.parts.js +4 -4
- package/dist/components/Breadcrumbs.parts.js.map +1 -1
- package/dist/components/Button.js +11 -7
- package/dist/components/Button.js.map +1 -1
- package/dist/components/Card.js +3 -3
- package/dist/components/Card.js.map +1 -1
- package/dist/components/Checkbox.parts.js +1 -1
- package/dist/components/Checkbox.parts.js.map +1 -1
- package/dist/components/Chip.d.ts +6 -0
- package/dist/components/Chip.d.ts.map +1 -0
- package/dist/components/Chip.js +5 -0
- package/dist/components/Chip.js.map +1 -0
- package/dist/components/Chip.meta.d.ts +17 -0
- package/dist/components/Chip.meta.d.ts.map +1 -0
- package/dist/components/Chip.meta.js +22 -0
- package/dist/components/Chip.meta.js.map +1 -0
- package/dist/components/Chip.parts.d.ts +52 -0
- package/dist/components/Chip.parts.d.ts.map +1 -0
- package/dist/components/Chip.parts.js +122 -0
- package/dist/components/Chip.parts.js.map +1 -0
- package/dist/components/CodeBlock.parts.js +1 -1
- package/dist/components/CodeBlock.parts.js.map +1 -1
- package/dist/components/CopyButton.js +1 -1
- package/dist/components/CopyButton.js.map +1 -1
- package/dist/components/DescriptionList.parts.js +1 -1
- package/dist/components/Keyboard.js +1 -1
- package/dist/components/Label.d.ts.map +1 -1
- package/dist/components/Label.js +1 -1
- package/dist/components/Label.js.map +1 -1
- package/dist/components/Link.d.ts.map +1 -1
- package/dist/components/Link.js +1 -1
- package/dist/components/Link.js.map +1 -1
- package/dist/components/Menu.parts.d.ts.map +1 -1
- package/dist/components/Menu.parts.js +15 -10
- package/dist/components/Menu.parts.js.map +1 -1
- package/dist/components/Modal.parts.js +1 -1
- package/dist/components/Modal.parts.js.map +1 -1
- package/dist/components/OTCInput.js +1 -1
- package/dist/components/OTCInput.js.map +1 -1
- package/dist/components/Search.d.ts +16 -0
- package/dist/components/Search.d.ts.map +1 -0
- package/dist/components/Search.js +129 -0
- package/dist/components/Search.js.map +1 -0
- package/dist/components/Search.meta.d.ts +15 -0
- package/dist/components/Search.meta.d.ts.map +1 -0
- package/dist/components/Search.meta.js +24 -0
- package/dist/components/Search.meta.js.map +1 -0
- package/dist/components/SegmentedControl.parts.d.ts.map +1 -1
- package/dist/components/SegmentedControl.parts.js +16 -28
- package/dist/components/SegmentedControl.parts.js.map +1 -1
- package/dist/components/TabSet.parts.js +3 -3
- package/dist/components/TabSet.parts.js.map +1 -1
- package/dist/components/ToggleButton.d.ts +0 -2
- package/dist/components/ToggleButton.d.ts.map +1 -1
- package/dist/components/ToggleButton.js.map +1 -1
- package/dist/components/Tooltip.parts.d.ts.map +1 -1
- package/dist/components/Tooltip.parts.js +9 -5
- package/dist/components/Tooltip.parts.js.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.d.ts +8 -5
- package/dist/components/base/ButtonOrLink.parts.d.ts.map +1 -1
- package/dist/components/base/ButtonOrLink.parts.js +42 -28
- package/dist/components/base/ButtonOrLink.parts.js.map +1 -1
- package/dist/components/base/Render.d.ts +1 -1
- package/dist/components/base/Render.d.ts.map +1 -1
- package/dist/components/base/Render.js +1 -1
- package/dist/components/base/Render.js.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +4 -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/useEffectWithRefDeps.js +1 -1
- package/dist/hooks/useEffectWithRefDeps.js.map +1 -1
- package/dist/hooks/useGDS.d.ts +1 -1
- package/dist/hooks/useNumberInput.js +1 -1
- package/dist/hooks/useNumberInput.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 +5 -0
- package/dist/tailwind-plugin.js.map +1 -1
- package/package.json +19 -18
- package/src/GDSProvider.tsx +31 -14
- package/src/components/Address.tsx +11 -6
- package/src/components/Avatar.tsx +25 -13
- package/src/components/Breadcrumbs.parts.tsx +4 -5
- package/src/components/Button.tsx +16 -6
- package/src/components/Card.tsx +3 -3
- package/src/components/Checkbox.parts.tsx +1 -1
- package/src/components/Chip.meta.ts +23 -0
- package/src/components/Chip.parts.tsx +329 -0
- package/src/components/Chip.tsx +7 -0
- package/src/components/CodeBlock.parts.tsx +3 -3
- package/src/components/CopyButton.tsx +1 -1
- package/src/components/DescriptionList.parts.tsx +1 -1
- package/src/components/Keyboard.tsx +1 -1
- package/src/components/Label.tsx +2 -1
- package/src/components/Link.tsx +2 -1
- package/src/components/Menu.parts.tsx +20 -24
- package/src/components/Modal.parts.tsx +1 -1
- package/src/components/OTCInput.tsx +1 -1
- package/src/components/Search.meta.ts +24 -0
- package/src/components/Search.tsx +238 -0
- package/src/components/SegmentedControl.parts.tsx +38 -40
- package/src/components/TabSet.parts.tsx +3 -3
- package/src/components/ToggleButton.tsx +0 -2
- package/src/components/Tooltip.parts.tsx +14 -4
- package/src/components/base/ButtonOrLink.parts.tsx +65 -34
- package/src/components/base/Render.tsx +1 -1
- package/src/components/index.ts +4 -0
- package/src/hooks/useCSSProp.ts +1 -1
- package/src/hooks/useEffectWithRefDeps.ts +1 -1
- package/src/hooks/useNumberInput.ts +1 -1
- package/src/hooks/useStyleObserver.ts +1 -1
- 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
|
|
12
|
-
import { RadioGroup } from '@base-ui
|
|
13
|
-
import { useMergedRefs } from '@base-ui
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
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
|
-
</
|
|
275
|
-
</
|
|
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
|
|
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-
|
|
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
|
|
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={
|
|
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
|
|
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
|
|
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
|
|
77
|
-
checked?: boolean |
|
|
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
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
? (
|
|
354
|
-
|
|
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={
|
|
389
|
+
<ButtonOrLinkConfigContext.Provider value={buttonOrLinkConfigContextValue}>
|
|
359
390
|
{children}
|
|
360
391
|
</ButtonOrLinkConfigContext.Provider>
|
|
361
392
|
)
|
package/src/components/index.ts
CHANGED
|
@@ -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,
|
package/src/hooks/useCSSProp.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
type Ref,
|
|
7
7
|
type RefCallback,
|
|
8
8
|
} from 'react'
|
|
9
|
-
import { useMergedRefs } from '@base-ui
|
|
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
|
|