@telus-uds/components-base 3.12.2 → 3.13.0

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 (33) hide show
  1. package/CHANGELOG.md +20 -2
  2. package/lib/cjs/Button/ButtonDropdown.js +105 -12
  3. package/lib/cjs/ExpandCollapse/ExpandCollapse.js +3 -1
  4. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMini.js +1 -1
  5. package/lib/cjs/ExpandCollapseMini/ExpandCollapseMiniControl.js +30 -6
  6. package/lib/cjs/FlexGrid/FlexGrid.js +54 -5
  7. package/lib/cjs/Icon/Icon.js +3 -1
  8. package/lib/cjs/InputLabel/InputLabel.js +1 -1
  9. package/lib/cjs/InputSupports/InputSupports.js +1 -1
  10. package/lib/cjs/Notification/Notification.js +27 -8
  11. package/lib/cjs/utils/props/inputSupportsProps.js +1 -1
  12. package/lib/esm/Button/ButtonDropdown.js +107 -14
  13. package/lib/esm/ExpandCollapse/ExpandCollapse.js +4 -2
  14. package/lib/esm/ExpandCollapseMini/ExpandCollapseMini.js +2 -2
  15. package/lib/esm/ExpandCollapseMini/ExpandCollapseMiniControl.js +30 -6
  16. package/lib/esm/FlexGrid/FlexGrid.js +55 -6
  17. package/lib/esm/Icon/Icon.js +3 -1
  18. package/lib/esm/InputLabel/InputLabel.js +1 -1
  19. package/lib/esm/InputSupports/InputSupports.js +1 -1
  20. package/lib/esm/Notification/Notification.js +27 -8
  21. package/lib/esm/utils/props/inputSupportsProps.js +1 -1
  22. package/lib/package.json +2 -2
  23. package/package.json +2 -2
  24. package/src/Button/ButtonDropdown.jsx +109 -16
  25. package/src/ExpandCollapse/ExpandCollapse.jsx +5 -2
  26. package/src/ExpandCollapseMini/ExpandCollapseMini.jsx +2 -2
  27. package/src/ExpandCollapseMini/ExpandCollapseMiniControl.jsx +39 -9
  28. package/src/FlexGrid/FlexGrid.jsx +62 -6
  29. package/src/Icon/Icon.jsx +3 -1
  30. package/src/InputLabel/InputLabel.jsx +1 -1
  31. package/src/InputSupports/InputSupports.jsx +1 -1
  32. package/src/Notification/Notification.jsx +58 -9
  33. package/src/utils/props/inputSupportsProps.js +1 -1
@@ -1,9 +1,9 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
- import { Platform, Text, View } from 'react-native'
3
+ import { Platform, StyleSheet, Text, View } from 'react-native'
4
4
  import buttonPropTypes, { textAndA11yText } from './propTypes'
5
5
  import ButtonBase from './ButtonBase'
6
- import { useThemeTokensCallback } from '../ThemeProvider'
6
+ import { applyTextStyles, useThemeTokensCallback } from '../ThemeProvider'
7
7
  import {
8
8
  a11yProps,
9
9
  getTokensPropType,
@@ -13,9 +13,10 @@ import {
13
13
  useInputValue
14
14
  } from '../utils'
15
15
  import Icon from '../Icon'
16
- import { getStackedContent } from '../StackView'
17
16
  import { getPressHandlersWithArgs } from '../utils/pressability'
18
17
 
18
+ const FULL_WIDTH_STYLE = 'full'
19
+
19
20
  const selectIconTokens = ({
20
21
  icon,
21
22
  iconPosition,
@@ -50,6 +51,44 @@ const selectIconTokens = ({
50
51
  }
51
52
  })
52
53
 
54
+ const selectDescriptionTextStyles = (tokens) => ({
55
+ ...applyTextStyles({
56
+ fontName: tokens?.descriptionFontName,
57
+ fontSize: tokens?.descriptionFontSize,
58
+ fontWeight: tokens?.descriptionFontWeight,
59
+ fontColor: tokens?.color
60
+ }),
61
+ paddingBottom: tokens?.descriptionTextPaddingBottom
62
+ })
63
+
64
+ const selectLeadIconTokens = (tokens) => ({
65
+ color: tokens?.leadIconColor,
66
+ backgroundColor: tokens?.leadIconBackgroundColor,
67
+ size: tokens?.leadIconSize,
68
+ borderRadius: tokens?.leadIconBorderRadius,
69
+ padding: tokens?.leadIconPadding
70
+ })
71
+
72
+ const selectLeadIconContainerStyles = (tokens) => ({
73
+ paddingTop: tokens?.leadIconContainerPaddingTop,
74
+ paddingBottom: tokens?.leadIconContainerPaddingBottom,
75
+ paddingLeft: tokens?.leadIconContainerPaddingLeft,
76
+ paddingRight: tokens?.leadIconContainerPaddingRight
77
+ })
78
+
79
+ const selectTextContainerStyles = (tokens) => ({
80
+ paddingLeft: tokens?.textPaddingLeft,
81
+ paddingRight: tokens?.textPaddingRight
82
+ })
83
+
84
+ const selectStackedContentStyles = (tokens, iconPosition, isFullWidth) => ({
85
+ ...staticStyles.stackedContent,
86
+ gap: tokens?.iconSpace,
87
+ flexDirection: iconPosition === 'left' ? 'row' : 'row-reverse',
88
+ ...(Platform.OS === 'web' && { flex: 1 }),
89
+ ...(isFullWidth && { justifyContent: 'space-between', flex: 1 })
90
+ })
91
+
53
92
  const ButtonDropdown = React.forwardRef(
54
93
  (
55
94
  {
@@ -63,10 +102,14 @@ const ButtonDropdown = React.forwardRef(
63
102
  readOnly = false,
64
103
  children = null,
65
104
  accessibilityRole = 'radio',
105
+ description,
106
+ singleOption,
66
107
  ...props
67
108
  },
68
109
  ref
69
110
  ) => {
111
+ const isFullWidth = variant?.width === FULL_WIDTH_STYLE
112
+
70
113
  const { currentValue: isOpen, setValue: setIsOpen } = useInputValue(
71
114
  {
72
115
  value,
@@ -85,7 +128,11 @@ const ButtonDropdown = React.forwardRef(
85
128
 
86
129
  const getTokens = useThemeTokensCallback('ButtonDropdown', tokens, extraState)
87
130
 
88
- const getButtonTokens = (buttonState) => selectTokens('Button', getTokens(buttonState))
131
+ const getButtonTokens = (buttonState) => ({
132
+ ...selectTokens('Button', getTokens(buttonState)),
133
+ iconSpace: props?.icon ? getTokens(buttonState)?.iconSpace : 0,
134
+ ...(isFullWidth && { width: 'full' })
135
+ })
89
136
 
90
137
  // Pass an object of relevant component state as first argument for any passed-in press handlers
91
138
  const pressHandlers = getPressHandlersWithArgs(props, [{ label, open: isOpen }])
@@ -103,7 +150,7 @@ const ButtonDropdown = React.forwardRef(
103
150
  {...pressHandlers}
104
151
  onPress={handlePress}
105
152
  tokens={getButtonTokens}
106
- inactive={inactive}
153
+ inactive={singleOption || inactive}
107
154
  icon={() => null}
108
155
  accessibilityRole={accessibilityRole}
109
156
  {...props}
@@ -116,31 +163,50 @@ const ButtonDropdown = React.forwardRef(
116
163
  // - Token sets: https://github.com/telus/universal-design-system/issues/782
117
164
 
118
165
  const itemTokens = getTokens(buttonState)
166
+ const leadIcon = itemTokens?.leadIcon
119
167
 
120
168
  const {
121
169
  iconTokens,
122
170
  iconPosition,
123
- iconSpace,
124
171
  iconWrapperStyle,
125
172
  icon: IconComponent
126
173
  } = selectIconTokens(itemTokens)
127
174
 
128
- const iconContent = IconComponent ? (
129
- <View style={iconWrapperStyle}>
130
- <Icon icon={IconComponent} tokens={iconTokens} />
131
- </View>
132
- ) : null
175
+ const iconContent =
176
+ IconComponent && !singleOption ? (
177
+ <View style={iconWrapperStyle}>
178
+ <Icon icon={IconComponent} tokens={iconTokens} />
179
+ </View>
180
+ ) : null
133
181
 
134
182
  const childrenContent = () =>
135
183
  typeof children === 'function'
136
184
  ? children({ ...resolvePressableState(buttonState, extraState), textStyles })
137
185
  : children
138
186
 
139
- const content = children ? childrenContent() : <Text style={textStyles}>{label}</Text>
187
+ const content = children ? (
188
+ childrenContent()
189
+ ) : (
190
+ <View style={staticStyles.contentContainer}>
191
+ {leadIcon && (
192
+ <View style={selectLeadIconContainerStyles(itemTokens)}>
193
+ <Icon icon={leadIcon} tokens={selectLeadIconTokens(itemTokens)} />
194
+ </View>
195
+ )}
196
+ <View style={[staticStyles.textContainer, selectTextContainerStyles(itemTokens)]}>
197
+ <Text style={textStyles}>{label}</Text>
198
+ {description && (
199
+ <Text style={selectDescriptionTextStyles(itemTokens)}>{description}</Text>
200
+ )}
201
+ </View>
202
+ </View>
203
+ )
140
204
 
141
- return getStackedContent(
142
- iconPosition === 'left' ? [iconContent, content] : [content, iconContent],
143
- { space: iconSpace, direction: 'row' }
205
+ return (
206
+ <View style={selectStackedContentStyles(itemTokens, iconPosition, isFullWidth)}>
207
+ {iconContent}
208
+ {content}
209
+ </View>
144
210
  )
145
211
  }}
146
212
  </ButtonBase>
@@ -176,7 +242,34 @@ ButtonDropdown.propTypes = {
176
242
  /**
177
243
  * By default, `ButtonDropdown` is treated by accessibility tools as a radio button.
178
244
  */
179
- accessibilityRole: PropTypes.string
245
+ accessibilityRole: PropTypes.string,
246
+ /**
247
+ * The description of ButtonDropdown.
248
+ */
249
+ description: PropTypes.string,
250
+ /**
251
+ * Use this prop to render the ButtonDropdown as display only without any interaction when there is only one option.
252
+ */
253
+ singleOption: PropTypes.bool
180
254
  }
181
255
 
256
+ const staticStyles = StyleSheet.create({
257
+ textContainer: {
258
+ alignItems: 'flex-start',
259
+ flexShrink: 1,
260
+ ...(Platform.OS === 'web' && { flex: 1 })
261
+ },
262
+ contentContainer: {
263
+ flexDirection: 'row',
264
+ alignContent: 'center',
265
+ alignItems: 'center',
266
+ ...(Platform.OS === 'web' && { flex: 1 }),
267
+ flexShrink: 1
268
+ },
269
+ stackedContent: {
270
+ flexDirection: 'row',
271
+ alignItems: 'center'
272
+ }
273
+ })
274
+
182
275
  export default ButtonDropdown
@@ -10,7 +10,8 @@ import {
10
10
  useMultipleInputValues,
11
11
  variantProp,
12
12
  viewProps,
13
- contentfulProps
13
+ contentfulProps,
14
+ useUniqueId
14
15
  } from '../utils'
15
16
 
16
17
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([
@@ -36,6 +37,8 @@ function selectBorderStyles(tokens) {
36
37
  */
37
38
  const ExpandCollapse = React.forwardRef(
38
39
  ({ children, tokens, variant, maxOpen, open, initialOpen, onChange, dataSet, ...rest }, ref) => {
40
+ const instanceId = useUniqueId('ExpandCollapse')
41
+
39
42
  const {
40
43
  currentValues: openIds,
41
44
  toggleOneValue: onToggle,
@@ -54,7 +57,7 @@ const ExpandCollapse = React.forwardRef(
54
57
  <View style={staticStyles.container} ref={ref} {...selectProps(rest)} dataSet={dataSet}>
55
58
  <View style={selectBorderStyles(themeTokens)}>
56
59
  {typeof children === 'function'
57
- ? children({ openIds, onToggle, resetValues, setValues, unsetValues })
60
+ ? children({ openIds, onToggle, resetValues, setValues, unsetValues, instanceId })
58
61
  : children}
59
62
  </View>
60
63
  </View>
@@ -1,7 +1,7 @@
1
1
  import React from 'react'
2
2
  import PropTypes from 'prop-types'
3
3
  import ExpandCollapse from '../ExpandCollapse'
4
- import { getTokensPropType, selectSystemProps, contentfulProps } from '../utils'
4
+ import { getTokensPropType, selectSystemProps, contentfulProps, useUniqueId } from '../utils'
5
5
  import { variantProp } from '../utils/props'
6
6
  import ExpandCollapseMiniControl from './ExpandCollapseMiniControl'
7
7
 
@@ -12,7 +12,7 @@ const ExpandCollapseMini = React.forwardRef(
12
12
  { children, onToggle = () => {}, tokens = {}, nativeID, initialOpen = false, dataSet, ...rest },
13
13
  ref
14
14
  ) => {
15
- const expandCollapeMiniPanelId = 'ExpandCollapseMiniPanel'
15
+ const expandCollapeMiniPanelId = useUniqueId('ExpandCollapseMiniPanel')
16
16
  const handleChange = (openPanels, event) => {
17
17
  if (typeof onToggle === 'function') {
18
18
  const isOpen = openPanels.length > 0
@@ -42,10 +42,15 @@ const ExpandCollapseMiniControl = React.forwardRef(
42
42
  pressed
43
43
  }
44
44
  )
45
- const { size, icon } = useThemeTokens('ExpandCollapseMiniControl', tokens, variant, {
46
- expanded,
47
- focus
48
- })
45
+ const { fontSize, lineHeight, iconSize, icon } = useThemeTokens(
46
+ 'ExpandCollapseMiniControl',
47
+ tokens,
48
+ variant,
49
+ {
50
+ expanded,
51
+ focus
52
+ }
53
+ )
49
54
 
50
55
  // Choose hover styles when any part of Control is hoverred
51
56
  const appearance = { ...variant, hover }
@@ -54,16 +59,39 @@ const ExpandCollapseMiniControl = React.forwardRef(
54
59
  const { hover: linkHover } = linkState || {}
55
60
  const isHovered = hover || linkHover
56
61
 
62
+ const iconBaselineOffset = 0
63
+ const hoverTranslateY = 4
64
+
65
+ // Calculate baseline alignment to vertically center icon with text
66
+ // This combines font and icon metrics with adjustments for visual balance
67
+ const fontBaseline = fontSize / hoverTranslateY // Quarter of font size - adjusts for text's visual center point
68
+ const iconBaseline = iconSize / hoverTranslateY // Quarter of icon size - adjusts for icon's visual center point
69
+ const staticOffset = hoverTranslateY // Fixed downward adjustment to fine-tune vertical alignment
70
+ const sizeCompensation = -Math.abs(iconSize - fontSize) // Compensates when icon and text sizes differ significantly
71
+
72
+ const baselineAlignment = fontBaseline + iconBaseline - staticOffset + sizeCompensation
73
+
57
74
  if (Platform.OS !== 'web') {
58
- return { iconTranslateY: -1 }
75
+ // For native platforms, use baseline alignment with optional offset
76
+ return { iconTranslateY: baselineAlignment + iconBaselineOffset }
59
77
  }
60
78
 
61
79
  if (isHovered) {
62
- // Include vertical icon animation on hover alongside built-in Link theme, the size is size4
63
- return { iconTranslateY: (expanded ? -1 : 1) * size }
80
+ // Apply animation offset to the baseline-aligned position
81
+ // When expanded: move icon UP (1.3 the hover distance for clear movement)
82
+ // When collapsed: move icon DOWN (single hover distance)
83
+ const hoverMovementDistance = 1.3
84
+ const animationOffset = expanded
85
+ ? -(hoverTranslateY * hoverMovementDistance)
86
+ : hoverTranslateY
87
+
88
+ return {
89
+ iconTranslateY: baselineAlignment + iconBaselineOffset + animationOffset
90
+ }
64
91
  }
65
92
 
66
- return {}
93
+ // Default state uses baseline alignment with optional offset
94
+ return { iconTranslateY: baselineAlignment + iconBaselineOffset }
67
95
  }
68
96
 
69
97
  return (
@@ -73,7 +101,9 @@ const ExpandCollapseMiniControl = React.forwardRef(
73
101
  iconPosition={iconPosition}
74
102
  tokens={(linkState) => ({
75
103
  ...linkTokens,
76
- ...getTokens(linkState)
104
+ ...getTokens(linkState),
105
+ iconSize,
106
+ blockLineHeight: lineHeight
77
107
  })}
78
108
  ref={ref}
79
109
  {...presentationOnly}
@@ -10,7 +10,8 @@ import {
10
10
  selectSystemProps,
11
11
  BaseView,
12
12
  StyleSheet,
13
- createMediaQueryStyles
13
+ createMediaQueryStyles,
14
+ useResponsiveProp
14
15
  } from '../utils'
15
16
  import Row from './Row'
16
17
  import Col from './Col'
@@ -21,6 +22,37 @@ import { useViewport } from '../ViewportProvider'
21
22
 
22
23
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
23
24
 
25
+ const CONTENT_MAX_WIDTH = 'max'
26
+ const CONTENT_FULL_WIDTH = 'full'
27
+
28
+ /**
29
+ * Resolves the maximum width for content based on the provided value and responsive width.
30
+ *
31
+ * @param {number|string|null|undefined} contentMinWidthValue - The minimum width value for the content.
32
+ * Can be a number (pixels), a string constant (e.g., CONTENT_FULL_WIDTH, CONTENT_MAX_WIDTH), or null/undefined.
33
+ * @param {number|string} responsiveWidth - The responsive width to use when contentMinWidthValue is CONTENT_MAX_WIDTH.
34
+ * @returns {number|string|null} The resolved maximum width value, which can be a number, a string (e.g., '100%'), or null.
35
+ */
36
+ const resolveContentMaxWidth = (contentMinWidthValue, responsiveWidth) => {
37
+ if (!contentMinWidthValue) {
38
+ return null
39
+ }
40
+
41
+ if (contentMinWidthValue === CONTENT_FULL_WIDTH) {
42
+ return '100%'
43
+ }
44
+
45
+ if (Number.isFinite(contentMinWidthValue)) {
46
+ return contentMinWidthValue
47
+ }
48
+
49
+ if (contentMinWidthValue === CONTENT_MAX_WIDTH) {
50
+ return responsiveWidth
51
+ }
52
+
53
+ return contentMinWidthValue
54
+ }
55
+
24
56
  /**
25
57
  * A mobile-first flexbox grid.
26
58
  */
@@ -40,6 +72,7 @@ const FlexGrid = React.forwardRef(
40
72
  accessibilityRole,
41
73
  children,
42
74
  dataSet,
75
+ contentMinWidth,
43
76
  ...rest
44
77
  },
45
78
  ref
@@ -47,30 +80,36 @@ const FlexGrid = React.forwardRef(
47
80
  const reverseLevel = applyInheritance([xsReverse, smReverse, mdReverse, lgReverse, xlReverse])
48
81
  const viewport = useViewport()
49
82
  const {
83
+ themeOptions,
50
84
  themeOptions: { enableMediaQueryStyleSheet }
51
85
  } = useTheme()
52
86
 
53
87
  let flexgridStyles
54
88
  let mediaIds
55
89
 
90
+ const contentMinWidthValue = useResponsiveProp(contentMinWidth)
91
+ const responsiveWidth = useResponsiveProp(themeOptions?.contentMaxWidth)
92
+ const maxWidth = resolveContentMaxWidth(contentMinWidthValue, responsiveWidth)
93
+
56
94
  const stylesByViewport = {
57
95
  xs: {
96
+ maxWidth: limitWidth ? viewports.map.get('sm') : maxWidth,
58
97
  flexDirection: reverseLevel[0] ? 'column-reverse' : 'column'
59
98
  },
60
99
  sm: {
61
- maxWidth: limitWidth && viewports.map.get('sm'),
100
+ maxWidth: limitWidth ? viewports.map.get('sm') : maxWidth,
62
101
  flexDirection: reverseLevel[1] ? 'column-reverse' : 'column'
63
102
  },
64
103
  md: {
65
- maxWidth: limitWidth && viewports.map.get('md'),
104
+ maxWidth: limitWidth ? viewports.map.get('md') : maxWidth,
66
105
  flexDirection: reverseLevel[2] ? 'column-reverse' : 'column'
67
106
  },
68
107
  lg: {
69
- maxWidth: limitWidth && viewports.map.get('lg'),
108
+ maxWidth: limitWidth ? viewports.map.get('lg') : maxWidth,
70
109
  flexDirection: reverseLevel[3] ? 'column-reverse' : 'column'
71
110
  },
72
111
  xl: {
73
- maxWidth: limitWidth && viewports.map.get('xl'),
112
+ maxWidth: limitWidth ? viewports.map.get('xl') : maxWidth,
74
113
  flexDirection: reverseLevel[4] ? 'column-reverse' : 'column'
75
114
  }
76
115
  }
@@ -162,7 +201,24 @@ FlexGrid.propTypes = {
162
201
  /**
163
202
  * The rows and columns of the Grid. Will typically be `FlexGrid.Row` and `FlexGrid.Col` components.
164
203
  */
165
- children: PropTypes.node.isRequired
204
+ children: PropTypes.node.isRequired,
205
+ /**
206
+ * The minimum width of the content in the FlexGrid.
207
+ * This prop accepts responsive values for different viewports. If a number is provided,
208
+ * it will be the max content width for the desired viewport.
209
+ * - `xs`: 'max' | 'full' | <number>
210
+ * - `sm`: 'max' | 'full' | <number>
211
+ * - `md`: 'max' | 'full' | <number>
212
+ * - `lg`: 'max' | 'full' | <number>
213
+ * - `xl`: 'max' | 'full' | <number>
214
+ */
215
+ contentMinWidth: PropTypes.shape({
216
+ xl: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
217
+ lg: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
218
+ md: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
219
+ sm: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number]),
220
+ xs: PropTypes.oneOfType([PropTypes.oneOf(['max', 'full']), PropTypes.number])
221
+ })
166
222
  }
167
223
 
168
224
  FlexGrid.Row = Row
package/src/Icon/Icon.jsx CHANGED
@@ -38,7 +38,9 @@ const Icon = React.forwardRef(
38
38
  width: themeTokens.size + themeTokens.width * 2, // sets the diameter of the circle which is the size of the icon plus twice the general padding established to obtain a perfect circle
39
39
  height: themeTokens.size + themeTokens.width * 2
40
40
  }
41
- : {}
41
+ : {
42
+ padding: themeTokens.padding
43
+ }
42
44
 
43
45
  const getIconContentForMobile = () => {
44
46
  if (Object.keys(paddingStyles).length) {
@@ -161,7 +161,7 @@ InputLabel.propTypes = {
161
161
  /**
162
162
  * Content of an optional `Tooltip`. If set, a tooltip button will be shown next to the label.
163
163
  */
164
- tooltip: PropTypes.oneOfType([tooltipPropTypes, PropTypes.string]),
164
+ tooltip: PropTypes.oneOfType([PropTypes.shape(tooltipPropTypes), PropTypes.string]),
165
165
  /**
166
166
  * Current number of characterts of an input text.
167
167
  */
@@ -121,7 +121,7 @@ InputSupports.propTypes = {
121
121
  * 1. `tooltip` as a string - The content of the tooltip.
122
122
  * 2. `tooltip` as an object - Tooltip component props to be passed.
123
123
  */
124
- tooltip: PropTypes.oneOfType([tooltipPropTypes, PropTypes.string]),
124
+ tooltip: PropTypes.oneOfType([PropTypes.shape(tooltipPropTypes), PropTypes.string]),
125
125
  /**
126
126
  * Use to visually mark an input as valid or invalid.
127
127
  */
@@ -26,6 +26,8 @@ import useCopy from '../utils/useCopy'
26
26
  import dictionary from './dictionary'
27
27
  import { useViewport } from '../ViewportProvider'
28
28
 
29
+ const CONTENT_MAX_WIDTH = 'max'
30
+
29
31
  const [selectProps, selectedSystemPropTypes] = selectSystemProps([a11yProps, viewProps])
30
32
 
31
33
  const selectContainerStyles = (tokens) => ({ ...tokens })
@@ -65,8 +67,14 @@ const selectDismissButtonContainerStyles = ({ dismissButtonGap }) => ({
65
67
  placeContent: 'start'
66
68
  })
67
69
 
68
- const selectContentContainerStyle = (themeTokens, maxWidth, system, viewport) => ({
69
- maxWidth: system && viewport === 'xl' ? maxWidth : '100%',
70
+ const selectContentContainerStyle = (
71
+ themeTokens,
72
+ maxWidth,
73
+ system,
74
+ viewport,
75
+ useContentMaxWidth
76
+ ) => ({
77
+ maxWidth: system && (useContentMaxWidth || viewport === 'xl') ? maxWidth : '100%',
70
78
  width: '100%',
71
79
  paddingRight: themeTokens?.containerPaddingRight,
72
80
  paddingLeft: themeTokens?.containerPaddingLeft
@@ -79,7 +87,8 @@ const getMediaQueryStyles = (
79
87
  mediaIdsRef,
80
88
  dismissible,
81
89
  viewport,
82
- system
90
+ system,
91
+ useContentMaxWidth
83
92
  ) => {
84
93
  const transformedSelectContainerStyles = Object.entries(themeTokens).reduce(
85
94
  (acc, [vp, viewportTokens]) => {
@@ -100,7 +109,7 @@ const getMediaQueryStyles = (
100
109
  const transformedSelectContentContainerStyles = Object.entries(themeTokens).reduce(
101
110
  (acc, [vp, viewportTokens]) => {
102
111
  acc[vp] = {
103
- ...selectContentContainerStyle(viewportTokens, maxWidth, system, vp),
112
+ ...selectContentContainerStyle(viewportTokens, maxWidth, system, vp, useContentMaxWidth),
104
113
  flexDirection: 'row',
105
114
  flexShrink: 1,
106
115
  justifyContent: 'space-between',
@@ -169,7 +178,15 @@ const getMediaQueryStyles = (
169
178
  }
170
179
  }
171
180
 
172
- const getDefaultStyles = (themeTokens, themeOptions, maxWidth, dismissible, viewport, system) => ({
181
+ const getDefaultStyles = (
182
+ themeTokens,
183
+ themeOptions,
184
+ maxWidth,
185
+ dismissible,
186
+ viewport,
187
+ system,
188
+ useContentMaxWidth
189
+ ) => ({
173
190
  containerStyles: {
174
191
  container: {
175
192
  flexDirection: 'column',
@@ -181,7 +198,7 @@ const getDefaultStyles = (themeTokens, themeOptions, maxWidth, dismissible, view
181
198
  flexDirection: 'row',
182
199
  flexShrink: 1,
183
200
  justifyContent: 'space-between',
184
- ...selectContentContainerStyle(themeTokens, maxWidth, system, viewport),
201
+ ...selectContentContainerStyle(themeTokens, maxWidth, system, viewport, useContentMaxWidth),
185
202
  ...(system && { alignSelf: 'center' })
186
203
  }
187
204
  },
@@ -251,7 +268,20 @@ const getDefaultStyles = (themeTokens, themeOptions, maxWidth, dismissible, view
251
268
  * Show system notifications at the top of the page, below the navigation, and expands the full-width of the viewport
252
269
  */
253
270
  const Notification = React.forwardRef(
254
- ({ children, system, dismissible, copy = 'en', tokens, variant, onDismiss, ...rest }, ref) => {
271
+ (
272
+ {
273
+ children,
274
+ system,
275
+ dismissible,
276
+ copy = 'en',
277
+ tokens,
278
+ variant,
279
+ onDismiss,
280
+ contentMinWidth,
281
+ ...rest
282
+ },
283
+ ref
284
+ ) => {
255
285
  const [isDismissed, setIsDismissed] = React.useState(false)
256
286
  const viewport = useViewport()
257
287
  const getCopy = useCopy({ dictionary, copy })
@@ -266,6 +296,7 @@ const Notification = React.forwardRef(
266
296
  system: isSystemEnabled,
267
297
  viewport
268
298
  })
299
+ const useContentMaxWidth = useResponsiveProp(contentMinWidth) === CONTENT_MAX_WIDTH
269
300
  const maxWidth = useResponsiveProp(
270
301
  themeOptions?.contentMaxWidth,
271
302
  viewports.map.get(viewports.xl)
@@ -300,7 +331,8 @@ const Notification = React.forwardRef(
300
331
  mediaIdsRef,
301
332
  dismissible,
302
333
  viewport,
303
- isSystemEnabled
334
+ isSystemEnabled,
335
+ useContentMaxWidth
304
336
  )
305
337
  } else {
306
338
  notificationComponentRef.current = getDefaultStyles(
@@ -309,7 +341,8 @@ const Notification = React.forwardRef(
309
341
  maxWidth,
310
342
  dismissible,
311
343
  viewport,
312
- isSystemEnabled
344
+ isSystemEnabled,
345
+ useContentMaxWidth
313
346
  )
314
347
  }
315
348
 
@@ -434,6 +467,22 @@ Notification.propTypes = {
434
467
  * Callback function called when the dismiss button is clicked
435
468
  */
436
469
  onDismiss: PropTypes.func,
470
+ /**
471
+ * The minimum width of the content in the Notification when using the system variant.
472
+ * This prop accepts responsive values for different viewports.
473
+ * - `xs`: 'max' | 'full'
474
+ * - `sm`: 'max' | 'full'
475
+ * - `md`: 'max' | 'full'
476
+ * - `lg`: 'max' | 'full'
477
+ * - `xl`: 'max' | 'full'
478
+ */
479
+ contentMinWidth: PropTypes.shape({
480
+ xl: PropTypes.oneOf(['max', 'full']),
481
+ lg: PropTypes.oneOf(['max', 'full']),
482
+ md: PropTypes.oneOf(['max', 'full']),
483
+ sm: PropTypes.oneOf(['max', 'full']),
484
+ xs: PropTypes.oneOf(['max', 'full'])
485
+ }),
437
486
  tokens: getTokensPropType('Notification'),
438
487
  variant: variantProp.propType
439
488
  }
@@ -38,7 +38,7 @@ export default {
38
38
  * 1. `tooltip` as a string - The content of the tooltip.
39
39
  * 2. `tooltip` as an object - Tooltip component props to be passed.
40
40
  */
41
- tooltip: PropTypes.oneOfType([tooltipPropTypes, PropTypes.string]),
41
+ tooltip: PropTypes.oneOfType([PropTypes.shape(tooltipPropTypes), PropTypes.string]),
42
42
  /**
43
43
  * Use to visually mark an input as valid or invalid.
44
44
  */