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/ComboBox.tsx
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
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
|
+
import {AriaComboBoxProps, useComboBox, useFilter} from 'react-aria';
|
|
13
|
+
import {ButtonContext} from './Button';
|
|
14
|
+
import {ComboBoxState, useComboBoxState} from 'react-stately';
|
|
15
|
+
import {ContextValue, forwardRefType, Provider, RenderProps, slotCallbackSymbol, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
|
|
16
|
+
import {InputContext} from './Input';
|
|
17
|
+
import {LabelContext} from './Label';
|
|
18
|
+
import {ListBoxContext, ListBoxProps} from './ListBox';
|
|
19
|
+
import {PopoverContext} from './Popover';
|
|
20
|
+
import React, {createContext, ForwardedRef, forwardRef, useCallback, useRef, useState} from 'react';
|
|
21
|
+
import {TextContext} from './Text';
|
|
22
|
+
import {useCollection} from './Collection';
|
|
23
|
+
import {useResizeObserver} from '@react-aria/utils';
|
|
24
|
+
|
|
25
|
+
export interface ComboBoxProps<T extends object> extends Omit<AriaComboBoxProps<T>, 'children' | 'placeholder' | 'name' | 'label' | 'description' | 'errorMessage'>, RenderProps<ComboBoxState<T>>, SlotProps {
|
|
26
|
+
/** The filter function used to determine if a option should be included in the combo box list. */
|
|
27
|
+
defaultFilter?: (textValue: string, inputValue: string) => boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const ComboBoxContext = createContext<ContextValue<ComboBoxProps<any>, HTMLDivElement>>(null);
|
|
31
|
+
|
|
32
|
+
function ComboBox<T extends object>(props: ComboBoxProps<T>, ref: ForwardedRef<HTMLDivElement>) {
|
|
33
|
+
[props, ref] = useContextProps(props, ref, ComboBoxContext);
|
|
34
|
+
let [propsFromListBox, setListBoxProps] = useState<ListBoxProps<T>>({children: []});
|
|
35
|
+
|
|
36
|
+
let {contains} = useFilter({sensitivity: 'base'});
|
|
37
|
+
let {portal, collection} = useCollection({
|
|
38
|
+
items: props.items ?? props.defaultItems ?? propsFromListBox.items,
|
|
39
|
+
children: propsFromListBox.children
|
|
40
|
+
});
|
|
41
|
+
let state = useComboBoxState({
|
|
42
|
+
defaultFilter: props.defaultFilter || contains,
|
|
43
|
+
...props,
|
|
44
|
+
items: propsFromListBox ? (props.items ?? propsFromListBox.items) : [],
|
|
45
|
+
children: undefined,
|
|
46
|
+
collection
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
let buttonRef = useRef<HTMLButtonElement>(null);
|
|
50
|
+
let inputRef = useRef<HTMLInputElement>(null);
|
|
51
|
+
let listBoxRef = useRef<HTMLDivElement>(null);
|
|
52
|
+
let popoverRef = useRef<HTMLDivElement>(null);
|
|
53
|
+
let [labelRef, label] = useSlot();
|
|
54
|
+
let {
|
|
55
|
+
buttonProps,
|
|
56
|
+
inputProps,
|
|
57
|
+
listBoxProps,
|
|
58
|
+
labelProps,
|
|
59
|
+
descriptionProps,
|
|
60
|
+
errorMessageProps
|
|
61
|
+
} = useComboBox({
|
|
62
|
+
...props,
|
|
63
|
+
label,
|
|
64
|
+
inputRef,
|
|
65
|
+
buttonRef,
|
|
66
|
+
listBoxRef,
|
|
67
|
+
popoverRef
|
|
68
|
+
},
|
|
69
|
+
state);
|
|
70
|
+
|
|
71
|
+
// Make menu width match input + button
|
|
72
|
+
let [menuWidth, setMenuWidth] = useState<string | null>(null);
|
|
73
|
+
let onResize = useCallback(() => {
|
|
74
|
+
if (inputRef.current) {
|
|
75
|
+
let buttonRect = buttonRef.current?.getBoundingClientRect();
|
|
76
|
+
let inputRect = inputRef.current.getBoundingClientRect();
|
|
77
|
+
let minX = buttonRect ? Math.min(buttonRect.left, inputRect.left) : inputRect.left;
|
|
78
|
+
let maxX = buttonRect ? Math.max(buttonRect.right, inputRect.right) : inputRect.right;
|
|
79
|
+
setMenuWidth((maxX - minX) + 'px');
|
|
80
|
+
}
|
|
81
|
+
}, [buttonRef, inputRef, setMenuWidth]);
|
|
82
|
+
|
|
83
|
+
useResizeObserver({
|
|
84
|
+
ref: inputRef,
|
|
85
|
+
onResize: onResize
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
let renderProps = useRenderProps({
|
|
89
|
+
...props,
|
|
90
|
+
values: state,
|
|
91
|
+
defaultClassName: 'react-aria-ComboBox'
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<Provider
|
|
96
|
+
values={[
|
|
97
|
+
[LabelContext, {...labelProps, ref: labelRef}],
|
|
98
|
+
[ButtonContext, {...buttonProps, ref: buttonRef, isPressed: state.isOpen}],
|
|
99
|
+
[InputContext, {...inputProps, ref: inputRef}],
|
|
100
|
+
[PopoverContext, {
|
|
101
|
+
state,
|
|
102
|
+
ref: popoverRef,
|
|
103
|
+
triggerRef: inputRef,
|
|
104
|
+
placement: 'bottom start',
|
|
105
|
+
preserveChildren: true,
|
|
106
|
+
isNonModal: true,
|
|
107
|
+
style: {'--trigger-width': menuWidth} as React.CSSProperties
|
|
108
|
+
}],
|
|
109
|
+
[ListBoxContext, {state, [slotCallbackSymbol]: setListBoxProps, ...listBoxProps, ref: listBoxRef}],
|
|
110
|
+
[TextContext, {
|
|
111
|
+
slots: {
|
|
112
|
+
description: descriptionProps,
|
|
113
|
+
errorMessage: errorMessageProps
|
|
114
|
+
}
|
|
115
|
+
}]
|
|
116
|
+
]}>
|
|
117
|
+
<div {...renderProps} ref={ref} slot={props.slot}>
|
|
118
|
+
{props.children}
|
|
119
|
+
</div>
|
|
120
|
+
{portal}
|
|
121
|
+
</Provider>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A combo box combines a text input with a listbox, allowing users to filter a list of options to items matching a query.
|
|
127
|
+
*/
|
|
128
|
+
const _ComboBox = (forwardRef as forwardRefType)(ComboBox);
|
|
129
|
+
export {_ComboBox as ComboBox};
|
|
@@ -0,0 +1,244 @@
|
|
|
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
|
+
import {AriaDateFieldProps, AriaTimeFieldProps, DateValue, mergeProps, TimeValue, useDateField, useDateSegment, useFocusRing, useHover, useLocale, useTimeField} from 'react-aria';
|
|
13
|
+
import {ContextValue, forwardRefType, Provider, RenderProps, SlotProps, StyleRenderProps, useContextProps, useRenderProps, useSlot} from './utils';
|
|
14
|
+
import {createCalendar} from '@internationalized/date';
|
|
15
|
+
import {DateFieldState, DateSegmentType, DateSegment as IDateSegment, useDateFieldState, useTimeFieldState} from 'react-stately';
|
|
16
|
+
import {LabelContext} from './Label';
|
|
17
|
+
import React, {cloneElement, createContext, ForwardedRef, forwardRef, HTMLAttributes, ReactElement, useContext, useRef} from 'react';
|
|
18
|
+
import {TextContext} from './Text';
|
|
19
|
+
import {useObjectRef} from '@react-aria/utils';
|
|
20
|
+
|
|
21
|
+
export interface DateFieldProps<T extends DateValue> extends Omit<AriaDateFieldProps<T>, 'label' | 'description' | 'errorMessage'>, RenderProps<DateFieldState>, SlotProps {}
|
|
22
|
+
export interface TimeFieldProps<T extends TimeValue> extends Omit<AriaTimeFieldProps<T>, 'label' | 'description' | 'errorMessage'>, RenderProps<DateFieldState>, SlotProps {}
|
|
23
|
+
|
|
24
|
+
interface DateInputContextValue extends SlotProps {
|
|
25
|
+
state: DateFieldState,
|
|
26
|
+
fieldProps: HTMLAttributes<HTMLElement>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const DateFieldContext = createContext<ContextValue<DateFieldProps<any>, HTMLDivElement>>(null);
|
|
30
|
+
export const TimeFieldContext = createContext<ContextValue<TimeFieldProps<any>, HTMLDivElement>>(null);
|
|
31
|
+
export const DateInputContext = createContext<ContextValue<DateInputContextValue, HTMLDivElement>>(null);
|
|
32
|
+
|
|
33
|
+
function DateField<T extends DateValue>(props: DateFieldProps<T>, ref: ForwardedRef<HTMLDivElement>) {
|
|
34
|
+
[props, ref] = useContextProps(props, ref, DateFieldContext);
|
|
35
|
+
let {locale} = useLocale();
|
|
36
|
+
let state = useDateFieldState({
|
|
37
|
+
...props,
|
|
38
|
+
locale,
|
|
39
|
+
createCalendar
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
let fieldRef = useRef<HTMLDivElement>(null);
|
|
43
|
+
let [labelRef, label] = useSlot();
|
|
44
|
+
let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useDateField({...props, label}, state, fieldRef);
|
|
45
|
+
|
|
46
|
+
let renderProps = useRenderProps({
|
|
47
|
+
...props,
|
|
48
|
+
values: state,
|
|
49
|
+
defaultClassName: 'react-aria-DateField'
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<Provider
|
|
54
|
+
values={[
|
|
55
|
+
[DateInputContext, {state, fieldProps, ref: fieldRef}],
|
|
56
|
+
[LabelContext, {...labelProps, ref: labelRef}],
|
|
57
|
+
[TextContext, {
|
|
58
|
+
slots: {
|
|
59
|
+
description: descriptionProps,
|
|
60
|
+
errorMessage: errorMessageProps
|
|
61
|
+
}
|
|
62
|
+
}]
|
|
63
|
+
]}>
|
|
64
|
+
<div {...renderProps} ref={ref} slot={props.slot}>
|
|
65
|
+
{props.children}
|
|
66
|
+
</div>
|
|
67
|
+
</Provider>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A date field allows users to enter and edit date and time values using a keyboard.
|
|
73
|
+
* Each part of a date value is displayed in an individually editable segment.
|
|
74
|
+
*/
|
|
75
|
+
const _DateField = (forwardRef as forwardRefType)(DateField);
|
|
76
|
+
export {_DateField as DateField};
|
|
77
|
+
|
|
78
|
+
function TimeField<T extends TimeValue>(props: TimeFieldProps<T>, ref: ForwardedRef<HTMLDivElement>) {
|
|
79
|
+
[props, ref] = useContextProps(props, ref, TimeFieldContext);
|
|
80
|
+
let {locale} = useLocale();
|
|
81
|
+
let state = useTimeFieldState({
|
|
82
|
+
...props,
|
|
83
|
+
locale
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
let fieldRef = useRef<HTMLDivElement>(null);
|
|
87
|
+
let [labelRef, label] = useSlot();
|
|
88
|
+
let {labelProps, fieldProps, descriptionProps, errorMessageProps} = useTimeField({...props, label}, state, fieldRef);
|
|
89
|
+
|
|
90
|
+
let renderProps = useRenderProps({
|
|
91
|
+
...props,
|
|
92
|
+
values: state,
|
|
93
|
+
defaultClassName: 'react-aria-TimeField'
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Provider
|
|
98
|
+
values={[
|
|
99
|
+
[DateInputContext, {state, fieldProps, ref: fieldRef}],
|
|
100
|
+
[LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}],
|
|
101
|
+
[TextContext, {
|
|
102
|
+
slots: {
|
|
103
|
+
description: descriptionProps,
|
|
104
|
+
errorMessage: errorMessageProps
|
|
105
|
+
}
|
|
106
|
+
}]
|
|
107
|
+
]}>
|
|
108
|
+
<div {...renderProps} ref={ref} slot={props.slot}>
|
|
109
|
+
{props.children}
|
|
110
|
+
</div>
|
|
111
|
+
</Provider>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A time field allows users to enter and edit time values using a keyboard.
|
|
117
|
+
* Each part of a time value is displayed in an individually editable segment.
|
|
118
|
+
*/
|
|
119
|
+
const _TimeField = (forwardRef as forwardRefType)(TimeField);
|
|
120
|
+
export {_TimeField as TimeField};
|
|
121
|
+
|
|
122
|
+
const InternalDateInputContext = createContext<DateFieldState| null>(null);
|
|
123
|
+
|
|
124
|
+
export interface DateInputRenderProps {
|
|
125
|
+
/**
|
|
126
|
+
* Whether the date input is currently hovered with a mouse.
|
|
127
|
+
* @selector [data-hovered]
|
|
128
|
+
*/
|
|
129
|
+
isHovered: boolean,
|
|
130
|
+
/**
|
|
131
|
+
* Whether an element within the date input is focused, either via a mouse or keyboard.
|
|
132
|
+
* @selector :focus-within
|
|
133
|
+
*/
|
|
134
|
+
isFocusWithin: boolean,
|
|
135
|
+
/**
|
|
136
|
+
* Whether an element within the date input is keyboard focused.
|
|
137
|
+
* @selector [data-focus-visible]
|
|
138
|
+
*/
|
|
139
|
+
isFocusVisible: boolean,
|
|
140
|
+
/**
|
|
141
|
+
* Whether the date input is disabled.
|
|
142
|
+
* @selector [data-disabled]
|
|
143
|
+
*/
|
|
144
|
+
isDisabled: boolean
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface DateInputProps extends SlotProps, StyleRenderProps<DateInputRenderProps> {
|
|
148
|
+
children: (segment: IDateSegment) => ReactElement
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function DateInput({children, slot, ...otherProps}: DateInputProps, ref: ForwardedRef<HTMLDivElement>) {
|
|
152
|
+
let [{state, fieldProps}, fieldRef] = useContextProps({slot} as DateInputProps & DateInputContextValue, ref, DateInputContext);
|
|
153
|
+
|
|
154
|
+
let {hoverProps, isHovered} = useHover({});
|
|
155
|
+
let {isFocused, isFocusVisible, focusProps} = useFocusRing({
|
|
156
|
+
within: true,
|
|
157
|
+
isTextInput: true
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
let renderProps = useRenderProps({
|
|
161
|
+
...otherProps,
|
|
162
|
+
values: {isHovered, isFocusWithin: isFocused, isFocusVisible, isDisabled: state.isDisabled},
|
|
163
|
+
defaultClassName: 'react-aria-DateInput'
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return (
|
|
167
|
+
<InternalDateInputContext.Provider value={state}>
|
|
168
|
+
<div
|
|
169
|
+
{...mergeProps(fieldProps, focusProps, hoverProps)}
|
|
170
|
+
{...renderProps}
|
|
171
|
+
ref={fieldRef}
|
|
172
|
+
slot={slot}
|
|
173
|
+
data-hovered={isHovered || undefined}
|
|
174
|
+
data-focus-visible={isFocusVisible || undefined}
|
|
175
|
+
data-disabled={state.isDisabled || undefined}>
|
|
176
|
+
{state.segments.map((segment, i) => cloneElement(children(segment), {key: i}))}
|
|
177
|
+
</div>
|
|
178
|
+
</InternalDateInputContext.Provider>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* A date input groups the editable date segments within a date field.
|
|
184
|
+
*/
|
|
185
|
+
const _DateInput = forwardRef(DateInput);
|
|
186
|
+
export {_DateInput as DateInput};
|
|
187
|
+
|
|
188
|
+
export interface DateSegmentRenderProps extends Omit<IDateSegment, 'isEditable'> {
|
|
189
|
+
/**
|
|
190
|
+
* Whether the value is a placeholder.
|
|
191
|
+
* @selector [data-placeholder]
|
|
192
|
+
*/
|
|
193
|
+
isPlaceholder: boolean,
|
|
194
|
+
/**
|
|
195
|
+
* Whether the segment is read only.
|
|
196
|
+
* @selector [aria-readonly]
|
|
197
|
+
*/
|
|
198
|
+
isReadOnly: boolean,
|
|
199
|
+
/**
|
|
200
|
+
* Whether the date field is in an invalid state.
|
|
201
|
+
* @selector [aria-invalid]
|
|
202
|
+
*/
|
|
203
|
+
isInvalid: boolean,
|
|
204
|
+
/**
|
|
205
|
+
* The type of segment. Values include `literal`, `year`, `month`, `day`, etc.
|
|
206
|
+
* @selector [data-type="..."]
|
|
207
|
+
*/
|
|
208
|
+
type: DateSegmentType
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export interface DateSegmentProps extends RenderProps<DateSegmentRenderProps> {
|
|
212
|
+
segment: IDateSegment
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function DateSegment({segment, ...otherProps}: DateSegmentProps, ref: ForwardedRef<HTMLDivElement>) {
|
|
216
|
+
let state = useContext(InternalDateInputContext)!;
|
|
217
|
+
let domRef = useObjectRef(ref);
|
|
218
|
+
let {segmentProps} = useDateSegment(segment, state, domRef);
|
|
219
|
+
let renderProps = useRenderProps({
|
|
220
|
+
...otherProps,
|
|
221
|
+
values: {
|
|
222
|
+
...segment,
|
|
223
|
+
isReadOnly: !segment.isEditable,
|
|
224
|
+
isInvalid: state.validationState === 'invalid'
|
|
225
|
+
},
|
|
226
|
+
defaultChildren: segment.text,
|
|
227
|
+
defaultClassName: 'react-aria-DateSegment'
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<div
|
|
232
|
+
{...segmentProps}
|
|
233
|
+
{...renderProps}
|
|
234
|
+
ref={domRef}
|
|
235
|
+
data-type={segment.type} />
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* A date segment displays an individual unit of a date and time, and allows users to edit
|
|
241
|
+
* the value by typing or using the arrow keys to increment and decrement.
|
|
242
|
+
*/
|
|
243
|
+
const _DateSegment = forwardRef(DateSegment);
|
|
244
|
+
export {_DateSegment as DateSegment};
|
|
@@ -0,0 +1,178 @@
|
|
|
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
|
+
import {AriaDatePickerProps, AriaDateRangePickerProps, DateValue, useDateField, useDatePicker, useDateRangePicker, useLocale} from 'react-aria';
|
|
13
|
+
import {ButtonContext} from './Button';
|
|
14
|
+
import {CalendarContext, RangeCalendarContext} from './Calendar';
|
|
15
|
+
import {ContextValue, forwardRefType, Provider, RenderProps, SlotProps, useContextProps, useRenderProps, useSlot} from './utils';
|
|
16
|
+
import {createCalendar} from '@internationalized/date';
|
|
17
|
+
import {DateInputContext} from './DateField';
|
|
18
|
+
import {DatePickerState, DateRangePickerState, useDateFieldState, useDatePickerState, useDateRangePickerState} from 'react-stately';
|
|
19
|
+
import {DialogContext} from './Dialog';
|
|
20
|
+
import {GroupContext} from './Group';
|
|
21
|
+
import {LabelContext} from './Label';
|
|
22
|
+
import {PopoverContext} from './Popover';
|
|
23
|
+
import React, {createContext, ForwardedRef, forwardRef, useRef} from 'react';
|
|
24
|
+
import {TextContext} from './Text';
|
|
25
|
+
|
|
26
|
+
export interface DatePickerProps<T extends DateValue> extends Omit<AriaDatePickerProps<T>, 'label' | 'description' | 'errorMessage'>, RenderProps<DatePickerState>, SlotProps {}
|
|
27
|
+
export interface DateRangePickerProps<T extends DateValue> extends Omit<AriaDateRangePickerProps<T>, 'label' | 'description' | 'errorMessage'>, RenderProps<DateRangePickerState>, SlotProps {}
|
|
28
|
+
|
|
29
|
+
export const DatePickerContext = createContext<ContextValue<DatePickerProps<any>, HTMLDivElement>>(null);
|
|
30
|
+
export const DateRangePickerContext = createContext<ContextValue<DateRangePickerProps<any>, HTMLDivElement>>(null);
|
|
31
|
+
|
|
32
|
+
function DatePicker<T extends DateValue>(props: DatePickerProps<T>, ref: ForwardedRef<HTMLDivElement>) {
|
|
33
|
+
[props, ref] = useContextProps(props, ref, DatePickerContext);
|
|
34
|
+
let state = useDatePickerState(props);
|
|
35
|
+
let groupRef = useRef<HTMLDivElement>(null);
|
|
36
|
+
let [labelRef, label] = useSlot();
|
|
37
|
+
let {
|
|
38
|
+
groupProps,
|
|
39
|
+
labelProps,
|
|
40
|
+
fieldProps,
|
|
41
|
+
buttonProps,
|
|
42
|
+
dialogProps,
|
|
43
|
+
calendarProps,
|
|
44
|
+
descriptionProps,
|
|
45
|
+
errorMessageProps
|
|
46
|
+
} = useDatePicker({...props, label}, state, groupRef);
|
|
47
|
+
|
|
48
|
+
let {locale} = useLocale();
|
|
49
|
+
let fieldState = useDateFieldState({
|
|
50
|
+
...fieldProps,
|
|
51
|
+
locale,
|
|
52
|
+
createCalendar
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let fieldRef = useRef<HTMLDivElement>(null);
|
|
56
|
+
let {fieldProps: dateFieldProps} = useDateField({...fieldProps, label}, fieldState, fieldRef);
|
|
57
|
+
|
|
58
|
+
let renderProps = useRenderProps({
|
|
59
|
+
...props,
|
|
60
|
+
values: state,
|
|
61
|
+
defaultClassName: 'react-aria-DatePicker'
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Provider
|
|
66
|
+
values={[
|
|
67
|
+
[GroupContext, {...groupProps, ref: groupRef}],
|
|
68
|
+
[DateInputContext, {state: fieldState, fieldProps: dateFieldProps, ref: fieldRef}],
|
|
69
|
+
[ButtonContext, {...buttonProps, isPressed: state.isOpen}],
|
|
70
|
+
[LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}],
|
|
71
|
+
[CalendarContext, calendarProps],
|
|
72
|
+
[PopoverContext, {state, triggerRef: groupRef, placement: 'bottom start'}],
|
|
73
|
+
[DialogContext, dialogProps],
|
|
74
|
+
[TextContext, {
|
|
75
|
+
slots: {
|
|
76
|
+
description: descriptionProps,
|
|
77
|
+
errorMessage: errorMessageProps
|
|
78
|
+
}
|
|
79
|
+
}]
|
|
80
|
+
]}>
|
|
81
|
+
<div {...renderProps} ref={ref} slot={props.slot}>
|
|
82
|
+
{props.children}
|
|
83
|
+
</div>
|
|
84
|
+
</Provider>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A date picker combines a DateField and a Calendar popover to allow users to enter or select a date and time value.
|
|
90
|
+
*/
|
|
91
|
+
const _DatePicker = (forwardRef as forwardRefType)(DatePicker);
|
|
92
|
+
export {_DatePicker as DatePicker};
|
|
93
|
+
|
|
94
|
+
function DateRangePicker<T extends DateValue>(props: DateRangePickerProps<T>, ref: ForwardedRef<HTMLDivElement>) {
|
|
95
|
+
[props, ref] = useContextProps(props, ref, DateRangePickerContext);
|
|
96
|
+
let state = useDateRangePickerState(props);
|
|
97
|
+
let groupRef = useRef<HTMLDivElement>(null);
|
|
98
|
+
let [labelRef, label] = useSlot();
|
|
99
|
+
let {
|
|
100
|
+
groupProps,
|
|
101
|
+
labelProps,
|
|
102
|
+
startFieldProps,
|
|
103
|
+
endFieldProps,
|
|
104
|
+
buttonProps,
|
|
105
|
+
dialogProps,
|
|
106
|
+
calendarProps,
|
|
107
|
+
descriptionProps,
|
|
108
|
+
errorMessageProps
|
|
109
|
+
} = useDateRangePicker({...props, label}, state, groupRef);
|
|
110
|
+
|
|
111
|
+
let {locale} = useLocale();
|
|
112
|
+
let startFieldState = useDateFieldState({
|
|
113
|
+
...startFieldProps,
|
|
114
|
+
locale,
|
|
115
|
+
createCalendar
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
let startFieldRef = useRef<HTMLDivElement>(null);
|
|
119
|
+
let {fieldProps: startDateFieldProps} = useDateField({...startFieldProps, label}, startFieldState, startFieldRef);
|
|
120
|
+
|
|
121
|
+
let endFieldState = useDateFieldState({
|
|
122
|
+
...endFieldProps,
|
|
123
|
+
locale,
|
|
124
|
+
createCalendar
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
let endFieldRef = useRef<HTMLDivElement>(null);
|
|
128
|
+
let {fieldProps: endDateFieldProps} = useDateField({...endFieldProps, label}, endFieldState, endFieldRef);
|
|
129
|
+
|
|
130
|
+
let renderProps = useRenderProps({
|
|
131
|
+
...props,
|
|
132
|
+
values: state,
|
|
133
|
+
defaultClassName: 'react-aria-DateRangePicker'
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<Provider
|
|
138
|
+
values={[
|
|
139
|
+
[GroupContext, {...groupProps, ref: groupRef}],
|
|
140
|
+
[ButtonContext, {...buttonProps, isPressed: state.isOpen}],
|
|
141
|
+
[LabelContext, {...labelProps, ref: labelRef, elementType: 'span'}],
|
|
142
|
+
[RangeCalendarContext, calendarProps],
|
|
143
|
+
[PopoverContext, {state, triggerRef: groupRef, placement: 'bottom start'}],
|
|
144
|
+
[DialogContext, dialogProps],
|
|
145
|
+
[DateInputContext, {
|
|
146
|
+
slots: {
|
|
147
|
+
start: {
|
|
148
|
+
state: startFieldState,
|
|
149
|
+
fieldProps: startDateFieldProps,
|
|
150
|
+
ref: startFieldRef
|
|
151
|
+
},
|
|
152
|
+
end: {
|
|
153
|
+
state: endFieldState,
|
|
154
|
+
fieldProps: endDateFieldProps,
|
|
155
|
+
ref: endFieldRef
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}],
|
|
159
|
+
[TextContext, {
|
|
160
|
+
slots: {
|
|
161
|
+
description: descriptionProps,
|
|
162
|
+
errorMessage: errorMessageProps
|
|
163
|
+
}
|
|
164
|
+
}]
|
|
165
|
+
]}>
|
|
166
|
+
<div {...renderProps} ref={ref} slot={props.slot}>
|
|
167
|
+
{props.children}
|
|
168
|
+
</div>
|
|
169
|
+
</Provider>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A date range picker combines two DateFields and a RangeCalendar popover to allow
|
|
175
|
+
* users to enter or select a date and time range.
|
|
176
|
+
*/
|
|
177
|
+
const _DateRangePicker = (forwardRef as forwardRefType)(DateRangePicker);
|
|
178
|
+
export {_DateRangePicker as DateRangePicker};
|
package/src/Dialog.tsx
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
import {AriaDialogProps, useDialog, useOverlayTrigger} from 'react-aria';
|
|
13
|
+
import {ButtonContext} from './Button';
|
|
14
|
+
import {ContextValue, DOMProps, Provider, SlotProps, useContextProps} from './utils';
|
|
15
|
+
import {filterDOMProps} from '@react-aria/utils';
|
|
16
|
+
import {HeadingContext} from './Heading';
|
|
17
|
+
import {ModalContext} from './Modal';
|
|
18
|
+
import {OverlayTriggerProps, useOverlayTriggerState} from 'react-stately';
|
|
19
|
+
import {PopoverContext} from './Popover';
|
|
20
|
+
import React, {createContext, ForwardedRef, forwardRef, ReactNode, useRef} from 'react';
|
|
21
|
+
|
|
22
|
+
export interface DialogTriggerProps extends OverlayTriggerProps {
|
|
23
|
+
children: ReactNode
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface DialogRenderProps {
|
|
27
|
+
close: () => void
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface DialogProps extends AriaDialogProps, DOMProps, SlotProps {
|
|
31
|
+
children?: ReactNode | ((opts: DialogRenderProps) => ReactNode),
|
|
32
|
+
onClose?: () => void
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const DialogContext = createContext<ContextValue<DialogProps, HTMLElement>>(null);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A DialogTrigger opens a dialog when a trigger element is pressed.
|
|
39
|
+
*/
|
|
40
|
+
export function DialogTrigger(props: DialogTriggerProps) {
|
|
41
|
+
let state = useOverlayTriggerState(props);
|
|
42
|
+
|
|
43
|
+
let buttonRef = useRef<HTMLButtonElement>(null);
|
|
44
|
+
let {triggerProps, overlayProps} = useOverlayTrigger({type: 'dialog'}, state, buttonRef);
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<Provider
|
|
48
|
+
values={[
|
|
49
|
+
[ModalContext, {state}],
|
|
50
|
+
[DialogContext, {...overlayProps, onClose: state.close}],
|
|
51
|
+
[ButtonContext, {...triggerProps, isPressed: state.isOpen, ref: buttonRef}],
|
|
52
|
+
[PopoverContext, {state, triggerRef: buttonRef}]
|
|
53
|
+
]}>
|
|
54
|
+
{props.children}
|
|
55
|
+
</Provider>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
function Dialog(props: DialogProps, ref: ForwardedRef<HTMLElement>) {
|
|
61
|
+
[props, ref] = useContextProps(props, ref, DialogContext);
|
|
62
|
+
let {dialogProps, titleProps} = useDialog(props, ref);
|
|
63
|
+
|
|
64
|
+
let children = props.children;
|
|
65
|
+
if (typeof children === 'function') {
|
|
66
|
+
children = children({
|
|
67
|
+
close: props.onClose || (() => {})
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<section
|
|
73
|
+
{...filterDOMProps(props)}
|
|
74
|
+
{...dialogProps}
|
|
75
|
+
ref={ref}
|
|
76
|
+
slot={props.slot}
|
|
77
|
+
style={props.style}
|
|
78
|
+
className={props.className ?? 'react-aria-Dialog'}>
|
|
79
|
+
<Provider
|
|
80
|
+
values={[
|
|
81
|
+
[ButtonContext, undefined],
|
|
82
|
+
// TODO: clear context within dialog content?
|
|
83
|
+
[HeadingContext, {...titleProps, level: 2}]
|
|
84
|
+
]}>
|
|
85
|
+
{children}
|
|
86
|
+
</Provider>
|
|
87
|
+
</section>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* A dialog is an overlay shown above other content in an application.
|
|
93
|
+
*/
|
|
94
|
+
const _Dialog = forwardRef(Dialog);
|
|
95
|
+
export {_Dialog as Dialog};
|