@kaizen/components 1.79.4 → 1.79.6

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.
@@ -36,7 +36,8 @@ var LikertScaleLegacy = function (_a) {
36
36
  onSelect = _a.onSelect,
37
37
  validationMessage = _a.validationMessage,
38
38
  status = _a.status,
39
- labelId = _a.labelId;
39
+ labelId = _a.labelId,
40
+ isRequired = _a.isRequired;
40
41
  var _e = React.useState(null),
41
42
  hoveredItem = _e[0],
42
43
  setHoveredItem = _e[1];
@@ -83,11 +84,12 @@ var LikertScaleLegacy = function (_a) {
83
84
  var isRated = selectedItem && selectedItem.value > 0;
84
85
  return React__default.default.createElement("div", {
85
86
  className: classnames__default.default(LikertScaleLegacy_module.container, isRated && LikertScaleLegacy_module.rated, reversed && [LikertScaleLegacy_module.reversed], hoveredItem !== null && LikertScaleLegacy_module.hovered),
86
- "aria-labelledby": labelId,
87
+ "aria-labelledby": isRequired ? "".concat(labelId) : labelId,
87
88
  role: "radiogroup",
88
89
  tabIndex: -1,
89
90
  "aria-describedby": validationMessageId,
90
- "data-testid": dataTestId
91
+ "data-testid": dataTestId,
92
+ "aria-required": isRequired
91
93
  }, React__default.default.createElement("div", {
92
94
  className: LikertScaleLegacy_module.legend,
93
95
  "data-testid": dataTestId && "".concat(dataTestId, "-legend")
@@ -44,17 +44,19 @@ var Select = function (_a) {
44
44
  status = _a.status,
45
45
  validationMessage = _a.validationMessage,
46
46
  isReversed = _a.isReversed,
47
+ _c = _a.isRequired,
48
+ isRequired = _c === void 0 ? false : _c,
47
49
  isFullWidth = _a.isFullWidth,
48
50
  disabledValues = _a.disabledValues,
49
51
  classNameOverride = _a.classNameOverride,
50
52
  selectedKey = _a.selectedKey,
51
53
  description = _a.description,
52
- _c = _a.placeholder,
53
- placeholder = _c === void 0 ? '' : _c,
54
+ _d = _a.placeholder,
55
+ placeholder = _d === void 0 ? '' : _d,
54
56
  isDisabled = _a.isDisabled,
55
57
  onSelectionChange = _a.onSelectionChange,
56
58
  portalContainerId = _a.portalContainerId,
57
- restProps = tslib.__rest(_a, ["label", "items", "id", "trigger", "children", "status", "validationMessage", "isReversed", "isFullWidth", "disabledValues", "classNameOverride", "selectedKey", "description", "placeholder", "isDisabled", "onSelectionChange", "portalContainerId"]);
59
+ restProps = tslib.__rest(_a, ["label", "items", "id", "trigger", "children", "status", "validationMessage", "isReversed", "isRequired", "isFullWidth", "disabledValues", "classNameOverride", "selectedKey", "description", "placeholder", "isDisabled", "onSelectionChange", "portalContainerId"]);
58
60
  var refs = reactDom.useFloating().refs;
59
61
  var triggerRef = refs.reference;
60
62
  var fallbackId = React.useId();
@@ -72,18 +74,19 @@ var Select = function (_a) {
72
74
  description: description,
73
75
  placeholder: placeholder,
74
76
  isDisabled: isDisabled,
77
+ isRequired: isRequired,
75
78
  onSelectionChange: onSelectionChange ? function (key) {
76
79
  return onSelectionChange(key);
77
80
  } : undefined
78
81
  }, restProps);
79
82
  var state = select.useSelectState(ariaSelectProps);
80
- var _d = select$1.useSelect(ariaSelectProps, state, triggerRef),
81
- labelProps = _d.labelProps,
82
- reactAriaTriggerProps = _d.triggerProps,
83
- valueProps = _d.valueProps,
84
- menuProps = _d.menuProps,
85
- errorMessageProps = _d.errorMessageProps,
86
- descriptionProps = _d.descriptionProps;
83
+ var _e = select$1.useSelect(ariaSelectProps, state, triggerRef),
84
+ labelProps = _e.labelProps,
85
+ reactAriaTriggerProps = _e.triggerProps,
86
+ valueProps = _e.valueProps,
87
+ menuProps = _e.menuProps,
88
+ errorMessageProps = _e.errorMessageProps,
89
+ descriptionProps = _e.descriptionProps;
87
90
  // Hack incoming:
88
91
  // react-aria/useSelect wants to prefix the combobox's accessible name with the value of the select.
89
92
  // We use role=combobox, meaning screen readers will read the value.
@@ -105,11 +108,12 @@ var Select = function (_a) {
105
108
  'isDisabled': triggerProps.isDisabled,
106
109
  isReversed: isReversed,
107
110
  'ref': refs.setReference,
108
- 'aria-describedby': classnames__default.default(validationMessage && validationId, description && descriptionId)
111
+ 'aria-describedby': classnames__default.default(validationMessage && validationId, description && descriptionId),
112
+ 'aria-required': isRequired
109
113
  });
110
- var _e = React.useState(),
111
- portalContainer = _e[0],
112
- setPortalContainer = _e[1];
114
+ var _f = React.useState(),
115
+ portalContainer = _f[0],
116
+ setPortalContainer = _f[1];
113
117
  React.useEffect(function () {
114
118
  if (portalContainerId) {
115
119
  var portalElement = document.getElementById(portalContainerId);
@@ -27,7 +27,8 @@ var LikertScaleLegacy = function (_a) {
27
27
  onSelect = _a.onSelect,
28
28
  validationMessage = _a.validationMessage,
29
29
  status = _a.status,
30
- labelId = _a.labelId;
30
+ labelId = _a.labelId,
31
+ isRequired = _a.isRequired;
31
32
  var _e = useState(null),
32
33
  hoveredItem = _e[0],
33
34
  setHoveredItem = _e[1];
@@ -74,11 +75,12 @@ var LikertScaleLegacy = function (_a) {
74
75
  var isRated = selectedItem && selectedItem.value > 0;
75
76
  return /*#__PURE__*/React.createElement("div", {
76
77
  className: classnames(styles.container, isRated && styles.rated, reversed && [styles.reversed], hoveredItem !== null && styles.hovered),
77
- "aria-labelledby": labelId,
78
+ "aria-labelledby": isRequired ? "".concat(labelId) : labelId,
78
79
  role: "radiogroup",
79
80
  tabIndex: -1,
80
81
  "aria-describedby": validationMessageId,
81
- "data-testid": dataTestId
82
+ "data-testid": dataTestId,
83
+ "aria-required": isRequired
82
84
  }, /*#__PURE__*/React.createElement("div", {
83
85
  className: styles.legend,
84
86
  "data-testid": dataTestId && "".concat(dataTestId, "-legend")
@@ -36,17 +36,19 @@ const Select = /*#__PURE__*/function () {
36
36
  status = _a.status,
37
37
  validationMessage = _a.validationMessage,
38
38
  isReversed = _a.isReversed,
39
+ _c = _a.isRequired,
40
+ isRequired = _c === void 0 ? false : _c,
39
41
  isFullWidth = _a.isFullWidth,
40
42
  disabledValues = _a.disabledValues,
41
43
  classNameOverride = _a.classNameOverride,
42
44
  selectedKey = _a.selectedKey,
43
45
  description = _a.description,
44
- _c = _a.placeholder,
45
- placeholder = _c === void 0 ? '' : _c,
46
+ _d = _a.placeholder,
47
+ placeholder = _d === void 0 ? '' : _d,
46
48
  isDisabled = _a.isDisabled,
47
49
  onSelectionChange = _a.onSelectionChange,
48
50
  portalContainerId = _a.portalContainerId,
49
- restProps = __rest(_a, ["label", "items", "id", "trigger", "children", "status", "validationMessage", "isReversed", "isFullWidth", "disabledValues", "classNameOverride", "selectedKey", "description", "placeholder", "isDisabled", "onSelectionChange", "portalContainerId"]);
51
+ restProps = __rest(_a, ["label", "items", "id", "trigger", "children", "status", "validationMessage", "isReversed", "isRequired", "isFullWidth", "disabledValues", "classNameOverride", "selectedKey", "description", "placeholder", "isDisabled", "onSelectionChange", "portalContainerId"]);
50
52
  var refs = useFloating().refs;
51
53
  var triggerRef = refs.reference;
52
54
  var fallbackId = useId();
@@ -64,18 +66,19 @@ const Select = /*#__PURE__*/function () {
64
66
  description: description,
65
67
  placeholder: placeholder,
66
68
  isDisabled: isDisabled,
69
+ isRequired: isRequired,
67
70
  onSelectionChange: onSelectionChange ? function (key) {
68
71
  return onSelectionChange(key);
69
72
  } : undefined
70
73
  }, restProps);
71
74
  var state = useSelectState(ariaSelectProps);
72
- var _d = useSelect(ariaSelectProps, state, triggerRef),
73
- labelProps = _d.labelProps,
74
- reactAriaTriggerProps = _d.triggerProps,
75
- valueProps = _d.valueProps,
76
- menuProps = _d.menuProps,
77
- errorMessageProps = _d.errorMessageProps,
78
- descriptionProps = _d.descriptionProps;
75
+ var _e = useSelect(ariaSelectProps, state, triggerRef),
76
+ labelProps = _e.labelProps,
77
+ reactAriaTriggerProps = _e.triggerProps,
78
+ valueProps = _e.valueProps,
79
+ menuProps = _e.menuProps,
80
+ errorMessageProps = _e.errorMessageProps,
81
+ descriptionProps = _e.descriptionProps;
79
82
  // Hack incoming:
80
83
  // react-aria/useSelect wants to prefix the combobox's accessible name with the value of the select.
81
84
  // We use role=combobox, meaning screen readers will read the value.
@@ -97,11 +100,12 @@ const Select = /*#__PURE__*/function () {
97
100
  'isDisabled': triggerProps.isDisabled,
98
101
  isReversed: isReversed,
99
102
  'ref': refs.setReference,
100
- 'aria-describedby': classnames(validationMessage && validationId, description && descriptionId)
103
+ 'aria-describedby': classnames(validationMessage && validationId, description && descriptionId),
104
+ 'aria-required': isRequired
101
105
  });
102
- var _e = useState(),
103
- portalContainer = _e[0],
104
- setPortalContainer = _e[1];
106
+ var _f = useState(),
107
+ portalContainer = _f[0],
108
+ setPortalContainer = _f[1];
105
109
  useEffect(function () {
106
110
  if (portalContainerId) {
107
111
  var portalElement = document.getElementById(portalContainerId);
package/dist/styles.css CHANGED
@@ -5062,7 +5062,9 @@
5062
5062
  align-items: center;
5063
5063
  justify-content: center;
5064
5064
  position: absolute;
5065
- z-index: 10000;
5065
+
5066
+ /* from $ca-z-index-fixed */
5067
+ z-index: 1030;
5066
5068
  background: var(--color-white);
5067
5069
  inset-block: 0 1px;
5068
5070
  width: 48px;
@@ -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;
@@ -55,7 +55,7 @@ export type SelectProps<Option extends SelectOption = SelectOption> = {
55
55
  * {@link https://cultureamp.design/?path=/docs/components-select--docs Storybook}
56
56
  */
57
57
  export declare const Select: {
58
- <Option extends SelectOption = SelectOption>({ label, items, id: propsId, trigger, children, status, validationMessage, isReversed, isFullWidth, disabledValues, classNameOverride, selectedKey, description, placeholder, isDisabled, onSelectionChange, portalContainerId, ...restProps }: SelectProps<Option>): JSX.Element;
58
+ <Option extends SelectOption = SelectOption>({ label, items, id: propsId, trigger, children, status, validationMessage, isReversed, isRequired, isFullWidth, disabledValues, classNameOverride, selectedKey, description, placeholder, isDisabled, onSelectionChange, portalContainerId, ...restProps }: SelectProps<Option>): JSX.Element;
59
59
  displayName: string;
60
60
  Section: {
61
61
  <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.79.4",
3
+ "version": "1.79.6",
4
4
  "description": "Kaizen component library",
5
5
  "author": "Geoffrey Chong <geoff.chong@cultureamp.com>",
6
6
  "homepage": "https://cultureamp.design",
@@ -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
+ }
@@ -0,0 +1,91 @@
1
+ import { Meta } from '@storybook/blocks'
2
+
3
+ <Meta title="Components/Menu/Migration guide" />
4
+
5
+ # Menu migration guide
6
+
7
+ ## Audience
8
+
9
+ This guide is relevant for Kaizen All-In-One (KAIO) v1 consumers.
10
+
11
+ ## Purpose
12
+
13
+ This guide provides instructions for migrating menu usage from the `deprecated` (`@kaizen/components`) `Menu` component to the `next` (`@kaizen/components/next`) `Menu` component.
14
+
15
+ This migration is a prerequisite for [migrating to KAIO v2](/docs/releases-upcoming-major-releases--docs).
16
+
17
+ ## Key API changes
18
+
19
+ `next/Menu` separates its functionality into the following components:
20
+
21
+ - `MenuTrigger` wraps the `MenuPopover` component and its trigger element.
22
+ - `MenuPopover` contains a `Menu` component, and controls the popover placement and open and close interactions.
23
+ - `Menu` contains one or more `MenuItem` and `MenuSection` components.
24
+ - `MenuSection` enables menu items to be grouped into sections.
25
+ - `MenuHeader` provides a section's header content.
26
+ - `MenuItem` provides a menu item's content, and handles item selection.
27
+
28
+ Other notable changes:
29
+
30
+ - `Menu.align` prop becomes MenuPopover.placement, and values are mapped as follows:
31
+ - `left` becomes `start`
32
+ - `right` becomes `end`
33
+ - `Menu.autoHide` prop is retired
34
+ - `Menu.button` prop becomes `MenuTrigger.children`
35
+ - The trigger element must be a `next/Button`
36
+ - `Menu.dropdownWidth` prop is retired
37
+ - `Menu.portalSelector` prop is retired
38
+ - Where needed, [PortalProvider](https://react-spectrum.adobe.com/react-aria/PortalProvider.html) can be used to control portalling behaviour
39
+ - `MenuItem.destructive` prop is retired
40
+ - This change aligns with a broader move towards more judicious use of colour
41
+ - `MenuItem.disabled` prop becomes `MenuItem.isDisabled`
42
+ - `MenuItem.label` prop becomes `MenuItem.children`
43
+ - `MenuItem.onClick` prop becomes `MenuItem.onAction`
44
+ - React Aria's `Menu` does not expose native click events, e.g. `MenuItem.onAction` cannot call `e.preventDefault()`
45
+ - See React Aria [Menu documentation](https://react-spectrum.adobe.com/react-aria/Menu.html) for more details on working with `Menu` and `MenuItem` events
46
+ - `MenuList.heading` prop becomes `MenuHeader` in a `MenuSection`
47
+
48
+ ## Migration example
49
+
50
+ ### Before
51
+
52
+ ```tsx
53
+ <Menu button={<Button>Trigger</Button>}>
54
+ <MenuList>
55
+ <MenuList heading={<MenuHeading>Section One</MenuHeading>}>
56
+ <MenuItem onClick={() => alert('1')} label="Item 1" />
57
+ <MenuItem onClick={() => alert('2')} label="Item 2" />
58
+ </MenuList>
59
+ <MenuList heading={<MenuHeading>Section Two</MenuHeading>}>
60
+ <MenuItem onClick={() => alert('3')} label="Item 3" />
61
+ <MenuItem onClick={() => alert('4')} label="Item 4" />
62
+ </MenuList>
63
+ </MenuList>
64
+ </Menu>
65
+ ```
66
+
67
+ ### After
68
+
69
+ ```tsx
70
+ <MenuTrigger>
71
+ <Button>Trigger</Button>
72
+ <MenuPopover>
73
+ <Menu>
74
+ <MenuSection>
75
+ <MenuHeader>Section One</MenuHeader>
76
+ <MenuItem onAction={() => alert('1')}>Item 1</MenuItem>
77
+ <MenuItem onAction={() => alert('2')}>Item 2</MenuItem>
78
+ </MenuSection>
79
+ <MenuSection>
80
+ <MenuHeader>Section Two</MenuHeader>
81
+ <MenuItem onAction={() => alert('3')}>Item 3</MenuItem>
82
+ <MenuItem onAction={() => alert('4')}>Item 4</MenuItem>
83
+ </MenuSection>
84
+ </Menu>
85
+ </MenuPopover>
86
+ </MenuTrigger>
87
+ ```
88
+
89
+ ## More information
90
+
91
+ More information about `next/Menu` can be found at [API Specification](/docs/components-menu-menu-next-api-specification--docs) and [Usage Guidelines](/docs/components-menu-menu-next-usage-guidelines--docs).
@@ -89,6 +89,7 @@ export const Select = <Option extends SelectOption = SelectOption>({
89
89
  status,
90
90
  validationMessage,
91
91
  isReversed,
92
+ isRequired = false,
92
93
  isFullWidth,
93
94
  disabledValues,
94
95
  classNameOverride,
@@ -119,6 +120,7 @@ export const Select = <Option extends SelectOption = SelectOption>({
119
120
  description,
120
121
  placeholder,
121
122
  isDisabled,
123
+ isRequired,
122
124
  onSelectionChange: onSelectionChange ? (key) => onSelectionChange(key!) : undefined,
123
125
  ...restProps,
124
126
  }
@@ -159,6 +161,7 @@ export const Select = <Option extends SelectOption = SelectOption>({
159
161
  isReversed,
160
162
  'ref': refs.setReference,
161
163
  'aria-describedby': classnames(validationMessage && validationId, description && descriptionId),
164
+ 'aria-required': isRequired,
162
165
  }
163
166
 
164
167
  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
+ }
@@ -26,7 +26,9 @@
26
26
  align-items: center;
27
27
  justify-content: center;
28
28
  position: absolute;
29
- z-index: 10000;
29
+
30
+ /* from $ca-z-index-fixed */
31
+ z-index: 1030;
30
32
  background: var(--color-white);
31
33
  inset-block: 0 1px;
32
34
  width: 48px;