@react-spectrum/s2 3.0.0-nightly-fee532d6a-241217 → 3.0.0-nightly-50c7ada5d-241219

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/dist/ActionBar.cjs +262 -0
  2. package/dist/ActionBar.cjs.map +1 -0
  3. package/dist/ActionBar.css +284 -0
  4. package/dist/ActionBar.css.map +1 -0
  5. package/dist/ActionBar.mjs +255 -0
  6. package/dist/ActionBar.mjs.map +1 -0
  7. package/dist/ActionButtonGroup.cjs.map +1 -1
  8. package/dist/ActionButtonGroup.css.map +1 -1
  9. package/dist/ActionButtonGroup.mjs.map +1 -1
  10. package/dist/Button.cjs +49 -9
  11. package/dist/Button.cjs.map +1 -1
  12. package/dist/Button.css +42 -18
  13. package/dist/Button.css.map +1 -1
  14. package/dist/Button.mjs +49 -9
  15. package/dist/Button.mjs.map +1 -1
  16. package/dist/CardView.cjs +33 -6
  17. package/dist/CardView.cjs.map +1 -1
  18. package/dist/CardView.css +28 -4
  19. package/dist/CardView.css.map +1 -1
  20. package/dist/CardView.mjs +35 -8
  21. package/dist/CardView.mjs.map +1 -1
  22. package/dist/CheckboxGroup.cjs +2 -1
  23. package/dist/CheckboxGroup.cjs.map +1 -1
  24. package/dist/CheckboxGroup.css.map +1 -1
  25. package/dist/CheckboxGroup.mjs +2 -1
  26. package/dist/CheckboxGroup.mjs.map +1 -1
  27. package/dist/CloseButton.cjs +9 -10
  28. package/dist/CloseButton.cjs.map +1 -1
  29. package/dist/CloseButton.css +4 -4
  30. package/dist/CloseButton.css.map +1 -1
  31. package/dist/CloseButton.mjs +9 -10
  32. package/dist/CloseButton.mjs.map +1 -1
  33. package/dist/Field.cjs +16 -10
  34. package/dist/Field.cjs.map +1 -1
  35. package/dist/Field.css +0 -4
  36. package/dist/Field.css.map +1 -1
  37. package/dist/Field.mjs +16 -10
  38. package/dist/Field.mjs.map +1 -1
  39. package/dist/InlineAlert.cjs +1 -6
  40. package/dist/InlineAlert.cjs.map +1 -1
  41. package/dist/InlineAlert.css +0 -4
  42. package/dist/InlineAlert.css.map +1 -1
  43. package/dist/InlineAlert.mjs +1 -6
  44. package/dist/InlineAlert.mjs.map +1 -1
  45. package/dist/Menu.cjs +2 -2
  46. package/dist/Menu.cjs.map +1 -1
  47. package/dist/Menu.css.map +1 -1
  48. package/dist/Menu.mjs +2 -2
  49. package/dist/Menu.mjs.map +1 -1
  50. package/dist/TableView.cjs +54 -29
  51. package/dist/TableView.cjs.map +1 -1
  52. package/dist/TableView.css +12 -12
  53. package/dist/TableView.css.map +1 -1
  54. package/dist/TableView.mjs +55 -30
  55. package/dist/TableView.mjs.map +1 -1
  56. package/dist/ar-AE.cjs +10 -2
  57. package/dist/ar-AE.cjs.map +1 -1
  58. package/dist/ar-AE.mjs +10 -2
  59. package/dist/ar-AE.mjs.map +1 -1
  60. package/dist/bg-BG.cjs +11 -2
  61. package/dist/bg-BG.cjs.map +1 -1
  62. package/dist/bg-BG.mjs +11 -2
  63. package/dist/bg-BG.mjs.map +1 -1
  64. package/dist/cs-CZ.cjs +7 -2
  65. package/dist/cs-CZ.cjs.map +1 -1
  66. package/dist/cs-CZ.mjs +7 -2
  67. package/dist/cs-CZ.mjs.map +1 -1
  68. package/dist/da-DK.cjs +12 -4
  69. package/dist/da-DK.cjs.map +1 -1
  70. package/dist/da-DK.mjs +12 -4
  71. package/dist/da-DK.mjs.map +1 -1
  72. package/dist/de-DE.cjs +11 -2
  73. package/dist/de-DE.cjs.map +1 -1
  74. package/dist/de-DE.mjs +11 -2
  75. package/dist/de-DE.mjs.map +1 -1
  76. package/dist/el-GR.cjs +11 -2
  77. package/dist/el-GR.cjs.map +1 -1
  78. package/dist/el-GR.mjs +11 -2
  79. package/dist/el-GR.mjs.map +1 -1
  80. package/dist/en-US.cjs +8 -0
  81. package/dist/en-US.cjs.map +1 -1
  82. package/dist/en-US.mjs +8 -0
  83. package/dist/en-US.mjs.map +1 -1
  84. package/dist/es-ES.cjs +11 -2
  85. package/dist/es-ES.cjs.map +1 -1
  86. package/dist/es-ES.mjs +11 -2
  87. package/dist/es-ES.mjs.map +1 -1
  88. package/dist/et-EE.cjs +10 -2
  89. package/dist/et-EE.cjs.map +1 -1
  90. package/dist/et-EE.mjs +10 -2
  91. package/dist/et-EE.mjs.map +1 -1
  92. package/dist/fi-FI.cjs +10 -2
  93. package/dist/fi-FI.cjs.map +1 -1
  94. package/dist/fi-FI.mjs +10 -2
  95. package/dist/fi-FI.mjs.map +1 -1
  96. package/dist/fr-FR.cjs +11 -2
  97. package/dist/fr-FR.cjs.map +1 -1
  98. package/dist/fr-FR.mjs +11 -2
  99. package/dist/fr-FR.mjs.map +1 -1
  100. package/dist/he-IL.cjs +11 -2
  101. package/dist/he-IL.cjs.map +1 -1
  102. package/dist/he-IL.mjs +11 -2
  103. package/dist/he-IL.mjs.map +1 -1
  104. package/dist/hr-HR.cjs +7 -2
  105. package/dist/hr-HR.cjs.map +1 -1
  106. package/dist/hr-HR.mjs +7 -2
  107. package/dist/hr-HR.mjs.map +1 -1
  108. package/dist/hu-HU.cjs +10 -2
  109. package/dist/hu-HU.cjs.map +1 -1
  110. package/dist/hu-HU.mjs +10 -2
  111. package/dist/hu-HU.mjs.map +1 -1
  112. package/dist/it-IT.cjs +11 -2
  113. package/dist/it-IT.cjs.map +1 -1
  114. package/dist/it-IT.mjs +11 -2
  115. package/dist/it-IT.mjs.map +1 -1
  116. package/dist/ja-JP.cjs +10 -2
  117. package/dist/ja-JP.cjs.map +1 -1
  118. package/dist/ja-JP.mjs +10 -2
  119. package/dist/ja-JP.mjs.map +1 -1
  120. package/dist/ko-KR.cjs +12 -4
  121. package/dist/ko-KR.cjs.map +1 -1
  122. package/dist/ko-KR.mjs +12 -4
  123. package/dist/ko-KR.mjs.map +1 -1
  124. package/dist/lt-LT.cjs +9 -4
  125. package/dist/lt-LT.cjs.map +1 -1
  126. package/dist/lt-LT.mjs +9 -4
  127. package/dist/lt-LT.mjs.map +1 -1
  128. package/dist/lv-LV.cjs +11 -3
  129. package/dist/lv-LV.cjs.map +1 -1
  130. package/dist/lv-LV.mjs +11 -3
  131. package/dist/lv-LV.mjs.map +1 -1
  132. package/dist/main.cjs +4 -0
  133. package/dist/main.cjs.map +1 -1
  134. package/dist/module.mjs +3 -1
  135. package/dist/module.mjs.map +1 -1
  136. package/dist/nb-NO.cjs +9 -4
  137. package/dist/nb-NO.cjs.map +1 -1
  138. package/dist/nb-NO.mjs +9 -4
  139. package/dist/nb-NO.mjs.map +1 -1
  140. package/dist/nl-NL.cjs +10 -2
  141. package/dist/nl-NL.cjs.map +1 -1
  142. package/dist/nl-NL.mjs +10 -2
  143. package/dist/nl-NL.mjs.map +1 -1
  144. package/dist/pl-PL.cjs +8 -3
  145. package/dist/pl-PL.cjs.map +1 -1
  146. package/dist/pl-PL.mjs +8 -3
  147. package/dist/pl-PL.mjs.map +1 -1
  148. package/dist/pt-BR.cjs +11 -2
  149. package/dist/pt-BR.cjs.map +1 -1
  150. package/dist/pt-BR.mjs +11 -2
  151. package/dist/pt-BR.mjs.map +1 -1
  152. package/dist/pt-PT.cjs +11 -2
  153. package/dist/pt-PT.cjs.map +1 -1
  154. package/dist/pt-PT.mjs +11 -2
  155. package/dist/pt-PT.mjs.map +1 -1
  156. package/dist/ro-RO.cjs +12 -3
  157. package/dist/ro-RO.cjs.map +1 -1
  158. package/dist/ro-RO.mjs +12 -3
  159. package/dist/ro-RO.mjs.map +1 -1
  160. package/dist/ru-RU.cjs +7 -2
  161. package/dist/ru-RU.cjs.map +1 -1
  162. package/dist/ru-RU.mjs +7 -2
  163. package/dist/ru-RU.mjs.map +1 -1
  164. package/dist/sk-SK.cjs +7 -2
  165. package/dist/sk-SK.cjs.map +1 -1
  166. package/dist/sk-SK.mjs +7 -2
  167. package/dist/sk-SK.mjs.map +1 -1
  168. package/dist/sl-SI.cjs +7 -2
  169. package/dist/sl-SI.cjs.map +1 -1
  170. package/dist/sl-SI.mjs +7 -2
  171. package/dist/sl-SI.mjs.map +1 -1
  172. package/dist/sr-SP.cjs +7 -2
  173. package/dist/sr-SP.cjs.map +1 -1
  174. package/dist/sr-SP.mjs +7 -2
  175. package/dist/sr-SP.mjs.map +1 -1
  176. package/dist/sv-SE.cjs +11 -2
  177. package/dist/sv-SE.cjs.map +1 -1
  178. package/dist/sv-SE.mjs +11 -2
  179. package/dist/sv-SE.mjs.map +1 -1
  180. package/dist/tr-TR.cjs +10 -2
  181. package/dist/tr-TR.cjs.map +1 -1
  182. package/dist/tr-TR.mjs +10 -2
  183. package/dist/tr-TR.mjs.map +1 -1
  184. package/dist/types.d.ts +34 -18
  185. package/dist/types.d.ts.map +1 -1
  186. package/dist/uk-UA.cjs +7 -2
  187. package/dist/uk-UA.cjs.map +1 -1
  188. package/dist/uk-UA.mjs +7 -2
  189. package/dist/uk-UA.mjs.map +1 -1
  190. package/dist/zh-CN.cjs +10 -2
  191. package/dist/zh-CN.cjs.map +1 -1
  192. package/dist/zh-CN.mjs +10 -2
  193. package/dist/zh-CN.mjs.map +1 -1
  194. package/dist/zh-TW.cjs +10 -2
  195. package/dist/zh-TW.cjs.map +1 -1
  196. package/dist/zh-TW.mjs +10 -2
  197. package/dist/zh-TW.mjs.map +1 -1
  198. package/package.json +19 -18
  199. package/src/ActionBar.tsx +235 -0
  200. package/src/ActionButtonGroup.tsx +3 -2
  201. package/src/Button.tsx +56 -7
  202. package/src/CardView.tsx +35 -8
  203. package/src/CheckboxGroup.tsx +1 -1
  204. package/src/CloseButton.tsx +4 -5
  205. package/src/Field.tsx +17 -15
  206. package/src/InlineAlert.tsx +0 -1
  207. package/src/Menu.tsx +2 -2
  208. package/src/TableView.tsx +32 -18
  209. package/src/index.ts +2 -0
  210. package/style/dist/main.cjs +1 -0
  211. package/style/dist/main.cjs.map +1 -1
  212. package/style/dist/module.mjs +2 -2
  213. package/style/dist/module.mjs.map +1 -1
  214. package/style/dist/spectrum-theme.cjs +4 -0
  215. package/style/dist/spectrum-theme.cjs.map +1 -1
  216. package/style/dist/spectrum-theme.mjs +4 -1
  217. package/style/dist/spectrum-theme.mjs.map +1 -1
  218. package/style/dist/types.d.ts +1 -0
  219. package/style/dist/types.d.ts.map +1 -1
  220. package/style/index.ts +1 -1
  221. package/style/spectrum-theme.ts +4 -0
@@ -0,0 +1,235 @@
1
+ /*
2
+ * Copyright 2024 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 {ActionButtonGroup} from './ActionButtonGroup';
14
+ import {announce} from '@react-aria/live-announcer';
15
+ import {CloseButton} from './CloseButton';
16
+ import {ContextValue, SlotProps} from 'react-aria-components';
17
+ import {createContext, ForwardedRef, forwardRef, ReactElement, ReactNode, RefObject, useCallback, useEffect, useMemo, useRef, useState} from 'react';
18
+ import {DOMRef, DOMRefValue, Key} from '@react-types/shared';
19
+ import {FocusScope, useKeyboard} from 'react-aria';
20
+ // @ts-ignore
21
+ import intlMessages from '../intl/*.json';
22
+ import {keyframes} from '../style/style-macro' with {type: 'macro'};
23
+ import {style} from '../style' with {type: 'macro'};
24
+ import {useControlledState} from '@react-stately/utils';
25
+ import {useDOMRef} from '@react-spectrum/utils';
26
+ import {useExitAnimation, useResizeObserver} from '@react-aria/utils';
27
+ import {useLocalizedStringFormatter} from '@react-aria/i18n';
28
+ import {useSpectrumContextProps} from './useSpectrumContextProps';
29
+
30
+ const slideIn = keyframes(`
31
+ from { transform: translateY(100%); opacity: 0 }
32
+ to { transform: translateY(0px); opacity: 1 }
33
+ `);
34
+
35
+ const slideOut = keyframes(`
36
+ from { transform: translateY(0px); opacity: 1 }
37
+ to { transform: translateY(100%); opacity: 0 }
38
+ `);
39
+
40
+ const actionBarStyles = style({
41
+ borderRadius: 'lg',
42
+ '--s2-container-bg': {
43
+ type: 'backgroundColor',
44
+ value: {
45
+ default: 'elevated',
46
+ isEmphasized: 'neutral'
47
+ }
48
+ },
49
+ backgroundColor: '--s2-container-bg',
50
+ boxShadow: 'elevated',
51
+ boxSizing: 'border-box',
52
+ outlineStyle: 'solid',
53
+ outlineWidth: 1,
54
+ outlineOffset: -1,
55
+ outlineColor: {
56
+ default: 'gray-200',
57
+ isEmphasized: 'transparent',
58
+ forcedColors: 'ButtonBorder'
59
+ },
60
+ paddingX: 8,
61
+ paddingY: 12,
62
+ display: 'flex',
63
+ gap: 16,
64
+ alignItems: 'center',
65
+ position: {
66
+ isInContainer: 'absolute'
67
+ },
68
+ bottom: 8,
69
+ insetStart: 8,
70
+ '--insetEnd': {
71
+ type: 'insetEnd',
72
+ value: 8
73
+ },
74
+ width: {
75
+ default: 'full',
76
+ isInContainer: 'auto'
77
+ },
78
+ marginX: 'auto',
79
+ maxWidth: 960,
80
+ animation: {
81
+ isInContainer: slideIn,
82
+ isExiting: slideOut
83
+ },
84
+ animationDuration: 200
85
+ });
86
+
87
+ export interface ActionBarProps extends SlotProps {
88
+ /** A list of ActionButtons to display. */
89
+ children: ReactNode,
90
+ /** Whether the ActionBar should be displayed with a emphasized style. */
91
+ isEmphasized?: boolean,
92
+ /** The number of selected items that the ActionBar is currently linked to. If 0, the ActionBar is hidden. */
93
+ selectedItemCount?: number | 'all',
94
+ /** Handler that is called when the ActionBar clear button is pressed. */
95
+ onClearSelection?: () => void,
96
+ /** A ref to the scrollable element the ActionBar appears above. */
97
+ scrollRef?: RefObject<HTMLElement | null>
98
+ }
99
+
100
+ export const ActionBarContext = createContext<ContextValue<Partial<ActionBarProps>, DOMRefValue<HTMLDivElement>>>(null);
101
+
102
+ export const ActionBar = forwardRef(function ActionBar(props: ActionBarProps, ref: DOMRef<HTMLDivElement>) {
103
+ [props, ref] = useSpectrumContextProps(props, ref, ActionBarContext);
104
+ let domRef = useDOMRef(ref);
105
+
106
+ let isOpen = props.selectedItemCount !== 0;
107
+ let isExiting = useExitAnimation(domRef, isOpen && props.scrollRef != null);
108
+ if (!isOpen && !isExiting) {
109
+ return null;
110
+ }
111
+
112
+ return <ActionBarInner {...props} ref={domRef} isExiting={isExiting} />;
113
+ });
114
+
115
+ const ActionBarInner = forwardRef(function ActionBarInner(props: ActionBarProps & {isExiting: boolean}, ref: ForwardedRef<HTMLDivElement | null>) {
116
+ let {isEmphasized, selectedItemCount = 0, children, onClearSelection, isExiting} = props;
117
+ let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
118
+
119
+ // Store the last count greater than zero so that we can retain it while rendering the fade-out animation.
120
+ let [lastCount, setLastCount] = useState(selectedItemCount);
121
+ if ((selectedItemCount === 'all' || selectedItemCount > 0) && selectedItemCount !== lastCount) {
122
+ setLastCount(selectedItemCount);
123
+ }
124
+
125
+ // Measure the width of the collection's scrollbar and offset the action bar by that amount.
126
+ let scrollRef = props.scrollRef;
127
+ let [scrollbarWidth, setScrollbarWidth] = useState(0);
128
+ let updateScrollbarWidth = useCallback(() => {
129
+ let el = scrollRef?.current;
130
+ if (el) {
131
+ let w = el.offsetWidth - el.clientWidth;
132
+ setScrollbarWidth(w);
133
+ }
134
+ }, [scrollRef]);
135
+
136
+ useResizeObserver({
137
+ ref: scrollRef,
138
+ onResize: updateScrollbarWidth
139
+ });
140
+
141
+ let {keyboardProps} = useKeyboard({
142
+ onKeyDown(e) {
143
+ if (e.key === 'Escape') {
144
+ e.preventDefault();
145
+ onClearSelection?.();
146
+ } else {
147
+ e.continuePropagation();
148
+ }
149
+ }
150
+ });
151
+
152
+ // Announce "actions available" on mount.
153
+ let isInitial = useRef(true);
154
+ useEffect(() => {
155
+ if (isInitial.current && scrollRef) {
156
+ isInitial.current = false;
157
+ announce(stringFormatter.format('actionbar.actionsAvailable'));
158
+ }
159
+ }, [stringFormatter, scrollRef]);
160
+
161
+ return (
162
+ <FocusScope restoreFocus>
163
+ <div
164
+ ref={ref}
165
+ {...keyboardProps}
166
+ className={actionBarStyles({isEmphasized, isInContainer: !!scrollRef, isExiting})}
167
+ style={{insetInlineEnd: `calc(var(--insetEnd) + ${scrollbarWidth}px)`}}>
168
+ <div className={style({order: 1, marginStart: 'auto'})}>
169
+ <ActionButtonGroup
170
+ staticColor={isEmphasized ? 'auto' : undefined}
171
+ isQuiet
172
+ aria-label={stringFormatter.format('actionbar.actions')}>
173
+ {children}
174
+ </ActionButtonGroup>
175
+ </div>
176
+ <div className={style({order: 0, display: 'flex', alignItems: 'center', gap: 4})}>
177
+ <CloseButton
178
+ staticColor={isEmphasized ? 'auto' : undefined}
179
+ aria-label={stringFormatter.format('actionbar.clearSelection')}
180
+ onPress={() => onClearSelection?.()} />
181
+ <span className={style({font: 'ui', color: {default: 'neutral', isEmphasized: 'gray-25'}})({isEmphasized})}>
182
+ {lastCount === 'all'
183
+ ? stringFormatter.format('actionbar.selectedAll')
184
+ : stringFormatter.format('actionbar.selected', {count: lastCount})}
185
+ </span>
186
+ </div>
187
+ </div>
188
+ </FocusScope>
189
+ );
190
+ });
191
+
192
+ interface ActionBarContainerHookProps {
193
+ selectedKeys?: 'all' | Iterable<Key>,
194
+ defaultSelectedKeys?: 'all' | Iterable<Key>,
195
+ onSelectionChange?: (keys: Set<Key>) => void,
196
+ renderActionBar?: (selectedKeys: 'all' | Set<Key>) => ReactElement,
197
+ scrollRef?: RefObject<HTMLElement | null>
198
+ }
199
+
200
+ export function useActionBarContainer(props: ActionBarContainerHookProps) {
201
+ let {renderActionBar, scrollRef} = props;
202
+ let [selectedKeys, setSelectedKeys] = useControlledState(props.selectedKeys, props.defaultSelectedKeys || new Set(), props.onSelectionChange);
203
+ let selectedKeysSet = useMemo(() => selectedKeys === 'all' ? selectedKeys as 'all' : new Set(selectedKeys), [selectedKeys]);
204
+ let actionBar = useMemo(() => renderActionBar?.(selectedKeysSet), [renderActionBar, selectedKeysSet]);
205
+ let selectedItemCount = selectedKeysSet === 'all' ? 'all' as const : selectedKeysSet.size;
206
+ let [actionBarHeight, setActionBarHeight] = useState(0);
207
+ let actionBarRef = useCallback((ref: DOMRefValue | null) => {
208
+ let actionBar = ref?.UNSAFE_getDOMNode();
209
+ if (actionBar) {
210
+ setActionBarHeight(actionBar.offsetHeight + 8);
211
+ } else {
212
+ setActionBarHeight(0);
213
+ }
214
+ }, []);
215
+
216
+ let actionBarContext = useMemo(() => ({
217
+ ref: actionBarRef,
218
+ scrollRef,
219
+ selectedItemCount,
220
+ onClearSelection: () => setSelectedKeys(new Set())
221
+ }), [scrollRef, actionBarRef, selectedItemCount, setSelectedKeys]);
222
+
223
+ let wrappedActionBar = useMemo(() => (
224
+ <ActionBarContext.Provider value={actionBarContext}>
225
+ {actionBar}
226
+ </ActionBarContext.Provider>
227
+ ), [actionBarContext, actionBar]);
228
+
229
+ return {
230
+ selectedKeys,
231
+ onSelectionChange: setSelectedKeys,
232
+ actionBar: wrappedActionBar,
233
+ actionBarHeight
234
+ };
235
+ }
@@ -10,13 +10,14 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
+ import {AriaLabelingProps} from '@react-types/shared';
13
14
  import {ContextValue, SlotProps, Toolbar} from 'react-aria-components';
14
15
  import {createContext, ForwardedRef, forwardRef, ReactNode} from 'react';
15
16
  import {getAllowedOverrides, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
16
17
  import {style} from '../style' with {type: 'macro'};
17
18
  import {useSpectrumContextProps} from './useSpectrumContextProps';
18
19
 
19
- export interface ActionButtonGroupProps extends UnsafeStyles, SlotProps {
20
+ export interface ActionButtonGroupProps extends AriaLabelingProps, UnsafeStyles, SlotProps {
20
21
  /** Spectrum-defined styles, returned by the `style()` macro. */
21
22
  styles?: StylesPropWithHeight,
22
23
  /** The children of the group. */
@@ -71,7 +72,7 @@ export const actionGroupStyle = style({
71
72
  }, getAllowedOverrides({height: true}));
72
73
 
73
74
 
74
- export const ActionButtonGroupContext = createContext<ContextValue<ActionButtonGroupProps, HTMLDivElement>>(null);
75
+ export const ActionButtonGroupContext = createContext<ContextValue<Partial<ActionButtonGroupProps>, HTMLDivElement>>(null);
75
76
 
76
77
  /**
77
78
  * An ActionButtonGroup is a grouping of related ActionButtons.
package/src/Button.tsx CHANGED
@@ -10,7 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import {baseColor, focusRing, fontRelative, style} from '../style' with {type: 'macro'};
13
+ import {baseColor, focusRing, fontRelative, linearGradient, style} from '../style' with {type: 'macro'};
14
14
  import {ButtonRenderProps, ContextValue, Link, LinkProps, OverlayTriggerStateContext, Provider, Button as RACButton, ButtonProps as RACButtonProps} from 'react-aria-components';
15
15
  import {centerBaseline} from './CenterBaseline';
16
16
  import {centerPadding, getAllowedOverrides, staticColor, StyleProps} from './style-utils' with {type: 'macro'};
@@ -34,7 +34,7 @@ interface ButtonStyleProps {
34
34
  *
35
35
  * @default 'primary'
36
36
  */
37
- variant?: 'primary' | 'secondary' | 'accent' | 'negative',
37
+ variant?: 'primary' | 'secondary' | 'accent' | 'negative' | 'premium' | 'genai',
38
38
  /**
39
39
  * The background style of the Button.
40
40
  *
@@ -102,6 +102,10 @@ const button = style<ButtonRenderProps & ButtonStyleProps & {isStaticColor: bool
102
102
  fillStyle: {
103
103
  fill: 0,
104
104
  outline: 2
105
+ },
106
+ variant: {
107
+ premium: 0,
108
+ genai: 0
105
109
  }
106
110
  },
107
111
  '--labelPadding': {
@@ -134,6 +138,25 @@ const button = style<ButtonRenderProps & ButtonStyleProps & {isStaticColor: bool
134
138
  isDisabled: 'GrayText'
135
139
  }
136
140
  },
141
+ backgroundImage: {
142
+ variant: {
143
+ premium: {
144
+ default: linearGradient('96deg', ['fuchsia-900', 0], ['indigo-900', 66], ['blue-900', 100]),
145
+ isHovered: linearGradient('96deg', ['fuchsia-1000', 0], ['indigo-1000', 66], ['blue-1000', 100]),
146
+ isPressed: linearGradient('96deg', ['fuchsia-1000', 0], ['indigo-1000', 66], ['blue-1000', 100]),
147
+ isFocusVisible: linearGradient('96deg', ['fuchsia-1000', 0], ['indigo-1000', 66], ['blue-1000', 100])
148
+ },
149
+ genai: {
150
+ default: linearGradient('96deg', ['red-900', 0], ['magenta-900', 33], ['indigo-900', 100]),
151
+ isHovered: linearGradient('96deg', ['red-1000', 0], ['magenta-1000', 33], ['indigo-1000', 100]),
152
+ isPressed: linearGradient('96deg', ['red-1000', 0], ['magenta-1000', 33], ['indigo-1000', 100]),
153
+ isFocusVisible: linearGradient('96deg', ['red-1000', 0], ['magenta-1000', 33], ['indigo-1000', 100])
154
+ }
155
+ },
156
+ isDisabled: 'none',
157
+ isPending: 'none',
158
+ forcedColors: 'none'
159
+ },
137
160
  backgroundColor: {
138
161
  fillStyle: {
139
162
  fill: {
@@ -141,11 +164,17 @@ const button = style<ButtonRenderProps & ButtonStyleProps & {isStaticColor: bool
141
164
  primary: 'neutral',
142
165
  secondary: baseColor('gray-100'),
143
166
  accent: 'accent',
144
- negative: 'negative'
167
+ negative: 'negative',
168
+ premium: 'gray-100',
169
+ genai: 'gray-100'
145
170
  },
146
171
  isDisabled: 'disabled'
147
172
  },
148
173
  outline: {
174
+ variant: {
175
+ premium: 'gray-100',
176
+ genai: 'gray-100'
177
+ },
149
178
  default: 'transparent',
150
179
  isHovered: 'gray-100',
151
180
  isPressed: 'gray-100',
@@ -158,11 +187,17 @@ const button = style<ButtonRenderProps & ButtonStyleProps & {isStaticColor: bool
158
187
  fill: {
159
188
  variant: {
160
189
  primary: baseColor('transparent-overlay-800'),
161
- secondary: baseColor('transparent-overlay-100')
190
+ secondary: baseColor('transparent-overlay-100'),
191
+ premium: 'gray-800',
192
+ genai: 'gray-800'
162
193
  },
163
194
  isDisabled: 'transparent-overlay-100'
164
195
  },
165
196
  outline: {
197
+ variant: {
198
+ premium: 'gray-800',
199
+ genai: 'gray-800'
200
+ },
166
201
  default: 'transparent',
167
202
  isHovered: 'transparent-overlay-100',
168
203
  isPressed: 'transparent-overlay-100',
@@ -189,12 +224,18 @@ const button = style<ButtonRenderProps & ButtonStyleProps & {isStaticColor: bool
189
224
  primary: 'gray-25',
190
225
  secondary: 'neutral',
191
226
  accent: 'white',
192
- negative: 'white'
227
+ negative: 'white',
228
+ premium: 'white',
229
+ genai: 'white'
193
230
  },
194
231
  isDisabled: 'disabled'
195
232
  },
196
233
  outline: {
197
234
  default: 'neutral',
235
+ variant: {
236
+ premium: 'white',
237
+ genai: 'white'
238
+ },
198
239
  isDisabled: 'disabled'
199
240
  }
200
241
  },
@@ -203,10 +244,18 @@ const button = style<ButtonRenderProps & ButtonStyleProps & {isStaticColor: bool
203
244
  fill: {
204
245
  variant: {
205
246
  primary: 'auto',
206
- secondary: baseColor('transparent-overlay-800')
247
+ secondary: baseColor('transparent-overlay-800'),
248
+ premium: 'white',
249
+ genai: 'white'
207
250
  }
208
251
  },
209
- outline: baseColor('transparent-overlay-800')
252
+ outline: {
253
+ variant: {
254
+ premium: 'white',
255
+ genai: 'white'
256
+ },
257
+ default: baseColor('transparent-overlay-800')
258
+ }
210
259
  },
211
260
  isDisabled: 'transparent-overlay-400'
212
261
  },
package/src/CardView.tsx CHANGED
@@ -19,12 +19,13 @@ import {
19
19
  UNSTABLE_Virtualizer
20
20
  } from 'react-aria-components';
21
21
  import {CardContext, InternalCardViewContext} from './Card';
22
- import {createContext, forwardRef, useMemo, useState} from 'react';
22
+ import {createContext, forwardRef, ReactElement, useMemo, useRef, useState} from 'react';
23
23
  import {DOMRef, DOMRefValue, forwardRefType, Key, LayoutDelegate, LoadingState, Node} from '@react-types/shared';
24
24
  import {focusRing, style} from '../style' with {type: 'macro'};
25
25
  import {getAllowedOverrides, StylesPropWithHeight, UnsafeStyles} from './style-utils' with {type: 'macro'};
26
26
  import {ImageCoordinator} from './ImageCoordinator';
27
27
  import {InvalidationContext, Layout, LayoutInfo, Rect, Size} from '@react-stately/virtualizer';
28
+ import {useActionBarContainer} from './ActionBar';
28
29
  import {useDOMRef} from '@react-spectrum/utils';
29
30
  import {useEffectEvent, useLayoutEffect, useLoadMore, useResizeObserver} from '@react-aria/utils';
30
31
  import {useSpectrumContextProps} from './useSpectrumContextProps';
@@ -60,7 +61,8 @@ export interface CardViewProps<T> extends Omit<GridListProps<T>, 'layout' | 'key
60
61
  /** Handler that is called when more items should be loaded, e.g. while scrolling near the bottom. */
61
62
  onLoadMore?: () => void,
62
63
  /** Spectrum-defined styles, returned by the `style()` macro. */
63
- styles?: StylesPropWithHeight
64
+ styles?: StylesPropWithHeight,
65
+ renderActionBar?: (selectedKeys: 'all' | Set<Key>) => ReactElement
64
66
  }
65
67
 
66
68
  class FlexibleGridLayout<T extends object> extends Layout<Node<T>, GridLayoutOptions> {
@@ -514,6 +516,7 @@ const cardViewStyles = style({
514
516
  display: {
515
517
  isEmpty: 'flex'
516
518
  },
519
+ boxSizing: 'border-box',
517
520
  flexDirection: 'column',
518
521
  alignItems: 'center',
519
522
  justifyContent: 'center',
@@ -533,6 +536,8 @@ export const CardView = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
533
536
  [props, ref] = useSpectrumContextProps(props, ref, CardViewContext);
534
537
  let {children, layout: layoutName = 'grid', size: sizeProp = 'M', density = 'regular', variant = 'primary', selectionStyle = 'checkbox', UNSAFE_className = '', UNSAFE_style, styles, ...otherProps} = props;
535
538
  let domRef = useDOMRef(ref);
539
+ let innerRef = useRef(null);
540
+ let scrollRef = props.renderActionBar ? innerRef : domRef;
536
541
  let layout = useMemo(() => {
537
542
  return layoutName === 'waterfall' ? new WaterfallLayout() : new FlexibleGridLayout();
538
543
  }, [layoutName]);
@@ -540,7 +545,7 @@ export const CardView = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
540
545
  // This calculates the maximum t-shirt size where at least two columns fit in the available width.
541
546
  let [maxSizeIndex, setMaxSizeIndex] = useState(SIZES.length - 1);
542
547
  let updateSize = useEffectEvent(() => {
543
- let w = domRef.current?.clientWidth ?? 0;
548
+ let w = scrollRef.current?.clientWidth ?? 0;
544
549
  let i = SIZES.length - 1;
545
550
  while (i > 0) {
546
551
  let opts = layoutOptions[SIZES[i]][density];
@@ -553,7 +558,7 @@ export const CardView = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
553
558
  });
554
559
 
555
560
  useResizeObserver({
556
- ref: domRef,
561
+ ref: scrollRef,
557
562
  box: 'border-box',
558
563
  onResize: updateSize
559
564
  });
@@ -570,23 +575,32 @@ export const CardView = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
570
575
  isLoading: props.loadingState !== 'idle' && props.loadingState !== 'error',
571
576
  items: props.items, // TODO: ideally this would be the collection. items won't exist for static collections, or those using <Collection>
572
577
  onLoadMore: props.onLoadMore
573
- }, domRef);
578
+ }, scrollRef);
574
579
 
575
580
  let ctx = useMemo(() => ({size, variant}), [size, variant]);
576
581
 
577
- return (
582
+ let {selectedKeys, onSelectionChange, actionBar, actionBarHeight} = useActionBarContainer({...props, scrollRef});
583
+
584
+ let cardView = (
578
585
  <UNSTABLE_Virtualizer layout={layout} layoutOptions={options}>
579
586
  <InternalCardViewContext.Provider value={GridListItem}>
580
587
  <CardContext.Provider value={ctx}>
581
588
  <ImageCoordinator>
582
589
  <AriaGridList
583
- ref={domRef}
590
+ ref={scrollRef}
584
591
  {...otherProps}
585
592
  layout="grid"
586
593
  selectionBehavior={selectionStyle === 'highlight' ? 'replace' : 'toggle'}
594
+ selectedKeys={selectedKeys}
595
+ defaultSelectedKeys={undefined}
596
+ onSelectionChange={onSelectionChange}
587
597
  style={{
588
598
  ...UNSAFE_style,
589
- scrollPadding: options.minSpace.height
599
+ // Add padding at the bottom when the action bar is visible so users can scroll to the last items.
600
+ // Also add scroll padding so keyboard navigating preserves the padding.
601
+ paddingBottom: actionBarHeight > 0 ? actionBarHeight + options.minSpace.height : 0,
602
+ scrollPadding: options.minSpace.height,
603
+ scrollPaddingBottom: actionBarHeight + options.minSpace.height
590
604
  }}
591
605
  className={renderProps => UNSAFE_className + cardViewStyles({...renderProps, isLoading: props.loadingState === 'loading'}, styles)}>
592
606
  {children}
@@ -596,4 +610,17 @@ export const CardView = /*#__PURE__*/ (forwardRef as forwardRefType)(function Ca
596
610
  </InternalCardViewContext.Provider>
597
611
  </UNSTABLE_Virtualizer>
598
612
  );
613
+
614
+ // Add extra wrapper if there is an action bar so we can position relative to it.
615
+ // ActionBar cannot be inside the GridList due to ARIA and focus management requirements.
616
+ if (props.renderActionBar) {
617
+ return (
618
+ <div ref={domRef} className={style({position: 'relative', overflow: 'clip', size: 'fit'})}>
619
+ {cardView}
620
+ {actionBar}
621
+ </div>
622
+ );
623
+ }
624
+
625
+ return cardView;
599
626
  });
@@ -120,7 +120,7 @@ export const CheckboxGroup = forwardRef(function CheckboxGroup(props: CheckboxGr
120
120
  columnGap: 16,
121
121
  flexWrap: 'wrap'
122
122
  })({orientation})}>
123
- <FormContext.Provider value={{...formContext, size}}>
123
+ <FormContext.Provider value={{...formContext, size, isRequired: undefined}}>
124
124
  <CheckboxContext.Provider value={{isEmphasized}}>
125
125
  {children}
126
126
  </CheckboxContext.Provider>
@@ -23,7 +23,7 @@ import {useFocusableRef} from '@react-spectrum/utils';
23
23
  import {useLocalizedStringFormatter} from '@react-aria/i18n';
24
24
  import {useSpectrumContextProps} from './useSpectrumContextProps';
25
25
 
26
- export interface CloseButtonProps extends Pick<ButtonProps, 'isDisabled'>, StyleProps {
26
+ export interface CloseButtonProps extends Pick<ButtonProps, 'isDisabled' | 'onPress'>, StyleProps {
27
27
  /**
28
28
  * The size of the CloseButton.
29
29
  *
@@ -34,10 +34,9 @@ export interface CloseButtonProps extends Pick<ButtonProps, 'isDisabled'>, Style
34
34
  staticColor?: 'white' | 'black' | 'auto'
35
35
  }
36
36
 
37
- // TODO(design): this is inconsistent with ActionButton
38
37
  const hoverBackground = {
39
- default: 'gray-100',
40
- isStaticColor: 'transparent-overlay-100'
38
+ default: 'gray-200',
39
+ isStaticColor: 'transparent-overlay-200'
41
40
  } as const;
42
41
 
43
42
  const styles = style({
@@ -91,7 +90,7 @@ export const CloseButton = forwardRef(function CloseButton(props: CloseButtonPro
91
90
  {...props}
92
91
  ref={domRef}
93
92
  slot="close"
94
- aria-label={stringFormatter.format('dialog.dismiss')}
93
+ aria-label={props['aria-label'] || stringFormatter.format('dialog.dismiss')}
95
94
  style={pressScale(domRef, UNSAFE_style)}
96
95
  className={renderProps => UNSAFE_className + styles({...renderProps, staticColor: props.staticColor, isStaticColor: !!props.staticColor}, props.styles)}>
97
96
  <CrossIcon size={({S: 'L', M: 'XL', L: 'XXL', XL: 'XXXL'} as const)[props.size || 'M']} />
package/src/Field.tsx CHANGED
@@ -127,21 +127,23 @@ export const FieldLabel = forwardRef(function FieldLabel(props: FieldLabelProps,
127
127
  )}
128
128
  </Label>
129
129
  {contextualHelp && (
130
- <CenterBaseline
131
- styles={style({
132
- display: 'inline-flex',
133
- height: 0,
134
- marginStart: 4
135
- })}>
136
- <ContextualHelpContext.Provider
137
- value={{
138
- id: contextualHelpId,
139
- 'aria-labelledby': labelProps?.id ? `${labelProps.id} ${contextualHelpId}` : undefined,
140
- size: (size === 'L' || size === 'XL') ? 'S' : 'XS'
141
- }}>
142
- {contextualHelp}
143
- </ContextualHelpContext.Provider>
144
- </CenterBaseline>
130
+ <span className={style({whiteSpace: 'nowrap'})}>
131
+ &nbsp;
132
+ <CenterBaseline
133
+ styles={style({
134
+ display: 'inline-flex',
135
+ height: 0
136
+ })}>
137
+ <ContextualHelpContext.Provider
138
+ value={{
139
+ id: contextualHelpId,
140
+ 'aria-labelledby': labelProps?.id ? `${labelProps.id} ${contextualHelpId}` : undefined,
141
+ size: (size === 'L' || size === 'XL') ? 'S' : 'XS'
142
+ }}>
143
+ {contextualHelp}
144
+ </ContextualHelpContext.Provider>
145
+ </CenterBaseline>
146
+ </span>
145
147
  )}
146
148
  </div>
147
149
  );
@@ -60,7 +60,6 @@ const inlineAlert = style<InlineStylesProps & {isFocusVisible?: boolean}>({
60
60
  display: 'inline-block',
61
61
  position: 'relative',
62
62
  boxSizing: 'border-box',
63
- maxWidth: 320,
64
63
  padding: 24,
65
64
  borderRadius: 'lg',
66
65
  borderStyle: 'solid',
package/src/Menu.tsx CHANGED
@@ -487,8 +487,8 @@ export function MenuItem(props: MenuItemProps) {
487
487
  [KeyboardContext, {styles: keyboard({size, isDisabled: renderProps.isDisabled})}],
488
488
  [ImageContext, {styles: image({size})}]
489
489
  ]}>
490
- {renderProps.selectionMode === 'single' && !isLink && !renderProps.hasSubmenu && <CheckmarkIcon size={checkmarkIconSize[size]} className={checkmark({...renderProps, size})} />}
491
- {renderProps.selectionMode === 'multiple' && !isLink && !renderProps.hasSubmenu && (
490
+ {renderProps.selectionMode === 'single' && !renderProps.hasSubmenu && <CheckmarkIcon size={checkmarkIconSize[size]} className={checkmark({...renderProps, size})} />}
491
+ {renderProps.selectionMode === 'multiple' && !renderProps.hasSubmenu && (
492
492
  <div className={mergeStyles(checkbox, box(checkboxRenderProps))}>
493
493
  <CheckmarkIcon size={size} className={iconStyles} />
494
494
  </div>