react-aria-components 0.0.4 → 1.0.0-alpha.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/LICENSE +201 -0
- package/README.md +12 -29
- package/dist/import.mjs +5223 -0
- package/dist/main.js +5345 -0
- package/dist/main.js.map +1 -0
- package/dist/module.js +5223 -0
- package/dist/module.js.map +1 -0
- package/dist/types.d.ts +1481 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +36 -44
- package/src/Breadcrumbs.tsx +90 -0
- package/src/Button.tsx +79 -0
- package/src/Calendar.tsx +471 -0
- package/src/Checkbox.tsx +240 -0
- package/src/Collection.tsx +787 -0
- package/src/ComboBox.tsx +129 -0
- package/src/DateField.tsx +244 -0
- package/src/DatePicker.tsx +178 -0
- package/src/Dialog.tsx +95 -0
- package/src/GridList.tsx +411 -0
- package/src/Group.tsx +69 -0
- package/src/Header.tsx +34 -0
- package/src/Heading.tsx +35 -0
- package/src/Input.tsx +73 -0
- package/src/Keyboard.tsx +24 -0
- package/src/Label.tsx +30 -0
- package/src/Link.tsx +98 -0
- package/src/ListBox.tsx +406 -0
- package/src/Menu.tsx +227 -0
- package/src/Meter.tsx +73 -0
- package/src/Modal.tsx +192 -0
- package/src/NumberField.tsx +79 -0
- package/src/OverlayArrow.tsx +70 -0
- package/src/Popover.tsx +125 -0
- package/src/ProgressBar.tsx +81 -0
- package/src/RadioGroup.tsx +219 -0
- package/src/SearchField.tsx +78 -0
- package/src/Select.tsx +180 -0
- package/src/Separator.tsx +53 -0
- package/src/Slider.tsx +217 -0
- package/src/Switch.tsx +127 -0
- package/src/Table.tsx +917 -0
- package/src/Tabs.tsx +282 -0
- package/src/Text.tsx +30 -0
- package/src/TextField.tsx +62 -0
- package/src/ToggleButton.tsx +59 -0
- package/src/Tooltip.tsx +135 -0
- package/src/index.ts +94 -0
- package/src/useDragAndDrop.tsx +172 -0
- package/src/utils.tsx +235 -0
- package/.babelrc +0 -17
- package/.eslintrc +0 -12
- package/src/accordion/accordion.css +0 -4
- package/src/accordion/accordion.js +0 -20
- package/src/accordion/index.js +0 -2
- package/src/accordion/section.css +0 -21
- package/src/accordion/section.js +0 -85
- package/src/examples/accordion-example.js +0 -73
- package/src/examples/example.js +0 -21
- package/src/examples/grid-cells/fancy-input-grid-cell.js +0 -206
- package/src/examples/grid-cells/input-grid-cell.js +0 -44
- package/src/examples/grid-example.css +0 -23
- package/src/examples/grid-example.js +0 -231
- package/src/examples/index.html +0 -10
- package/src/examples/index.js +0 -53
- package/src/examples/tabs-example.js +0 -52
- package/src/grid/column-header.css +0 -4
- package/src/grid/column-header.js +0 -29
- package/src/grid/grid-cell.css +0 -16
- package/src/grid/grid-cell.js +0 -104
- package/src/grid/grid-context.js +0 -3
- package/src/grid/grid.css +0 -4
- package/src/grid/grid.js +0 -110
- package/src/grid/index.js +0 -6
- package/src/grid/interactive-grid-cell.js +0 -96
- package/src/grid/row-headers.css +0 -6
- package/src/grid/row-headers.js +0 -22
- package/src/grid/row.css +0 -5
- package/src/grid/row.js +0 -21
- package/src/prop-types/ref.js +0 -3
- package/src/tabs/index.js +0 -4
- package/src/tabs/tab-list.css +0 -6
- package/src/tabs/tab-list.js +0 -103
- package/src/tabs/tab-panels.js +0 -35
- package/src/tabs/tab.css +0 -10
- package/src/tabs/tab.js +0 -45
- package/src/tabs/tabs.js +0 -52
- package/src/utils/debounce.js +0 -12
- package/src/utils/event-handlers-factory.js +0 -42
- package/src/utils/unique-id.js +0 -6
- package/webpack.config.js +0 -43
package/src/Popover.tsx
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {AriaPopoverProps, DismissButton, Overlay, PlacementAxis, PositionProps, usePopover} from 'react-aria';
|
|
14
|
+
import {ContextValue, HiddenContext, RenderProps, SlotProps, useContextProps, useEnterAnimation, useExitAnimation, useRenderProps} from './utils';
|
|
15
|
+
import {OverlayArrowContext} from './OverlayArrow';
|
|
16
|
+
import {OverlayTriggerState} from 'react-stately';
|
|
17
|
+
import React, {createContext, ForwardedRef, forwardRef, RefObject} from 'react';
|
|
18
|
+
|
|
19
|
+
export interface PopoverProps extends Omit<PositionProps, 'isOpen'>, Omit<AriaPopoverProps, 'popoverRef' | 'triggerRef'>, RenderProps<PopoverRenderProps>, SlotProps {
|
|
20
|
+
/**
|
|
21
|
+
* The ref for the element which the popover positions itself with respect to.
|
|
22
|
+
*
|
|
23
|
+
* When used within a trigger component such as DialogTrigger, MenuTrigger, Select, etc.,
|
|
24
|
+
* this is set automatically. It is only required when used standalone.
|
|
25
|
+
*/
|
|
26
|
+
triggerRef?: RefObject<Element>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface PopoverRenderProps {
|
|
30
|
+
/**
|
|
31
|
+
* The placement of the tooltip relative to the trigger.
|
|
32
|
+
* @selector [data-placement="left | right | top | bottom"]
|
|
33
|
+
*/
|
|
34
|
+
placement: PlacementAxis,
|
|
35
|
+
/**
|
|
36
|
+
* Whether the popover is currently entering. Use this to apply animations.
|
|
37
|
+
* @selector [data-entering]
|
|
38
|
+
*/
|
|
39
|
+
isEntering: boolean,
|
|
40
|
+
/**
|
|
41
|
+
* Whether the popover is currently exiting. Use this to apply animations.
|
|
42
|
+
* @selector [data-exiting]
|
|
43
|
+
*/
|
|
44
|
+
isExiting: boolean
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface PopoverContextValue extends PopoverProps {
|
|
48
|
+
state: OverlayTriggerState,
|
|
49
|
+
preserveChildren?: boolean,
|
|
50
|
+
triggerRef: RefObject<Element>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const PopoverContext = createContext<ContextValue<PopoverContextValue, HTMLElement>>(null);
|
|
54
|
+
|
|
55
|
+
function Popover(props: PopoverProps, ref: ForwardedRef<HTMLElement>) {
|
|
56
|
+
[props, ref] = useContextProps(props, ref, PopoverContext);
|
|
57
|
+
let {preserveChildren, state, triggerRef} = props as PopoverContextValue;
|
|
58
|
+
let isExiting = useExitAnimation(ref, state.isOpen);
|
|
59
|
+
|
|
60
|
+
if (state && !state.isOpen && !isExiting) {
|
|
61
|
+
return preserveChildren ? <HiddenContext.Provider value>{props.children}</HiddenContext.Provider> : null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<PopoverInner
|
|
66
|
+
{...props}
|
|
67
|
+
triggerRef={triggerRef}
|
|
68
|
+
state={state}
|
|
69
|
+
popoverRef={ref}
|
|
70
|
+
isExiting={isExiting} />
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A popover is an overlay element positioned relative to a trigger.
|
|
76
|
+
*/
|
|
77
|
+
const _Popover = forwardRef(Popover);
|
|
78
|
+
export {_Popover as Popover};
|
|
79
|
+
|
|
80
|
+
interface PopoverInnerProps extends AriaPopoverProps, RenderProps<PopoverRenderProps>, SlotProps {
|
|
81
|
+
state: OverlayTriggerState,
|
|
82
|
+
isExiting: boolean
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function PopoverInner({children, state, isExiting, ...props}: PopoverInnerProps) {
|
|
86
|
+
let {popoverProps, underlayProps, arrowProps, placement} = usePopover({
|
|
87
|
+
...props,
|
|
88
|
+
offset: props.offset ?? 8
|
|
89
|
+
}, state);
|
|
90
|
+
|
|
91
|
+
let ref = props.popoverRef as RefObject<HTMLDivElement>;
|
|
92
|
+
let isEntering = useEnterAnimation(ref, !!placement);
|
|
93
|
+
let renderProps = useRenderProps({
|
|
94
|
+
...props,
|
|
95
|
+
defaultClassName: 'react-aria-Popover',
|
|
96
|
+
values: {
|
|
97
|
+
placement,
|
|
98
|
+
isEntering,
|
|
99
|
+
isExiting
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
let style = {...renderProps.style, ...popoverProps.style};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<Overlay>
|
|
107
|
+
{!props.isNonModal && <div {...underlayProps} style={{position: 'fixed', inset: 0}} />}
|
|
108
|
+
<div
|
|
109
|
+
{...popoverProps}
|
|
110
|
+
{...renderProps}
|
|
111
|
+
ref={ref}
|
|
112
|
+
slot={props.slot}
|
|
113
|
+
style={style}
|
|
114
|
+
data-placement={placement}
|
|
115
|
+
data-entering={isEntering || undefined}
|
|
116
|
+
data-exiting={isExiting || undefined}>
|
|
117
|
+
{!props.isNonModal && <DismissButton onDismiss={state.close} />}
|
|
118
|
+
<OverlayArrowContext.Provider value={{arrowProps, placement}}>
|
|
119
|
+
{children}
|
|
120
|
+
</OverlayArrowContext.Provider>
|
|
121
|
+
<DismissButton onDismiss={state.close} />
|
|
122
|
+
</div>
|
|
123
|
+
</Overlay>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {AriaProgressBarProps, useProgressBar} from 'react-aria';
|
|
14
|
+
import {ContextValue, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
|
|
15
|
+
import {LabelContext} from './Label';
|
|
16
|
+
import React, {createContext, ForwardedRef, forwardRef} from 'react';
|
|
17
|
+
|
|
18
|
+
export interface ProgressBarProps extends Omit<AriaProgressBarProps, 'label'>, RenderProps<ProgressBarRenderProps>, SlotProps {}
|
|
19
|
+
|
|
20
|
+
export interface ProgressBarRenderProps {
|
|
21
|
+
/**
|
|
22
|
+
* The value as a percentage between the minimum and maximum.
|
|
23
|
+
*/
|
|
24
|
+
percentage?: number,
|
|
25
|
+
/**
|
|
26
|
+
* A formatted version of the value.
|
|
27
|
+
* @selector [aria-valuetext]
|
|
28
|
+
*/
|
|
29
|
+
valueText?: string,
|
|
30
|
+
/**
|
|
31
|
+
* Whether the progress bar is indeterminate.
|
|
32
|
+
* @selector :not([aria-valuenow])
|
|
33
|
+
*/
|
|
34
|
+
isIndeterminate: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const ProgressBarContext = createContext<ContextValue<ProgressBarProps, HTMLDivElement>>(null);
|
|
38
|
+
|
|
39
|
+
function ProgressBar(props: ProgressBarProps, ref: ForwardedRef<HTMLDivElement>) {
|
|
40
|
+
[props, ref] = useContextProps(props, ref, ProgressBarContext);
|
|
41
|
+
let {
|
|
42
|
+
value = 0,
|
|
43
|
+
minValue = 0,
|
|
44
|
+
maxValue = 100,
|
|
45
|
+
isIndeterminate = false
|
|
46
|
+
} = props;
|
|
47
|
+
|
|
48
|
+
let [labelRef, label] = useSlot();
|
|
49
|
+
let {
|
|
50
|
+
progressBarProps,
|
|
51
|
+
labelProps
|
|
52
|
+
} = useProgressBar({...props, label});
|
|
53
|
+
|
|
54
|
+
// Calculate the width of the progress bar as a percentage
|
|
55
|
+
let percentage = isIndeterminate ? undefined : (value - minValue) / (maxValue - minValue) * 100;
|
|
56
|
+
|
|
57
|
+
let renderProps = useRenderProps({
|
|
58
|
+
...props,
|
|
59
|
+
defaultClassName: 'react-aria-ProgressBar',
|
|
60
|
+
values: {
|
|
61
|
+
percentage,
|
|
62
|
+
valueText: progressBarProps['aria-valuetext'],
|
|
63
|
+
isIndeterminate
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div {...progressBarProps} {...renderProps} ref={ref} slot={props.slot}>
|
|
69
|
+
<LabelContext.Provider value={{...labelProps, ref: labelRef, elementType: 'span'}}>
|
|
70
|
+
{renderProps.children}
|
|
71
|
+
</LabelContext.Provider>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Progress bars show either determinate or indeterminate progress of an operation
|
|
78
|
+
* over time.
|
|
79
|
+
*/
|
|
80
|
+
const _ProgressBar = forwardRef(ProgressBar);
|
|
81
|
+
export {_ProgressBar as ProgressBar};
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {AriaRadioGroupProps, AriaRadioProps, Orientation, useFocusRing, useHover, usePress, useRadio, useRadioGroup, VisuallyHidden} from 'react-aria';
|
|
14
|
+
import {ContextValue, Provider, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
|
|
15
|
+
import {LabelContext} from './Label';
|
|
16
|
+
import {mergeProps, useObjectRef} from '@react-aria/utils';
|
|
17
|
+
import {RadioGroupState, useRadioGroupState, ValidationState} from 'react-stately';
|
|
18
|
+
import React, {createContext, ForwardedRef, forwardRef, useState} from 'react';
|
|
19
|
+
import {TextContext} from './Text';
|
|
20
|
+
|
|
21
|
+
export interface RadioGroupProps extends Omit<AriaRadioGroupProps, 'children' | 'label' | 'description' | 'errorMessage'>, RenderProps<RadioGroupRenderProps>, SlotProps {}
|
|
22
|
+
export interface RadioProps extends Omit<AriaRadioProps, 'children'>, RenderProps<RadioRenderProps> {}
|
|
23
|
+
|
|
24
|
+
export interface RadioGroupRenderProps {
|
|
25
|
+
/**
|
|
26
|
+
* The orientation of the radio group.
|
|
27
|
+
* @selector [aria-orientation="horizontal | vertical"]
|
|
28
|
+
*/
|
|
29
|
+
orientation: Orientation,
|
|
30
|
+
/**
|
|
31
|
+
* Whether the radio group is disabled.
|
|
32
|
+
* @selector [aria-disabled]
|
|
33
|
+
*/
|
|
34
|
+
isDisabled: boolean,
|
|
35
|
+
/**
|
|
36
|
+
* Whether the radio group is read only.
|
|
37
|
+
* @selector [aria-readonly]
|
|
38
|
+
*/
|
|
39
|
+
isReadOnly: boolean,
|
|
40
|
+
/**
|
|
41
|
+
* Whether the radio group is required.
|
|
42
|
+
* @selector [aria-required]
|
|
43
|
+
*/
|
|
44
|
+
isRequired: boolean,
|
|
45
|
+
/**
|
|
46
|
+
* The validation state of the radio group.
|
|
47
|
+
* @selector [aria-invalid]
|
|
48
|
+
*/
|
|
49
|
+
validationState: ValidationState | null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface RadioRenderProps {
|
|
53
|
+
/**
|
|
54
|
+
* Whether the radio is selected.
|
|
55
|
+
* @selector [data-selected]
|
|
56
|
+
*/
|
|
57
|
+
isSelected: boolean,
|
|
58
|
+
/**
|
|
59
|
+
* Whether the radio is currently hovered with a mouse.
|
|
60
|
+
* @selector [data-hovered]
|
|
61
|
+
*/
|
|
62
|
+
isHovered: boolean,
|
|
63
|
+
/**
|
|
64
|
+
* Whether the radio is currently in a pressed state.
|
|
65
|
+
* @selector [data-pressed]
|
|
66
|
+
*/
|
|
67
|
+
isPressed: boolean,
|
|
68
|
+
/**
|
|
69
|
+
* Whether the radio is focused, either via a mouse or keyboard.
|
|
70
|
+
* @selector [data-focused]
|
|
71
|
+
*/
|
|
72
|
+
isFocused: boolean,
|
|
73
|
+
/**
|
|
74
|
+
* Whether the radio is keyboard focused.
|
|
75
|
+
* @selector [data-focus-visible]
|
|
76
|
+
*/
|
|
77
|
+
isFocusVisible: boolean,
|
|
78
|
+
/**
|
|
79
|
+
* Whether the radio is disabled.
|
|
80
|
+
* @selector [data-disabled]
|
|
81
|
+
*/
|
|
82
|
+
isDisabled: boolean,
|
|
83
|
+
/**
|
|
84
|
+
* Whether the radio is read only.
|
|
85
|
+
* @selector [data-readonly]
|
|
86
|
+
*/
|
|
87
|
+
isReadOnly: boolean,
|
|
88
|
+
/**
|
|
89
|
+
* Whether the radio is valid or invalid.
|
|
90
|
+
* @selector [data-validation-state="valid | invalid"]
|
|
91
|
+
*/
|
|
92
|
+
validationState: ValidationState | null,
|
|
93
|
+
/**
|
|
94
|
+
* Whether the checkbox is required.
|
|
95
|
+
* @selector [data-required]
|
|
96
|
+
*/
|
|
97
|
+
isRequired: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const RadioGroupContext = createContext<ContextValue<RadioGroupProps, HTMLDivElement>>(null);
|
|
101
|
+
let InternalRadioContext = createContext<RadioGroupState | null>(null);
|
|
102
|
+
|
|
103
|
+
function RadioGroup(props: RadioGroupProps, ref: ForwardedRef<HTMLDivElement>) {
|
|
104
|
+
[props, ref] = useContextProps(props, ref, RadioGroupContext);
|
|
105
|
+
let state = useRadioGroupState(props);
|
|
106
|
+
let [labelRef, label] = useSlot();
|
|
107
|
+
let {radioGroupProps, labelProps, descriptionProps, errorMessageProps} = useRadioGroup({
|
|
108
|
+
...props,
|
|
109
|
+
label
|
|
110
|
+
}, state);
|
|
111
|
+
|
|
112
|
+
let renderProps = useRenderProps({
|
|
113
|
+
...props,
|
|
114
|
+
values: {
|
|
115
|
+
orientation: props.orientation || 'vertical',
|
|
116
|
+
isDisabled: state.isDisabled,
|
|
117
|
+
isReadOnly: state.isReadOnly,
|
|
118
|
+
isRequired: state.isRequired,
|
|
119
|
+
validationState: state.validationState
|
|
120
|
+
},
|
|
121
|
+
defaultClassName: 'react-aria-RadioGroup'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<div {...radioGroupProps} {...renderProps} ref={ref} slot={props.slot}>
|
|
126
|
+
<Provider
|
|
127
|
+
values={[
|
|
128
|
+
[InternalRadioContext, state],
|
|
129
|
+
[LabelContext, {...labelProps, ref: labelRef}],
|
|
130
|
+
[TextContext, {
|
|
131
|
+
slots: {
|
|
132
|
+
description: descriptionProps,
|
|
133
|
+
errorMessage: errorMessageProps
|
|
134
|
+
}
|
|
135
|
+
}]
|
|
136
|
+
]}>
|
|
137
|
+
{renderProps.children}
|
|
138
|
+
</Provider>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function Radio(props: RadioProps, ref: ForwardedRef<HTMLInputElement>) {
|
|
144
|
+
let state = React.useContext(InternalRadioContext)!;
|
|
145
|
+
let domRef = useObjectRef(ref);
|
|
146
|
+
let {inputProps, isSelected, isDisabled, isPressed: isPressedKeyboard} = useRadio(props, state, domRef);
|
|
147
|
+
let {isFocused, isFocusVisible, focusProps} = useFocusRing();
|
|
148
|
+
let interactionDisabled = isDisabled || state.isReadOnly;
|
|
149
|
+
|
|
150
|
+
// Handle press state for full label. Keyboard press state is returned by useCheckbox
|
|
151
|
+
// since it is handled on the <input> element itself.
|
|
152
|
+
let [isPressed, setPressed] = useState(false);
|
|
153
|
+
let {pressProps} = usePress({
|
|
154
|
+
isDisabled: interactionDisabled,
|
|
155
|
+
onPressStart(e) {
|
|
156
|
+
if (e.pointerType !== 'keyboard') {
|
|
157
|
+
setPressed(true);
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
onPressEnd(e) {
|
|
161
|
+
if (e.pointerType !== 'keyboard') {
|
|
162
|
+
setPressed(false);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
let {hoverProps, isHovered} = useHover({
|
|
168
|
+
isDisabled: interactionDisabled
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
let pressed = interactionDisabled ? false : (isPressed || isPressedKeyboard);
|
|
172
|
+
|
|
173
|
+
let renderProps = useRenderProps({
|
|
174
|
+
...props,
|
|
175
|
+
defaultClassName: 'react-aria-Radio',
|
|
176
|
+
values: {
|
|
177
|
+
isSelected,
|
|
178
|
+
isPressed: pressed,
|
|
179
|
+
isHovered,
|
|
180
|
+
isFocused,
|
|
181
|
+
isFocusVisible,
|
|
182
|
+
isDisabled,
|
|
183
|
+
isReadOnly: state.isReadOnly,
|
|
184
|
+
validationState: state.validationState,
|
|
185
|
+
isRequired: state.isRequired
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return (
|
|
190
|
+
<label
|
|
191
|
+
{...mergeProps(pressProps, hoverProps, renderProps)}
|
|
192
|
+
data-selected={isSelected || undefined}
|
|
193
|
+
data-pressed={pressed || undefined}
|
|
194
|
+
data-hovered={isHovered || undefined}
|
|
195
|
+
data-focused={isFocused || undefined}
|
|
196
|
+
data-focus-visible={isFocusVisible || undefined}
|
|
197
|
+
data-disabled={isDisabled || undefined}
|
|
198
|
+
data-readonly={state.isReadOnly || undefined}
|
|
199
|
+
data-validation-state={state.validationState || undefined}
|
|
200
|
+
data-required={state.isRequired || undefined}>
|
|
201
|
+
<VisuallyHidden>
|
|
202
|
+
<input {...inputProps} {...focusProps} ref={domRef} />
|
|
203
|
+
</VisuallyHidden>
|
|
204
|
+
{renderProps.children}
|
|
205
|
+
</label>
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* A radio group allows a user to select a single item from a list of mutually exclusive options.
|
|
211
|
+
*/
|
|
212
|
+
const _RadioGroup = forwardRef(RadioGroup);
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* A radio represents an individual option within a radio group.
|
|
216
|
+
*/
|
|
217
|
+
const _Radio = forwardRef(Radio);
|
|
218
|
+
|
|
219
|
+
export {_RadioGroup as RadioGroup, _Radio as Radio};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {AriaSearchFieldProps, useSearchField} from 'react-aria';
|
|
14
|
+
import {ButtonContext} from './Button';
|
|
15
|
+
import {ContextValue, Provider, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
|
|
16
|
+
import {InputContext} from './Input';
|
|
17
|
+
import {LabelContext} from './Label';
|
|
18
|
+
import React, {createContext, ForwardedRef, forwardRef, useRef} from 'react';
|
|
19
|
+
import {SearchFieldState, useSearchFieldState} from 'react-stately';
|
|
20
|
+
import {TextContext} from './Text';
|
|
21
|
+
|
|
22
|
+
export interface SearchFieldProps extends Omit<AriaSearchFieldProps, 'label' | 'placeholder' | 'description' | 'errorMessage'>, RenderProps<SearchFieldState>, SlotProps {}
|
|
23
|
+
|
|
24
|
+
export interface SearchFieldRenderProps {
|
|
25
|
+
/**
|
|
26
|
+
* Whether the search field is empty.
|
|
27
|
+
* @selector [data-empty]
|
|
28
|
+
*/
|
|
29
|
+
isEmpty: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const SearchFieldContext = createContext<ContextValue<SearchFieldProps, HTMLDivElement>>(null);
|
|
33
|
+
|
|
34
|
+
function SearchField(props: SearchFieldProps, ref: ForwardedRef<HTMLDivElement>) {
|
|
35
|
+
[props, ref] = useContextProps(props, ref, SearchFieldContext);
|
|
36
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
37
|
+
let [labelRef, label] = useSlot();
|
|
38
|
+
let state = useSearchFieldState(props);
|
|
39
|
+
let {labelProps, inputProps, clearButtonProps, descriptionProps, errorMessageProps} = useSearchField({
|
|
40
|
+
...props,
|
|
41
|
+
label
|
|
42
|
+
}, state, inputRef);
|
|
43
|
+
|
|
44
|
+
let renderProps = useRenderProps({
|
|
45
|
+
...props,
|
|
46
|
+
values: state,
|
|
47
|
+
defaultClassName: 'react-aria-SearchField'
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
{...renderProps}
|
|
53
|
+
ref={ref}
|
|
54
|
+
slot={props.slot}
|
|
55
|
+
data-empty={state.value === '' || undefined}>
|
|
56
|
+
<Provider
|
|
57
|
+
values={[
|
|
58
|
+
[LabelContext, {...labelProps, ref: labelRef}],
|
|
59
|
+
[InputContext, {...inputProps, ref: inputRef}],
|
|
60
|
+
[ButtonContext, clearButtonProps],
|
|
61
|
+
[TextContext, {
|
|
62
|
+
slots: {
|
|
63
|
+
description: descriptionProps,
|
|
64
|
+
errorMessage: errorMessageProps
|
|
65
|
+
}
|
|
66
|
+
}]
|
|
67
|
+
]}>
|
|
68
|
+
{renderProps.children}
|
|
69
|
+
</Provider>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* A search field allows a user to enter and clear a search query.
|
|
76
|
+
*/
|
|
77
|
+
const _SearchField = forwardRef(SearchField);
|
|
78
|
+
export {_SearchField as SearchField};
|
package/src/Select.tsx
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2022 Adobe. All rights reserved.
|
|
3
|
+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
|
+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
*
|
|
7
|
+
* Unless required by applicable law or agreed to in writing, software distributed under
|
|
8
|
+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
|
|
9
|
+
* OF ANY KIND, either express or implied. See the License for the specific language
|
|
10
|
+
* governing permissions and limitations under the License.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {AriaSelectProps, HiddenSelect, useSelect} from 'react-aria';
|
|
14
|
+
import {ButtonContext} from './Button';
|
|
15
|
+
import {ContextValue, forwardRefType, Provider, RenderProps, slotCallbackSymbol, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
|
|
16
|
+
import {createContext, ForwardedRef, HTMLAttributes, ReactNode, useCallback, useContext, useRef, useState} from 'react';
|
|
17
|
+
import {ItemRenderProps, useCollection} from './Collection';
|
|
18
|
+
import {LabelContext} from './Label';
|
|
19
|
+
import {ListBoxContext, ListBoxProps} from './ListBox';
|
|
20
|
+
import {PopoverContext} from './Popover';
|
|
21
|
+
import React, {forwardRef} from 'react';
|
|
22
|
+
import {SelectState, useSelectState} from 'react-stately';
|
|
23
|
+
import {TextContext} from './Text';
|
|
24
|
+
import {useResizeObserver} from '@react-aria/utils';
|
|
25
|
+
|
|
26
|
+
export interface SelectProps<T extends object> extends Omit<AriaSelectProps<T>, 'children' | 'label' | 'description' | 'errorMessage'>, RenderProps<SelectState<T>>, SlotProps {}
|
|
27
|
+
|
|
28
|
+
interface SelectValueContext {
|
|
29
|
+
state: SelectState<unknown>,
|
|
30
|
+
valueProps: HTMLAttributes<HTMLElement>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const SelectContext = createContext<ContextValue<SelectProps<any>, HTMLDivElement>>(null);
|
|
34
|
+
const InternalSelectContext = createContext<SelectValueContext | null>(null);
|
|
35
|
+
|
|
36
|
+
function Select<T extends object>(props: SelectProps<T>, ref: ForwardedRef<HTMLDivElement>) {
|
|
37
|
+
[props, ref] = useContextProps(props, ref, SelectContext);
|
|
38
|
+
let [listBoxProps, setListBoxProps] = useState<ListBoxProps<any>>({children: []});
|
|
39
|
+
|
|
40
|
+
let {portal, collection} = useCollection({
|
|
41
|
+
items: props.items ?? listBoxProps.items,
|
|
42
|
+
children: listBoxProps.children
|
|
43
|
+
});
|
|
44
|
+
let state = useSelectState({
|
|
45
|
+
...props,
|
|
46
|
+
collection,
|
|
47
|
+
children: undefined
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Get props for child elements from useSelect
|
|
51
|
+
let buttonRef = useRef<HTMLButtonElement>(null);
|
|
52
|
+
let [labelRef, label] = useSlot();
|
|
53
|
+
let {
|
|
54
|
+
labelProps,
|
|
55
|
+
triggerProps,
|
|
56
|
+
valueProps,
|
|
57
|
+
menuProps,
|
|
58
|
+
descriptionProps,
|
|
59
|
+
errorMessageProps
|
|
60
|
+
} = useSelect({...props, label}, state, buttonRef);
|
|
61
|
+
|
|
62
|
+
// Make menu width match input + button
|
|
63
|
+
let [buttonWidth, setButtonWidth] = useState<string | null>(null);
|
|
64
|
+
let onResize = useCallback(() => {
|
|
65
|
+
if (buttonRef.current) {
|
|
66
|
+
setButtonWidth(buttonRef.current.offsetWidth + 'px');
|
|
67
|
+
}
|
|
68
|
+
}, [buttonRef]);
|
|
69
|
+
|
|
70
|
+
useResizeObserver({
|
|
71
|
+
ref: buttonRef,
|
|
72
|
+
onResize: onResize
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
let renderProps = useRenderProps({
|
|
76
|
+
...props,
|
|
77
|
+
values: state,
|
|
78
|
+
defaultClassName: 'react-aria-Select'
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<Provider
|
|
83
|
+
values={[
|
|
84
|
+
[InternalSelectContext, {state, valueProps}],
|
|
85
|
+
[LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}],
|
|
86
|
+
[ButtonContext, {...triggerProps, ref: buttonRef, isPressed: state.isOpen}],
|
|
87
|
+
[PopoverContext, {
|
|
88
|
+
state,
|
|
89
|
+
triggerRef: buttonRef,
|
|
90
|
+
preserveChildren: true,
|
|
91
|
+
placement: 'bottom start',
|
|
92
|
+
style: {'--trigger-width': buttonWidth} as React.CSSProperties
|
|
93
|
+
}],
|
|
94
|
+
[ListBoxContext, {state, [slotCallbackSymbol]: setListBoxProps, ...menuProps}],
|
|
95
|
+
[TextContext, {
|
|
96
|
+
slots: {
|
|
97
|
+
description: descriptionProps,
|
|
98
|
+
errorMessage: errorMessageProps
|
|
99
|
+
}
|
|
100
|
+
}]
|
|
101
|
+
]}>
|
|
102
|
+
<div {...renderProps} ref={ref} slot={props.slot}>
|
|
103
|
+
{props.children}
|
|
104
|
+
</div>
|
|
105
|
+
{portal}
|
|
106
|
+
<HiddenSelect
|
|
107
|
+
state={state}
|
|
108
|
+
triggerRef={buttonRef}
|
|
109
|
+
label={label}
|
|
110
|
+
name={props.name} />
|
|
111
|
+
</Provider>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A select displays a collapsible list of options and allows a user to select one of them.
|
|
117
|
+
*/
|
|
118
|
+
const _Select = (forwardRef as forwardRefType)(Select);
|
|
119
|
+
export {_Select as Select};
|
|
120
|
+
|
|
121
|
+
export interface SelectValueRenderProps<T> {
|
|
122
|
+
/**
|
|
123
|
+
* Whether the value is a placeholder.
|
|
124
|
+
* @selector [data-placeholder]
|
|
125
|
+
*/
|
|
126
|
+
isPlaceholder: boolean,
|
|
127
|
+
/** The object value of the currently selected item. */
|
|
128
|
+
selectedItem: T | null,
|
|
129
|
+
/** The textValue of the currently selected item. */
|
|
130
|
+
selectedText: string | null
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface SelectValueProps<T extends object> extends Omit<HTMLAttributes<HTMLElement>, keyof RenderProps<unknown>>, RenderProps<SelectValueRenderProps<T>> {}
|
|
134
|
+
|
|
135
|
+
function SelectValue<T extends object>(props: SelectValueProps<T>, ref: ForwardedRef<HTMLSpanElement>) {
|
|
136
|
+
let {state, valueProps} = useContext(InternalSelectContext)!;
|
|
137
|
+
let rendered = state.selectedItem?.rendered;
|
|
138
|
+
if (typeof rendered === 'function') {
|
|
139
|
+
// If the selected item has a function as a child, we need to call it to render to JSX.
|
|
140
|
+
let fn = rendered as (s: ItemRenderProps) => ReactNode;
|
|
141
|
+
rendered = fn({
|
|
142
|
+
isHovered: false,
|
|
143
|
+
isPressed: false,
|
|
144
|
+
isSelected: false,
|
|
145
|
+
isFocused: false,
|
|
146
|
+
isFocusVisible: false,
|
|
147
|
+
isDisabled: false,
|
|
148
|
+
selectionMode: 'single',
|
|
149
|
+
selectionBehavior: 'toggle'
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let renderProps = useRenderProps({
|
|
154
|
+
...props,
|
|
155
|
+
// TODO: localize this.
|
|
156
|
+
defaultChildren: rendered || 'Select an item',
|
|
157
|
+
defaultClassName: 'react-aria-SelectValue',
|
|
158
|
+
values: {
|
|
159
|
+
selectedItem: state.selectedItem?.value as T ?? null,
|
|
160
|
+
selectedText: state.selectedItem?.textValue ?? null,
|
|
161
|
+
isPlaceholder: !state.selectedItem
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<span ref={ref} {...valueProps} {...renderProps} data-placeholder={!state.selectedItem || undefined}>
|
|
167
|
+
{/* clear description and error message slots */}
|
|
168
|
+
<TextContext.Provider value={undefined}>
|
|
169
|
+
{renderProps.children}
|
|
170
|
+
</TextContext.Provider>
|
|
171
|
+
</span>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* SelectValue renders the current value of a Select, or a placeholder if no value is selected.
|
|
177
|
+
* It is usually placed within the button element.
|
|
178
|
+
*/
|
|
179
|
+
const _SelectValue = (forwardRef as forwardRefType)(SelectValue);
|
|
180
|
+
export {_SelectValue as SelectValue};
|