@react-spectrum/s2 3.0.0-nightly-4b2c6e76f-250325 → 3.0.0-nightly-d7511d723-250326

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.
@@ -0,0 +1,160 @@
1
+ /*
2
+ * Copyright 2025 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 {AriaLabelingProps, DOMProps, DOMRef, DOMRefValue} from '@react-types/shared';
14
+ import {ContextValue, SlotProps} from 'react-aria-components';
15
+ import {filterDOMProps} from '@react-aria/utils';
16
+ import {fontRelative, style} from '../style' with {type: 'macro'};
17
+ import {getAllowedOverrides, StyleProps} from './style-utils' with {type: 'macro'};
18
+ // @ts-ignore
19
+ import intlMessages from '../intl/*.json';
20
+ import {NumberFormatter} from '@internationalized/number';
21
+ import React, {createContext, forwardRef} from 'react';
22
+ import {useDOMRef} from '@react-spectrum/utils';
23
+ import {useLocale, useLocalizedStringFormatter} from '@react-aria/i18n';
24
+ import {useSpectrumContextProps} from './useSpectrumContextProps';
25
+
26
+ export interface NotificationBadgeStyleProps {
27
+ /**
28
+ * The size of the notification badge.
29
+ *
30
+ * @default 'S'
31
+ */
32
+ size?: 'S' | 'M' | 'L' | 'XL'
33
+ }
34
+
35
+ export interface NotificationBadgeProps extends DOMProps, AriaLabelingProps, StyleProps, NotificationBadgeStyleProps, SlotProps {
36
+ /**
37
+ * The value to be displayed in the notification badge.
38
+ */
39
+ value?: number | null
40
+ }
41
+
42
+ export const NotificationBadgeContext = createContext<ContextValue<Partial<NotificationBadgeProps>, DOMRefValue<HTMLDivElement>>>(null);
43
+
44
+ const badge = style({
45
+ display: 'flex',
46
+ font: 'control',
47
+ color: {
48
+ default: 'white',
49
+ forcedColors: 'ButtonText'
50
+ },
51
+ fontSize: {
52
+ size: {
53
+ S: 'ui-xs',
54
+ M: 'ui-xs',
55
+ L: 'ui-sm',
56
+ XL: 'ui'
57
+ }
58
+ },
59
+ borderStyle: {
60
+ forcedColors: 'solid'
61
+ },
62
+ borderWidth: {
63
+ forcedColors: '[1px]'
64
+ },
65
+ borderColor: {
66
+ forcedColors: 'ButtonBorder'
67
+ },
68
+ justifyContent: 'center',
69
+ alignItems: 'center',
70
+ backgroundColor: {
71
+ default: 'accent',
72
+ forcedColors: 'ButtonFace'
73
+ },
74
+ height: {
75
+ size: {
76
+ S: {
77
+ default: 12,
78
+ isIndicatorOnly: 8
79
+ },
80
+ M: {
81
+ default: fontRelative(18), // sort of arbitrary? tried to get as close to the figma designs as possible
82
+ isIndicatorOnly: 8
83
+ },
84
+ L: {
85
+ default: 16,
86
+ isIndicatorOnly: fontRelative(12)
87
+ },
88
+ XL: {
89
+ default: 18,
90
+ isIndicatorOnly: fontRelative(12)
91
+ }
92
+ }
93
+ },
94
+ aspectRatio: {
95
+ isIndicatorOnly: 'square',
96
+ isSingleDigit: 'square'
97
+ },
98
+ width: 'fit',
99
+ paddingX: {
100
+ isDoubleDigit: 'edge-to-text'
101
+ },
102
+ borderRadius: 'pill'
103
+ }, getAllowedOverrides());
104
+
105
+ /**
106
+ * Notification badges are used to indicate new or pending activity .
107
+ */
108
+ export const NotificationBadge = forwardRef(function Badge(props: NotificationBadgeProps, ref: DOMRef<HTMLDivElement>) {
109
+ let stringFormatter = useLocalizedStringFormatter(intlMessages, '@react-spectrum/s2');
110
+ [props, ref] = useSpectrumContextProps(props, ref, NotificationBadgeContext);
111
+ let {
112
+ size = 'S',
113
+ value,
114
+ ...otherProps
115
+ } = props;
116
+ let domRef = useDOMRef(ref);
117
+ let {locale} = useLocale();
118
+ let formattedValue = '';
119
+
120
+ let isIndicatorOnly = false;
121
+ let isSingleDigit = false;
122
+ let isDoubleDigit = false;
123
+
124
+ if (value == null) {
125
+ isIndicatorOnly = true;
126
+ } else if (value <= 0) {
127
+ throw new Error('Value cannot be negative or zero');
128
+ } else if (!Number.isInteger(value)) {
129
+ throw new Error('Value must be a positive integer');
130
+ } else {
131
+ formattedValue = new NumberFormatter(locale).format(Math.min(value, 99));
132
+ let length = Math.log(value <= 99 ? value : 99) * Math.LOG10E + 1 | 0; // for positive integers (https://stackoverflow.com/questions/14879691/get-number-of-digits-with-javascript)
133
+ if (length === 1) {
134
+ isSingleDigit = true;
135
+ } else if (length === 2) {
136
+ isDoubleDigit = true;
137
+ }
138
+
139
+ if (value > 99) {
140
+ formattedValue = stringFormatter.format('notificationbadge.plus', {notifications: formattedValue});
141
+ }
142
+ }
143
+
144
+ let ariaLabel = props['aria-label'] || undefined;
145
+ if (ariaLabel === undefined && isIndicatorOnly) {
146
+ ariaLabel = stringFormatter.format('notificationbadge.indicatorOnly');
147
+ }
148
+
149
+ return (
150
+ <span
151
+ {...filterDOMProps(otherProps, {labelable: true})}
152
+ role={ariaLabel && 'img'}
153
+ aria-label={ariaLabel}
154
+ className={(props.UNSAFE_className || '') + badge({size, isIndicatorOnly, isSingleDigit, isDoubleDigit}, props.styles)}
155
+ style={props.UNSAFE_style}
156
+ ref={domRef}>
157
+ {formattedValue}
158
+ </span>
159
+ );
160
+ });
package/src/index.ts CHANGED
@@ -53,6 +53,7 @@ export {InlineAlert, InlineAlertContext} from './InlineAlert';
53
53
  export {Link, LinkContext} from './Link';
54
54
  export {MenuItem, MenuTrigger, Menu, MenuSection, SubmenuTrigger, MenuContext} from './Menu';
55
55
  export {Meter, MeterContext} from './Meter';
56
+ export {NotificationBadge, NotificationBadgeContext} from './NotificationBadge';
56
57
  export {NumberField, NumberFieldContext} from './NumberField';
57
58
  export {Picker, PickerItem, PickerSection, PickerContext} from './Picker';
58
59
  export {Popover} from './Popover';
@@ -123,6 +124,7 @@ export type {ImageCoordinatorProps} from './ImageCoordinator';
123
124
  export type {LinkProps} from './Link';
124
125
  export type {MenuTriggerProps, MenuProps, MenuItemProps, MenuSectionProps, SubmenuTriggerProps} from './Menu';
125
126
  export type {MeterProps} from './Meter';
127
+ export type {NotificationBadgeProps} from './NotificationBadge';
126
128
  export type {PickerProps, PickerItemProps, PickerSectionProps} from './Picker';
127
129
  export type {PopoverProps} from './Popover';
128
130
  export type {ProgressBarProps} from './ProgressBar';