@kaizen/components 1.78.0 → 1.78.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.
Files changed (38) hide show
  1. package/dist/cjs/src/Filter/FilterBar/subcomponents/FilterBarSelect/FilterBarSelect.cjs +1 -1
  2. package/dist/cjs/src/LikertScaleLegacy/LikertScaleLegacy.cjs +5 -3
  3. package/dist/cjs/src/Menu/subcomponents/StatelessMenu/StatelessMenu.cjs +0 -1
  4. package/dist/cjs/src/__next__/Select/Select.cjs +23 -15
  5. package/dist/esm/src/Filter/FilterBar/subcomponents/FilterBarSelect/FilterBarSelect.mjs +1 -1
  6. package/dist/esm/src/LikertScaleLegacy/LikertScaleLegacy.mjs +5 -3
  7. package/dist/esm/src/Menu/subcomponents/StatelessMenu/StatelessMenu.mjs +0 -1
  8. package/dist/esm/src/__next__/Select/Select.mjs +23 -15
  9. package/dist/styles.css +8950 -8951
  10. package/dist/types/LikertScaleLegacy/LikertScaleLegacy.d.ts +5 -1
  11. package/dist/types/Menu/subcomponents/StatelessMenu/StatelessMenu.d.ts +0 -1
  12. package/dist/types/__next__/Select/Select.d.ts +1 -1
  13. package/package.json +27 -26
  14. package/src/Avatar/Avatar.module.css +1 -1
  15. package/src/Badge/Badge.module.css +3 -3
  16. package/src/Filter/FilterBar/subcomponents/FilterBarSelect/FilterBarSelect.tsx +1 -1
  17. package/src/LikertScaleLegacy/LikertScaleLegacy.spec.tsx +1 -0
  18. package/src/LikertScaleLegacy/LikertScaleLegacy.tsx +7 -1
  19. package/src/LikertScaleLegacy/_docs/LikertScaleLegacy.mdx +8 -0
  20. package/src/LikertScaleLegacy/_docs/LikertScaleLegacy.stories.tsx +30 -1
  21. package/src/Link/Link.module.css +2 -2
  22. package/src/Menu/subcomponents/StatelessMenu/StatelessMenu.tsx +0 -2
  23. package/src/Pagination/subcomponents/TruncateIndicator/TruncateIndicator.module.css +1 -1
  24. package/src/Popover/Popover.module.scss +1 -1
  25. package/src/Radio/RadioField/RadioField.module.scss +2 -3
  26. package/src/Table/Table.module.scss +2 -2
  27. package/src/TextArea/TextArea.module.css +7 -7
  28. package/src/Workflow/subcomponents/Footer/components/Root/FooterRoot.module.css +1 -1
  29. package/src/__next__/Button/Button.module.css +11 -11
  30. package/src/__next__/Select/Select.tsx +5 -0
  31. package/src/__next__/Select/_docs/Select.mdx +8 -0
  32. package/src/__next__/Select/_docs/Select.stories.tsx +93 -0
  33. package/src/__next__/Select/subcomponents/SelectToggle/SelectToggle.module.scss +1 -1
  34. package/src/__next__/Tooltip/_docs/ApiSpecification.mdx +2 -2
  35. package/src/__next__/Tooltip/_docs/Tooltip.docs.stories.tsx +15 -30
  36. package/src/__next__/Tooltip/_docs/Tooltip.mdx +1 -1
  37. package/src/__next__/Tooltip/_docs/Tooltip.spec.stories.tsx +21 -58
  38. package/src/__next__/Tooltip/_docs/Tooltip.stories.tsx +2 -2
@@ -12,10 +12,14 @@ export type LikertScaleProps = {
12
12
  'colorSchema'?: ColorSchema | 'classical';
13
13
  'validationMessage'?: string;
14
14
  'status'?: 'default' | 'error';
15
+ /**
16
+ * Sets aria-required value on radiogroup for assistive technologies. Validation must still be handled.
17
+ */
18
+ 'isRequired'?: boolean;
15
19
  'onSelect': (value: ScaleItem | null) => void;
16
20
  };
17
21
  /**
18
22
  * {@link https://cultureamp.atlassian.net/wiki/spaces/DesignSystem/pages/3082060201/Likert+Scale Guidance} |
19
23
  * {@link https://cultureamp.design/?path=/docs/components-likertscalelegacy--docs Storybook}
20
24
  */
21
- export declare const LikertScaleLegacy: ({ scale, selectedItem, reversed, colorSchema, "data-testid": dataTestId, onSelect, validationMessage, status, labelId, }: LikertScaleProps) => JSX.Element;
25
+ export declare const LikertScaleLegacy: ({ scale, selectedItem, reversed, colorSchema, "data-testid": dataTestId, onSelect, validationMessage, status, labelId, isRequired, }: LikertScaleProps) => JSX.Element;
@@ -41,7 +41,6 @@ export type StatelessMenuProps = {
41
41
  'renderButton': (args: {
42
42
  'onClick': (e: any) => void;
43
43
  'onMouseDown': (e: any) => void;
44
- 'aria-haspopup': boolean;
45
44
  'aria-expanded': boolean;
46
45
  }) => React.ReactElement;
47
46
  'onClick'?: (event: SyntheticEvent) => void;
@@ -51,7 +51,7 @@ export type SelectProps<Option extends SelectOption = SelectOption> = {
51
51
  * {@link https://cultureamp.design/?path=/docs/components-select--docs Storybook}
52
52
  */
53
53
  export declare const Select: {
54
- <Option extends SelectOption = SelectOption>({ label, items, id: propsId, trigger, children, status, validationMessage, isReversed, isFullWidth, disabledValues, classNameOverride, selectedKey, description, placeholder, isDisabled, portalContainerId, ...restProps }: SelectProps<Option>): JSX.Element;
54
+ <Option extends SelectOption = SelectOption>({ label, items, id: propsId, trigger, children, status, validationMessage, isReversed, isRequired, isFullWidth, disabledValues, classNameOverride, selectedKey, description, placeholder, isDisabled, portalContainerId, onSelectionChange, ...restProps }: SelectProps<Option>): JSX.Element;
55
55
  displayName: string;
56
56
  Section: {
57
57
  <Option extends SelectOption = SelectOption>({ section, }: import("./subcomponents").ListBoxSectionProps<Option>): JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaizen/components",
3
- "version": "1.78.0",
3
+ "version": "1.78.2",
4
4
  "description": "Kaizen component library",
5
5
  "author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
6
6
  "homepage": "https://cultureamp.design",
@@ -103,25 +103,25 @@
103
103
  },
104
104
  "dependencies": {
105
105
  "@floating-ui/react-dom": "^2.1.2",
106
- "@headlessui/react": "^2.2.2",
107
- "@internationalized/date": "^3.8.0",
106
+ "@headlessui/react": "^2.2.4",
107
+ "@internationalized/date": "^3.8.1",
108
108
  "@popperjs/core": "^2.11.8",
109
109
  "@reach/tabs": "^0.18.0",
110
- "@react-aria/button": "^3.13.0",
111
- "@react-aria/datepicker": "^3.14.2",
112
- "@react-aria/focus": "^3.20.2",
113
- "@react-aria/i18n": "^3.12.8",
114
- "@react-aria/listbox": "^3.14.3",
115
- "@react-aria/menu": "^3.18.2",
116
- "@react-aria/overlays": "^3.27.0",
117
- "@react-aria/select": "^3.15.4",
118
- "@react-aria/utils": "^3.28.2",
119
- "@react-stately/collections": "^3.12.3",
120
- "@react-stately/datepicker": "^3.14.0",
121
- "@react-stately/list": "^3.12.1",
122
- "@react-stately/menu": "^3.9.3",
123
- "@react-stately/select": "^3.6.12",
124
- "@react-types/shared": "^3.29.0",
110
+ "@react-aria/button": "^3.13.1",
111
+ "@react-aria/datepicker": "^3.14.3",
112
+ "@react-aria/focus": "^3.20.3",
113
+ "@react-aria/i18n": "^3.12.9",
114
+ "@react-aria/listbox": "^3.14.4",
115
+ "@react-aria/menu": "^3.18.3",
116
+ "@react-aria/overlays": "^3.27.1",
117
+ "@react-aria/select": "^3.15.5",
118
+ "@react-aria/utils": "^3.29.0",
119
+ "@react-stately/collections": "^3.12.4",
120
+ "@react-stately/datepicker": "^3.14.1",
121
+ "@react-stately/list": "^3.12.2",
122
+ "@react-stately/menu": "^3.9.4",
123
+ "@react-stately/select": "^3.6.13",
124
+ "@react-types/shared": "^3.29.1",
125
125
  "classnames": "^2.5.1",
126
126
  "date-fns": "^4.1.0",
127
127
  "lodash.debounce": "^4.0.8",
@@ -136,10 +136,10 @@
136
136
  "prosemirror-state": "^1.4.3",
137
137
  "prosemirror-transform": "^1.10.4",
138
138
  "prosemirror-utils": "^1.2.2",
139
- "prosemirror-view": "^1.39.2",
139
+ "prosemirror-view": "^1.39.3",
140
140
  "react-animate-height": "^3.2.3",
141
- "react-aria": "^3.39.0",
142
- "react-aria-components": "^1.8.0",
141
+ "react-aria": "^3.40.0",
142
+ "react-aria-components": "^1.9.0",
143
143
  "react-day-picker": "9.6.7",
144
144
  "react-focus-lock": "^2.13.6",
145
145
  "react-focus-on": "^3.9.4",
@@ -154,9 +154,9 @@
154
154
  "react-day-picker": "Version locked until a11y gets fixed (https://github.com/gpbl/react-day-picker/pull/1708)"
155
155
  },
156
156
  "devDependencies": {
157
- "@cultureamp/frontend-apis": "^13.3.0",
158
- "@cultureamp/i18n-react-intl": "^2.8.0",
159
- "@cultureamp/package-bundler": "^2.3.1",
157
+ "@cultureamp/frontend-apis": "13.3.0",
158
+ "@cultureamp/i18n-react-intl": "^2.9.1",
159
+ "@cultureamp/package-bundler": "^2.3.2",
160
160
  "@testing-library/dom": "^10.4.0",
161
161
  "@types/jest-axe": "^3.5.9",
162
162
  "@types/lodash.debounce": "^4.0.9",
@@ -174,7 +174,7 @@
174
174
  "postcss-import": "^16.1.0",
175
175
  "postcss-preset-env": "^10.1.6",
176
176
  "postcss-scss": "^4.0.9",
177
- "query-string": "^9.1.2",
177
+ "query-string": "^9.2.0",
178
178
  "react": "^19.0.0",
179
179
  "react-dom": "^19.0.0",
180
180
  "react-highlight": "^0.15.0",
@@ -190,7 +190,8 @@
190
190
  },
191
191
  "devDependenciesComments": {
192
192
  "sass": "Prevent deprecation warnings introduced in 1.80 as we plan to move away from sass",
193
- "typescript": "Installed in root"
193
+ "typescript": "Installed in root",
194
+ "frontend-apis": "There's an unresolved dep with 13.4.0 and onwards with node/msw, so we are locking to 13.3.0 for this update."
194
195
  },
195
196
  "peerDependencies": {
196
197
  "@cultureamp/i18n-react-intl": "^2.5.9",
@@ -74,7 +74,7 @@
74
74
  }
75
75
 
76
76
  .fallbackIcon {
77
- color: rgba(var(--color-purple-800-rgb), 0.7);
77
+ color: rgb(var(--color-purple-800-rgb), 0.7);
78
78
  font-size: calc(var(--avatar-x-y) * 0.7);
79
79
  }
80
80
 
@@ -15,7 +15,7 @@
15
15
  }
16
16
 
17
17
  .reversed {
18
- --badge-background-color: rgba(var(--color-white-rgb), 0.1);
18
+ --badge-background-color: rgb(var(--color-white-rgb), 0.1);
19
19
 
20
20
  color: var(--color-white);
21
21
  }
@@ -50,7 +50,7 @@
50
50
  }
51
51
 
52
52
  .dark {
53
- --badge-background-color: rgba(var(--color-purple-700-rgb), 0.1);
53
+ --badge-background-color: rgb(var(--color-purple-700-rgb), 0.1);
54
54
 
55
55
  color: var(--color-purple-800);
56
56
  }
@@ -87,7 +87,7 @@
87
87
  }
88
88
 
89
89
  .animationOn .badge.dark {
90
- --badge-background-color: rgba(var(--color-purple-700-rgb), 0.2);
90
+ --badge-background-color: rgb(var(--color-purple-700-rgb), 0.2);
91
91
  }
92
92
 
93
93
  .animationOn .badge.active {
@@ -60,7 +60,7 @@ export const FilterBarSelect = <Option extends SelectOption = SelectOption>({
60
60
  <FilterBarButton {...triggerProps} filterId={id} isRemovable={filterState.isRemovable} />
61
61
  )}
62
62
  onSelectionChange={(key): void => {
63
- updateValue(id, key)
63
+ updateValue(id, key ?? undefined)
64
64
  onSelectionChange?.(key)
65
65
  }}
66
66
  isOpen={filterState.isOpen}
@@ -36,6 +36,7 @@ const LikertScaleLegacyWrapper = (props: Partial<LikertScaleProps>): JSX.Element
36
36
  labelId="test__likert-scale"
37
37
  selectedItem={null}
38
38
  onSelect={(): void => undefined}
39
+ isRequired
39
40
  {...props}
40
41
  />
41
42
  )
@@ -25,6 +25,10 @@ export type LikertScaleProps = {
25
25
  'colorSchema'?: ColorSchema | 'classical'
26
26
  'validationMessage'?: string
27
27
  'status'?: 'default' | 'error'
28
+ /**
29
+ * Sets aria-required value on radiogroup for assistive technologies. Validation must still be handled.
30
+ */
31
+ 'isRequired'?: boolean
28
32
  'onSelect': (value: ScaleItem | null) => void
29
33
  }
30
34
 
@@ -46,6 +50,7 @@ export const LikertScaleLegacy = ({
46
50
  validationMessage,
47
51
  status,
48
52
  labelId,
53
+ isRequired,
49
54
  }: LikertScaleProps): JSX.Element => {
50
55
  const [hoveredItem, setHoveredItem] = useState<ScaleItem | null>(null)
51
56
  const itemRefs: ItemRefs = scale.map((s) => ({
@@ -104,11 +109,12 @@ export const LikertScaleLegacy = ({
104
109
  reversed && [styles.reversed],
105
110
  hoveredItem !== null && styles.hovered,
106
111
  )}
107
- aria-labelledby={labelId}
112
+ aria-labelledby={isRequired ? `${labelId}` : labelId}
108
113
  role="radiogroup"
109
114
  tabIndex={-1}
110
115
  aria-describedby={validationMessageId}
111
116
  data-testid={dataTestId}
117
+ aria-required={isRequired}
112
118
  >
113
119
  <div className={styles.legend} data-testid={dataTestId && `${dataTestId}-legend`}>
114
120
  <Text variant="small" color={reversed ? 'white' : 'dark'}>
@@ -21,3 +21,11 @@ Likert scale radio buttons let people select one option in a Likert scale rangin
21
21
 
22
22
  <Canvas of={LikertScaleLegacyStories.Playground} />
23
23
  <Controls of={LikertScaleLegacyStories.Playground} />
24
+
25
+ ## API
26
+
27
+ ### isRequired
28
+
29
+ Sets aria-required value on radiogroup for assistive technologies. An accessible label must be provided and validation must still be handled within implementations.
30
+
31
+ <Canvas of={LikertScaleLegacyStories.IsRequired} />
@@ -1,5 +1,7 @@
1
1
  import React, { useState } from 'react'
2
2
  import { type Meta, type StoryObj } from '@storybook/react'
3
+ import { expect, within } from '@storybook/test'
4
+ import { VisuallyHidden } from '~components/VisuallyHidden'
3
5
  import { LikertScaleLegacy } from '../index'
4
6
  import { type Scale, type ScaleItem } from '../types'
5
7
 
@@ -57,7 +59,7 @@ export const Playground: Story = {
57
59
  code: `
58
60
  const SatisfactionExample = () => {
59
61
  const [selectedItem, setSelectedItem] = useState<ScaleItem | null>(null)
60
-
62
+
61
63
  return (
62
64
  <LikertScaleLegacy
63
65
  scale={[
@@ -82,3 +84,30 @@ export const Playground: Story = {
82
84
  },
83
85
  },
84
86
  }
87
+
88
+ export const IsRequired: Story = {
89
+ render: (args) => {
90
+ const [selectedItem, setSelectedItem] = useState<ScaleItem | null>(null)
91
+ const labelId = React.useId()
92
+ return (
93
+ <div>
94
+ <VisuallyHidden id={labelId}>Likert scale label</VisuallyHidden>
95
+ <LikertScaleLegacy
96
+ {...args}
97
+ labelId={labelId}
98
+ selectedItem={selectedItem}
99
+ onSelect={setSelectedItem}
100
+ />
101
+ </div>
102
+ )
103
+ },
104
+ args: {
105
+ isRequired: true,
106
+ },
107
+ play: async ({ canvasElement }) => {
108
+ const canvas = within(canvasElement.parentElement!)
109
+ const likertScale = canvas.getByRole('radiogroup', { name: 'Likert scale label' })
110
+
111
+ expect(likertScale).toHaveAttribute('aria-required', 'true')
112
+ },
113
+ }
@@ -94,7 +94,7 @@
94
94
  }
95
95
 
96
96
  .white.isDisabled {
97
- --link-text-color: rgba(var(--color-white-rgb), 0.2);
97
+ --link-text-color: rgb(var(--color-white-rgb), 0.2);
98
98
  }
99
99
 
100
100
  .reversed {
@@ -110,5 +110,5 @@
110
110
  }
111
111
 
112
112
  .reversed.isDisabled {
113
- --link-text-color: rgba(var(--color-white-rgb), 0.2);
113
+ --link-text-color: rgb(var(--color-white-rgb), 0.2);
114
114
  }
@@ -46,7 +46,6 @@ export type StatelessMenuProps = {
46
46
  'renderButton': (args: {
47
47
  'onClick': (e: any) => void
48
48
  'onMouseDown': (e: any) => void
49
- 'aria-haspopup': boolean
50
49
  'aria-expanded': boolean
51
50
  }) => React.ReactElement
52
51
  'onClick'?: (event: SyntheticEvent) => void
@@ -76,7 +75,6 @@ export const StatelessMenu = ({
76
75
  toggleMenuDropdown()
77
76
  },
78
77
  'onMouseDown': (e: React.MouseEvent<Element, MouseEvent>) => e.preventDefault(),
79
- 'aria-haspopup': true,
80
78
  'aria-expanded': isMenuVisible,
81
79
  })
82
80
 
@@ -4,6 +4,6 @@
4
4
  justify-content: center;
5
5
  width: 36px;
6
6
  background-color: transparent;
7
- color: rgba(var(color-purple-800-rgb), 0.7);
7
+ color: rgb(var(color-purple-800-rgb), 0.7);
8
8
  margin: 0 var(--spacing-4);
9
9
  }
@@ -46,7 +46,7 @@ $large-width: 450px;
46
46
  --border-width: var(--border-width-1);
47
47
 
48
48
  border: var(--border-width) var(--border-solid-border-style);
49
- filter: drop-shadow(0 0 7px rgba(0, 0, 0, 0.1));
49
+ filter: drop-shadow(0 0 7px rgb(0, 0, 0, 0.1));
50
50
  border-radius: $border-solid-border-radius;
51
51
  color: $color-purple-800;
52
52
  text-align: start;
@@ -7,9 +7,8 @@ $dt-color-radio-background-color-hover: $color-gray-200;
7
7
 
8
8
  .container {
9
9
  position: relative;
10
- overflow-wrap: break-word;
11
- word-wrap: break-word;
12
- word-break: break-word;
10
+ overflow-wrap: anywhere;
11
+ word-break: normal;
13
12
  margin-bottom: $spacing-sm;
14
13
 
15
14
  label {
@@ -6,7 +6,7 @@
6
6
 
7
7
  // Taken from design-tokens/sass/shadow
8
8
  // we need control of the x and y offset in this component
9
- $box-shadow-color-sm: rgba(53, 55, 74, 0.09);
9
+ $box-shadow-color-sm: rgb(53, 55, 74, 0.09);
10
10
  $row-height: 60px;
11
11
  $row-height-data-variant: 48px;
12
12
 
@@ -170,7 +170,7 @@ $row-height-data-variant: 48px;
170
170
 
171
171
  // This is an optical hack to stop the card shadow from overlapping over
172
172
  // the proceeding cards
173
- box-shadow: 0 4px 6px rgba(53, 55, 74, 0.04);
173
+ box-shadow: 0 4px 6px rgb(53, 55, 74, 0.04);
174
174
  border: solid 1px rgba($color-purple-700-rgb, 0.1);
175
175
  transition:
176
176
  box-shadow $animation-duration-rapid,
@@ -58,7 +58,7 @@
58
58
  .default {
59
59
  &:not(.error, .caution) {
60
60
  &:disabled {
61
- border-color: rgba(var(--color-gray-500-rgb), 0.3);
61
+ border-color: rgb(var(--color-gray-500-rgb), 0.3);
62
62
  }
63
63
  }
64
64
 
@@ -91,13 +91,13 @@
91
91
 
92
92
  &.disabled {
93
93
  background-color: var(--color-white);
94
- border-color: rgba(var(--color-gray-500-rgb), 0.3);
95
- color: rgba(var(--color-purple-800-rgb), 0.3);
94
+ border-color: rgb(var(--color-gray-500-rgb), 0.3);
95
+ color: rgb(var(--color-purple-800-rgb), 0.3);
96
96
  }
97
97
  }
98
98
 
99
99
  .reversed {
100
- border-color: rgba(var(--color-white-rgb), 0.65);
100
+ border-color: rgb(var(--color-white-rgb), 0.65);
101
101
  background: transparent;
102
102
  color: var(--color-white);
103
103
 
@@ -108,7 +108,7 @@
108
108
  &:focus:not([disabled]),
109
109
  &:hover:not([disabled]),
110
110
  &:hover:focus:not([disabled]) {
111
- background: rgba(var(--color-white-rgb), 0.1);
111
+ background: rgb(var(--color-white-rgb), 0.1);
112
112
  border-color: var(--color-white);
113
113
  }
114
114
 
@@ -134,7 +134,7 @@
134
134
 
135
135
  &.disabled {
136
136
  background: transparent;
137
- border-color: rgba(var(--color-white-rgb), 0.3);
138
- color: rgba(var(--color-white-rgb), 0.3);
137
+ border-color: rgb(var(--color-white-rgb), 0.3);
138
+ color: rgb(var(--color-white-rgb), 0.3);
139
139
  }
140
140
  }
@@ -8,7 +8,7 @@
8
8
  padding: var(--spacing-24) var(--spacing-12);
9
9
  background: var(--color-white);
10
10
  gap: var(--spacing-16);
11
- border-top: 2px solid rgba(var(--color-gray-600-rgb), 0.1);
11
+ border-top: 2px solid rgb(var(--color-gray-600-rgb), 0.1);
12
12
 
13
13
  @media (width >= 768px) {
14
14
  grid-template-columns: 1fr 5fr 1fr;
@@ -185,9 +185,9 @@
185
185
  }
186
186
 
187
187
  &.isDisabled {
188
- --button-bg-color: rgba(var(--color-white-rgb), 0.2);
188
+ --button-bg-color: rgb(var(--color-white-rgb), 0.2);
189
189
  --button-border-color: transparent;
190
- --button-text-color: rgba(var(--color-purple-800-rgb), 0.7);
190
+ --button-text-color: rgb(var(--color-purple-800-rgb), 0.7);
191
191
  }
192
192
  }
193
193
 
@@ -197,24 +197,24 @@
197
197
  --button-text-color: var(--color-white);
198
198
 
199
199
  &[data-hovered] {
200
- --button-bg-color: rgba(var(--color-white-rgb), 0.2);
200
+ --button-bg-color: rgb(var(--color-white-rgb), 0.2);
201
201
  --button-border-color: var(--color-white);
202
202
  }
203
203
 
204
204
  &[data-pressed] {
205
- --button-bg-color: rgba(var(--color-white-rgb), 0.1);
205
+ --button-bg-color: rgb(var(--color-white-rgb), 0.1);
206
206
  --button-border-color: var(--color-white);
207
207
  }
208
208
 
209
209
  &[data-pending] {
210
- --button-bg-color: rgba(var(--color-white-rgb), 0.1);
210
+ --button-bg-color: rgb(var(--color-white-rgb), 0.1);
211
211
  --button-border-color: var(--color-white);
212
212
  }
213
213
 
214
214
  &.isDisabled {
215
215
  --button-bg-color: transparent;
216
- --button-border-color: rgba(var(--color-white), 0.2);
217
- --button-text-color: rgba(var(--color-white-rgb), 0.2);
216
+ --button-border-color: rgb(var(--color-white), 0.2);
217
+ --button-text-color: rgb(var(--color-white-rgb), 0.2);
218
218
  }
219
219
  }
220
220
 
@@ -224,24 +224,24 @@
224
224
  --button-text-color: var(--color-white);
225
225
 
226
226
  &[data-hovered] {
227
- --button-bg-color: rgba(var(--color-white-rgb), 0.2);
227
+ --button-bg-color: rgb(var(--color-white-rgb), 0.2);
228
228
  --button-border-color: transparent;
229
229
  }
230
230
 
231
231
  &[data-pressed] {
232
- --button-bg-color: rgba(var(--color-white-rgb), 0.1);
232
+ --button-bg-color: rgb(var(--color-white-rgb), 0.1);
233
233
  --button-border-color: transparent;
234
234
  }
235
235
 
236
236
  &[data-pending] {
237
- --button-bg-color: rgba(var(--color-white-rgb), 0.1);
237
+ --button-bg-color: rgb(var(--color-white-rgb), 0.1);
238
238
  --button-border-color: transparent;
239
239
  }
240
240
 
241
241
  &.isDisabled {
242
242
  --button-bg-color: transparent;
243
243
  --button-border-color: transparent;
244
- --button-text-color: rgba(var(--color-white-rgb), 0.2);
244
+ --button-text-color: rgb(var(--color-white-rgb), 0.2);
245
245
  }
246
246
  }
247
247
 
@@ -85,6 +85,7 @@ export const Select = <Option extends SelectOption = SelectOption>({
85
85
  status,
86
86
  validationMessage,
87
87
  isReversed,
88
+ isRequired = false,
88
89
  isFullWidth,
89
90
  disabledValues,
90
91
  classNameOverride,
@@ -93,6 +94,7 @@ export const Select = <Option extends SelectOption = SelectOption>({
93
94
  placeholder = '',
94
95
  isDisabled,
95
96
  portalContainerId,
97
+ onSelectionChange,
96
98
  ...restProps
97
99
  }: SelectProps<Option>): JSX.Element => {
98
100
  const { refs } = useFloating<HTMLButtonElement>()
@@ -114,6 +116,8 @@ export const Select = <Option extends SelectOption = SelectOption>({
114
116
  description,
115
117
  placeholder,
116
118
  isDisabled,
119
+ isRequired,
120
+ onSelectionChange: onSelectionChange ? (key) => onSelectionChange(key!) : undefined,
117
121
  ...restProps,
118
122
  }
119
123
 
@@ -153,6 +157,7 @@ export const Select = <Option extends SelectOption = SelectOption>({
153
157
  isReversed,
154
158
  'ref': refs.setReference,
155
159
  'aria-describedby': classnames(validationMessage && validationId, description && descriptionId),
160
+ 'aria-required': isRequired,
156
161
  }
157
162
 
158
163
  const [portalContainer, setPortalContainer] = useState<HTMLElement>()
@@ -92,6 +92,14 @@ Add validation messages using `status` and `validationMessage`.
92
92
 
93
93
  <Canvas of={SelectStories.Validation} />
94
94
 
95
+ #### isRequired and validationBehavior
96
+
97
+ When using the `isRequired` property you can also specify the `validationBehavior` to change from `aria` to `native` form validation.
98
+
99
+ <Canvas of={SelectStories.SelectNativeValidationBehavior} />
100
+
101
+ While both use `aria-required` to announce whether the field has to have a value to assistive technologies, the `native` will option will prevent form submissions if the `selectedKey` is `undefined`.
102
+
95
103
  ### Full width
96
104
 
97
105
  Set `isFullWidth` to `true` to have the Select span the full width of its container.
@@ -1,7 +1,10 @@
1
1
  import React from 'react'
2
2
  import { type Meta, type StoryObj } from '@storybook/react'
3
+ import { expect, userEvent, waitFor, within } from '@storybook/test'
4
+ import { FieldMessage } from '~components/FieldMessage'
3
5
  import { ContextModal } from '~components/Modal'
4
6
  import { RadioField, RadioGroup } from '~components/Radio'
7
+ import { Button } from '~components/__next__'
5
8
  import { Select } from '../Select'
6
9
  import { type SelectOption } from '../types'
7
10
  import { groupedMockItems, mixedMockItemsDisabled, singleMockItems } from './mockData'
@@ -229,3 +232,93 @@ export const TouchDeviceTest: Story = {
229
232
  )
230
233
  },
231
234
  }
235
+
236
+ export const RequiredSelect: Story = {
237
+ args: {
238
+ label: 'Required Select',
239
+ isRequired: true,
240
+ validationBehavior: 'native',
241
+ },
242
+ render: (args) => <Select {...args} />,
243
+ }
244
+
245
+ export const SelectNativeValidationBehavior: Story = {
246
+ parameters: {
247
+ name: 'Required Select with native form validation',
248
+ },
249
+ args: {
250
+ label: 'Required Select',
251
+ isRequired: true,
252
+ validationBehavior: 'native',
253
+ },
254
+ render: (args) => {
255
+ const [hasSubmitted, setHasSubmitted] = React.useState(false)
256
+ return (
257
+ <div>
258
+ <form
259
+ className="flex flex-col gap-16"
260
+ name="form-with-required-select"
261
+ aria-describedby={hasSubmitted ? 'id--field-message-form' : undefined}
262
+ onSubmit={(e) => {
263
+ e.preventDefault()
264
+ setHasSubmitted(true)
265
+ }}
266
+ >
267
+ <Select {...args} isRequired />
268
+ <div>
269
+ <Button type="submit">Submit</Button>
270
+ </div>
271
+ </form>
272
+ {hasSubmitted && (
273
+ <FieldMessage
274
+ id="id--field-message-form"
275
+ classNameOverride="mt-8"
276
+ status="success"
277
+ message={'Form submitted!'}
278
+ />
279
+ )}
280
+ </div>
281
+ )
282
+ },
283
+ }
284
+
285
+ export const NativeFormValidationWithoutSelectedVal: Story = {
286
+ ...SelectNativeValidationBehavior,
287
+ play: async ({ canvasElement, step }) => {
288
+ const canvas = within(canvasElement.parentElement!)
289
+ const submitButton = canvas.getByRole('button', { name: 'Submit' })
290
+ const requiredSelect = canvas.getByRole('combobox', { name: 'Required Select' })
291
+ const form = await canvas.findByRole('form')
292
+
293
+ await step('Select has aria-required attribute', async () => {
294
+ expect(requiredSelect).toHaveAttribute('aria-required', 'true')
295
+ })
296
+
297
+ await step('Submit will not call onSubmit without a selected value', async () => {
298
+ await userEvent.click(submitButton)
299
+ await waitFor(() => {
300
+ expect(form).toHaveAccessibleDescription('')
301
+ })
302
+ })
303
+ },
304
+ }
305
+
306
+ export const NativeFormValidationWithSelectedVal: Story = {
307
+ ...SelectNativeValidationBehavior,
308
+ args: {
309
+ selectedKey: 'short-black',
310
+ },
311
+ play: async ({ canvasElement, step }) => {
312
+ const canvas = within(canvasElement.parentElement!)
313
+ const submitButton = canvas.getByRole('button', { name: 'Submit' })
314
+ const form = await canvas.findByRole('form')
315
+
316
+ await step('Submit will call onSubmit with a selected value', async () => {
317
+ await userEvent.click(submitButton)
318
+
319
+ await waitFor(() => {
320
+ expect(form).toHaveAccessibleDescription('Form submitted!')
321
+ })
322
+ })
323
+ },
324
+ }
@@ -91,7 +91,7 @@
91
91
  }
92
92
 
93
93
  &.selectToggle .icon {
94
- color: rgba(255, 255, 255, 0.8);
94
+ color: rgb(255, 255, 255, 0.8);
95
95
  }
96
96
 
97
97
  &:active,