agroptima-design-system 1.2.32-beta.0 → 1.2.32
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.
- package/package.json +1 -1
- package/src/atoms/InputWithSelect/InputWithSelect.scss +0 -4
- package/src/atoms/InputWithSelect/InputWithSelect.tsx +14 -12
- package/src/stories/Changelog.mdx +2 -1
- package/src/stories/InputWithSelect.stories.tsx +11 -10
- package/tests/InputWithSelect.spec.tsx +19 -20
- package/tests/library.ts +1 -0
- package/.claude/scheduled_tasks.lock +0 -1
package/package.json
CHANGED
|
@@ -40,10 +40,6 @@
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
// Input.scss colors every .icon inside .input-container as neutral-400 by
|
|
44
|
-
// default (see .password-icon there for the same kind of exception). This
|
|
45
|
-
// selector is qualified one level deeper than that rule so it wins on
|
|
46
|
-
// specificity regardless of CSS load order between the two files.
|
|
47
43
|
.input-with-select .input-container .input-suffix .select-suffix-trigger {
|
|
48
44
|
@include mixins.icon-color(var(--primary-color-600));
|
|
49
45
|
|
|
@@ -10,6 +10,7 @@ import type { Option } from '../Select'
|
|
|
10
10
|
import { SelectItem } from '../Select/SelectItem'
|
|
11
11
|
|
|
12
12
|
const ESCAPE_KEY = 'Escape'
|
|
13
|
+
const EMPTY_OPTION: Option = { id: '', label: '' }
|
|
13
14
|
|
|
14
15
|
export interface InputWithSelectProps extends Omit<
|
|
15
16
|
InputProps,
|
|
@@ -17,7 +18,7 @@ export interface InputWithSelectProps extends Omit<
|
|
|
17
18
|
> {
|
|
18
19
|
options: Option[]
|
|
19
20
|
selectAccessibilityLabel: string
|
|
20
|
-
|
|
21
|
+
selectFieldName?: string
|
|
21
22
|
defaultSelectedOption?: string
|
|
22
23
|
onSelectChange?: (value: string) => void
|
|
23
24
|
}
|
|
@@ -25,7 +26,7 @@ export interface InputWithSelectProps extends Omit<
|
|
|
25
26
|
export function InputWithSelect({
|
|
26
27
|
options,
|
|
27
28
|
selectAccessibilityLabel,
|
|
28
|
-
|
|
29
|
+
selectFieldName,
|
|
29
30
|
defaultSelectedOption,
|
|
30
31
|
onSelectChange = () => {},
|
|
31
32
|
className,
|
|
@@ -33,18 +34,15 @@ export function InputWithSelect({
|
|
|
33
34
|
}: InputWithSelectProps): React.JSX.Element {
|
|
34
35
|
const { isOpen, close, toggle } = useOpen()
|
|
35
36
|
const defaultOption =
|
|
36
|
-
options.find((option) => option.id === defaultSelectedOption) ||
|
|
37
|
+
options.find((option) => option.id === defaultSelectedOption) ||
|
|
38
|
+
options[0] ||
|
|
39
|
+
EMPTY_OPTION
|
|
37
40
|
const [selectedOption, setSelectedOption] = useState<Option>(defaultOption)
|
|
38
41
|
const containerRef = useRef(null)
|
|
39
42
|
const triggerRef = useRef<HTMLButtonElement>(null)
|
|
40
43
|
const optionsId = `${props.id || props.name}-select-options`
|
|
41
44
|
|
|
42
|
-
|
|
43
|
-
if (!isOpen) return
|
|
44
|
-
close()
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
useOutsideClick(containerRef, handleClose)
|
|
45
|
+
useOutsideClick(containerRef, close)
|
|
48
46
|
|
|
49
47
|
useEffect(() => {
|
|
50
48
|
if (!isOpen) return
|
|
@@ -76,7 +74,7 @@ export function InputWithSelect({
|
|
|
76
74
|
type="button"
|
|
77
75
|
role="combobox"
|
|
78
76
|
className="select-suffix-trigger"
|
|
79
|
-
aria-label={selectAccessibilityLabel}
|
|
77
|
+
aria-label={`${selectAccessibilityLabel}: ${selectedOption.label}`}
|
|
80
78
|
aria-haspopup="listbox"
|
|
81
79
|
aria-expanded={isOpen}
|
|
82
80
|
aria-controls={optionsId}
|
|
@@ -86,8 +84,12 @@ export function InputWithSelect({
|
|
|
86
84
|
<span>{selectedOption.label}</span>
|
|
87
85
|
<Icon size="3" name={isOpen ? 'AngleUp' : 'AngleDown'} decorative />
|
|
88
86
|
</button>
|
|
89
|
-
{
|
|
90
|
-
<input
|
|
87
|
+
{selectFieldName && (
|
|
88
|
+
<input
|
|
89
|
+
type="hidden"
|
|
90
|
+
name={selectFieldName}
|
|
91
|
+
value={selectedOption.id}
|
|
92
|
+
/>
|
|
91
93
|
)}
|
|
92
94
|
{isOpen && (
|
|
93
95
|
<ul id={optionsId} className="select-suffix-options" role="listbox">
|
|
@@ -9,7 +9,7 @@ const meta = {
|
|
|
9
9
|
description: {
|
|
10
10
|
component:
|
|
11
11
|
'<h2>Usage guidelines</h2>' +
|
|
12
|
-
'<p>The InputWithSelect component is used when the value the user types needs a unit or symbol attached to it, such as a
|
|
12
|
+
'<p>The InputWithSelect component is used when the value the user types needs a unit or symbol attached to it, such as a percentage or a measurement. It combines an input field with a compact select fused into the same field, placed as a suffix.</p>' +
|
|
13
13
|
'<ul>' +
|
|
14
14
|
' <li>Use when the unit is closely tied to the value and only a short, fixed list of symbols applies (e.g. %, kg, m²)</li>' +
|
|
15
15
|
' <li>Keep the list of options short, these are not meant to be searched</li>' +
|
|
@@ -30,7 +30,7 @@ const meta = {
|
|
|
30
30
|
name: {
|
|
31
31
|
description: 'Set name property for the input value',
|
|
32
32
|
},
|
|
33
|
-
|
|
33
|
+
selectFieldName: {
|
|
34
34
|
description:
|
|
35
35
|
'Set name property for the hidden input holding the selected option, if it needs to be submitted in a form',
|
|
36
36
|
},
|
|
@@ -61,24 +61,25 @@ type Story = StoryObj<typeof meta>
|
|
|
61
61
|
|
|
62
62
|
export const Primary: Story = {
|
|
63
63
|
args: {
|
|
64
|
-
label: '
|
|
65
|
-
name: '
|
|
64
|
+
label: 'Weight',
|
|
65
|
+
name: 'weight',
|
|
66
66
|
type: 'number',
|
|
67
67
|
defaultValue: 10,
|
|
68
|
-
helpText: '
|
|
69
|
-
selectAccessibilityLabel: 'Select
|
|
68
|
+
helpText: 'This text can help you',
|
|
69
|
+
selectAccessibilityLabel: 'Select weight unit',
|
|
70
70
|
options: [
|
|
71
|
-
{ id: '
|
|
72
|
-
{ id: '
|
|
71
|
+
{ id: 'kg', label: 'kg' },
|
|
72
|
+
{ id: 'g', label: 'g' },
|
|
73
|
+
{ id: 'lb', label: 'lb' },
|
|
73
74
|
],
|
|
74
|
-
id: '
|
|
75
|
+
id: 'weight',
|
|
75
76
|
},
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
export const WithErrors: Story = {
|
|
79
80
|
args: {
|
|
80
81
|
...Primary.args,
|
|
81
|
-
errors: ['
|
|
82
|
+
errors: ['Weight must be greater than 0'],
|
|
82
83
|
},
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -4,8 +4,8 @@ import React from 'react'
|
|
|
4
4
|
import { InputWithSelect } from '../src/atoms/InputWithSelect'
|
|
5
5
|
|
|
6
6
|
const options = [
|
|
7
|
-
{ id: '
|
|
8
|
-
{ id: '
|
|
7
|
+
{ id: 'percentage', label: '%' },
|
|
8
|
+
{ id: 'kilogram', label: 'kg' },
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
describe('InputWithSelect', () => {
|
|
@@ -22,50 +22,49 @@ describe('InputWithSelect', () => {
|
|
|
22
22
|
|
|
23
23
|
expect(screen.getByRole('spinbutton')).toBeInTheDocument()
|
|
24
24
|
expect(
|
|
25
|
-
screen.getByRole('combobox', { name: 'Select unit' }),
|
|
25
|
+
screen.getByRole('combobox', { name: 'Select unit: %' }),
|
|
26
26
|
).toHaveTextContent('%')
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
it('
|
|
29
|
+
it('opens the options list and updates the selected option on click', async () => {
|
|
30
|
+
const user = userEvent.setup()
|
|
31
|
+
const handleSelectChange = jest.fn()
|
|
30
32
|
render(
|
|
31
33
|
<InputWithSelect
|
|
32
|
-
defaultSelectedOption="kg"
|
|
33
34
|
label="Discount"
|
|
34
35
|
name="discount"
|
|
36
|
+
onSelectChange={handleSelectChange}
|
|
35
37
|
options={options}
|
|
36
38
|
selectAccessibilityLabel="Select unit"
|
|
37
39
|
type="number"
|
|
38
40
|
/>,
|
|
39
41
|
)
|
|
40
42
|
|
|
43
|
+
await user.click(screen.getByRole('combobox', { name: 'Select unit: %' }))
|
|
44
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument()
|
|
45
|
+
|
|
46
|
+
await user.click(screen.getByRole('option', { name: 'kg' }))
|
|
47
|
+
|
|
48
|
+
expect(handleSelectChange).toHaveBeenCalledWith('kilogram')
|
|
41
49
|
expect(
|
|
42
|
-
screen.getByRole('combobox', { name: 'Select unit' }),
|
|
50
|
+
screen.getByRole('combobox', { name: 'Select unit: kg' }),
|
|
43
51
|
).toHaveTextContent('kg')
|
|
52
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
44
53
|
})
|
|
45
54
|
|
|
46
|
-
it('
|
|
47
|
-
const user = userEvent.setup()
|
|
48
|
-
const handleSelectChange = jest.fn()
|
|
55
|
+
it('renders an empty selection without crashing when options is empty', () => {
|
|
49
56
|
render(
|
|
50
57
|
<InputWithSelect
|
|
51
58
|
label="Discount"
|
|
52
59
|
name="discount"
|
|
53
|
-
|
|
54
|
-
options={options}
|
|
60
|
+
options={[]}
|
|
55
61
|
selectAccessibilityLabel="Select unit"
|
|
56
62
|
type="number"
|
|
57
63
|
/>,
|
|
58
64
|
)
|
|
59
65
|
|
|
60
|
-
await user.click(screen.getByRole('combobox', { name: 'Select unit' }))
|
|
61
|
-
expect(screen.getByRole('listbox')).toBeInTheDocument()
|
|
62
|
-
|
|
63
|
-
await user.click(screen.getByRole('option', { name: 'kg' }))
|
|
64
|
-
|
|
65
|
-
expect(handleSelectChange).toHaveBeenCalledWith('kg')
|
|
66
66
|
expect(
|
|
67
|
-
screen.getByRole('combobox', { name: 'Select unit' }),
|
|
68
|
-
).toHaveTextContent('
|
|
69
|
-
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
67
|
+
screen.getByRole('combobox', { name: 'Select unit:' }),
|
|
68
|
+
).toHaveTextContent('')
|
|
70
69
|
})
|
|
71
70
|
})
|
package/tests/library.ts
CHANGED
|
@@ -23,6 +23,7 @@ export * as IconButton from '../src/stories/IconButton.stories'
|
|
|
23
23
|
export * as InfiniteSelect from '../src/stories/InfiniteSelect.stories'
|
|
24
24
|
export * as Input from '../src/stories/Input.stories'
|
|
25
25
|
export * as InputWithButton from '../src/stories/InputWithButton.stories'
|
|
26
|
+
export * as InputWithSelect from '../src/stories/InputWithSelect.stories'
|
|
26
27
|
export * as Label from '../src/stories/Label.stories'
|
|
27
28
|
export * as Menu from '../src/stories/Menu.stories'
|
|
28
29
|
export * as Modal from '../src/stories/Modal.stories'
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"sessionId":"f3e7999c-f3bc-4b21-918e-276243976569","pid":533964,"procStart":"130173790","acquiredAt":1785859988086}
|