goobs-frontend 0.9.10 → 0.9.11
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/components/DataGrid/Table/ColumnHeaderRow/index.tsx +15 -5
- package/src/components/DataGrid/Table/index.tsx +14 -7
- package/src/components/DataGrid/index.tsx +5 -6
- package/src/components/DataGrid/utils/useComputeTableResize.tsx +41 -23
- package/src/components/Field/Dropdown/Searchable/index.tsx +125 -31
- package/src/components/Field/Dropdown/Searchable/searchabledropdown.stories.tsx +373 -26
- package/src/components/Toolbar/index.tsx +0 -2
- package/src/components/Toolbar/leftCenter/index.tsx +1 -1
- package/src/index.ts +5 -4
package/package.json
CHANGED
|
@@ -19,7 +19,7 @@ interface ColumnHeaderRowProps {
|
|
|
19
19
|
// The entire columns array if we need them on mobile
|
|
20
20
|
allColumns: ColumnDef[]
|
|
21
21
|
|
|
22
|
-
// The chosen
|
|
22
|
+
// The chosen "overflow" column or mobile column
|
|
23
23
|
selectedOverflowField: string
|
|
24
24
|
setSelectedOverflowField: React.Dispatch<React.SetStateAction<string>>
|
|
25
25
|
}
|
|
@@ -77,7 +77,7 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
|
|
|
77
77
|
boxSizing: 'border-box',
|
|
78
78
|
overflow: 'visible',
|
|
79
79
|
position: 'relative',
|
|
80
|
-
zIndex:
|
|
80
|
+
zIndex: 100, // Increased z-index for mobile dropdown
|
|
81
81
|
// If you want no left padding on mobile header as well:
|
|
82
82
|
paddingLeft: 0,
|
|
83
83
|
}}
|
|
@@ -93,6 +93,10 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
|
|
|
93
93
|
shrunkfontcolor="black"
|
|
94
94
|
unshrunkfontcolor="black"
|
|
95
95
|
shrunklabelposition="aboveNotch"
|
|
96
|
+
style={{
|
|
97
|
+
marginBottom: 0,
|
|
98
|
+
marginTop: 0,
|
|
99
|
+
}}
|
|
96
100
|
/>
|
|
97
101
|
</TableCell>
|
|
98
102
|
</TableRow>
|
|
@@ -133,12 +137,14 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
|
|
|
133
137
|
<TableCell
|
|
134
138
|
key="overflow-header"
|
|
135
139
|
sx={{
|
|
136
|
-
width: 275,
|
|
140
|
+
width: 275, // Increased width for dropdown (was 200)
|
|
141
|
+
minWidth: 275,
|
|
137
142
|
boxSizing: 'border-box',
|
|
138
143
|
overflow: 'visible',
|
|
139
144
|
position: 'relative',
|
|
140
|
-
zIndex:
|
|
145
|
+
zIndex: 100, // Increased z-index to ensure dropdown appears above other elements
|
|
141
146
|
paddingLeft: 0, // <-- remove left padding here
|
|
147
|
+
height: '55px',
|
|
142
148
|
}}
|
|
143
149
|
>
|
|
144
150
|
<SearchableDropdown
|
|
@@ -154,7 +160,11 @@ const ColumnHeaderRow: React.FC<ColumnHeaderRowProps> = ({
|
|
|
154
160
|
inputfontcolor="black"
|
|
155
161
|
shrunkfontcolor="black"
|
|
156
162
|
unshrunkfontcolor="black"
|
|
157
|
-
shrunklabelposition="
|
|
163
|
+
shrunklabelposition="onNotch"
|
|
164
|
+
style={{
|
|
165
|
+
marginBottom: 0,
|
|
166
|
+
marginTop: 0,
|
|
167
|
+
}}
|
|
158
168
|
/>
|
|
159
169
|
</TableCell>
|
|
160
170
|
)
|
|
@@ -52,7 +52,7 @@ function Table({
|
|
|
52
52
|
})
|
|
53
53
|
|
|
54
54
|
// Decide which columns to render in the <TableHead /> for desktop.
|
|
55
|
-
// On mobile, we skip the
|
|
55
|
+
// On mobile, we skip the "__overflow__" approach and just show the single dropdown.
|
|
56
56
|
const finalDesktopColumns = !isMobile
|
|
57
57
|
? overflowDesktopColumns.length > 0
|
|
58
58
|
? [
|
|
@@ -63,16 +63,23 @@ function Table({
|
|
|
63
63
|
: []
|
|
64
64
|
|
|
65
65
|
return (
|
|
66
|
-
// The main wrapper
|
|
67
|
-
<Box sx={{ width: '100%', overflowX: '
|
|
66
|
+
// The main wrapper - Using overflowX: 'hidden' to prevent horizontal scrollbar
|
|
67
|
+
<Box sx={{ width: '100%', overflowX: 'hidden' }}>
|
|
68
68
|
{/* We set the "ref" here so that useComputeTableResize can measure width. */}
|
|
69
|
-
<TableContainer
|
|
69
|
+
<TableContainer
|
|
70
|
+
ref={containerRef}
|
|
71
|
+
sx={{
|
|
72
|
+
overflowX: 'visible', // Changed from 'auto' to 'visible'
|
|
73
|
+
}}
|
|
74
|
+
>
|
|
70
75
|
<MuiTable
|
|
71
76
|
sx={{
|
|
72
|
-
//
|
|
77
|
+
// Set width to 100% to fit container
|
|
78
|
+
width: '100%',
|
|
79
|
+
// Keep tableLayout as 'auto' to respect column widths
|
|
73
80
|
tableLayout: 'auto',
|
|
74
81
|
// Force the table's minimum width to accommodate large columns
|
|
75
|
-
minWidth: 'fit-content',
|
|
82
|
+
minWidth: isMobile ? 'auto' : 'fit-content',
|
|
76
83
|
}}
|
|
77
84
|
>
|
|
78
85
|
{/* Table Header */}
|
|
@@ -87,7 +94,7 @@ function Table({
|
|
|
87
94
|
finalDesktopColumns={finalDesktopColumns}
|
|
88
95
|
// Overflow columns (desktop)
|
|
89
96
|
overflowDesktopColumns={overflowDesktopColumns}
|
|
90
|
-
// Current
|
|
97
|
+
// Current "selected" column for overflow or mobile
|
|
91
98
|
selectedOverflowField={selectedOverflowField}
|
|
92
99
|
setSelectedOverflowField={setSelectedOverflowField}
|
|
93
100
|
// The entire columns array so we can present them all on mobile
|
|
@@ -93,8 +93,8 @@ function DataGrid({
|
|
|
93
93
|
flexDirection: 'column',
|
|
94
94
|
// Increase or remove height if you want more vertical space:
|
|
95
95
|
height: 'calc(100vh - 60px)',
|
|
96
|
-
//
|
|
97
|
-
overflow: '
|
|
96
|
+
// Add overflow hidden at the DataGrid level to prevent horizontal scrollbars
|
|
97
|
+
overflow: 'hidden',
|
|
98
98
|
backgroundColor: woad.main,
|
|
99
99
|
}}
|
|
100
100
|
>
|
|
@@ -132,12 +132,11 @@ function DataGrid({
|
|
|
132
132
|
display: 'flex',
|
|
133
133
|
flexDirection: 'column',
|
|
134
134
|
alignItems: 'flex-start',
|
|
135
|
+
// Ensure this container doesn't create scrollbars
|
|
136
|
+
overflow: 'hidden',
|
|
135
137
|
}}
|
|
136
138
|
>
|
|
137
|
-
{/*
|
|
138
|
-
This is the actual <Table/> component (not the file).
|
|
139
|
-
Just leaving it as-is, but inside it we do a horizontal scroll and tableLayout: 'auto'.
|
|
140
|
-
*/}
|
|
139
|
+
{/* Table component */}
|
|
141
140
|
<Table
|
|
142
141
|
columns={columns}
|
|
143
142
|
rows={visibleRows}
|
|
@@ -86,8 +86,8 @@ export function useComputeTableResize({
|
|
|
86
86
|
return 60
|
|
87
87
|
}
|
|
88
88
|
const header = col.headerName || col.field
|
|
89
|
-
// +
|
|
90
|
-
return measureTextWidth(header) +
|
|
89
|
+
// +60 as a larger buffer for padding, sorting icons, etc. to prevent premature overflow
|
|
90
|
+
return measureTextWidth(header) + 60
|
|
91
91
|
},
|
|
92
92
|
[measureTextWidth]
|
|
93
93
|
)
|
|
@@ -102,14 +102,17 @@ export function useComputeTableResize({
|
|
|
102
102
|
if (!containerRef.current) return
|
|
103
103
|
const containerWidth = containerRef.current.offsetWidth
|
|
104
104
|
|
|
105
|
+
// Add a buffer zone to make transitions smoother
|
|
106
|
+
const COLUMN_TRANSITION_BUFFER = 50 // pixels of buffer
|
|
107
|
+
|
|
105
108
|
// Only consider columns that are visible
|
|
106
109
|
const visibleCols = columns.filter(
|
|
107
110
|
col => columnVisibility[col.field] !== false
|
|
108
111
|
)
|
|
109
112
|
|
|
110
113
|
let usedWidth = checkboxSelection ? 50 : 0
|
|
111
|
-
//
|
|
112
|
-
const overflowReservedWidth = showOverflowDropdown ?
|
|
114
|
+
// Increase overflow column width for better usability (was 180)
|
|
115
|
+
const overflowReservedWidth = showOverflowDropdown ? 275 : 0
|
|
113
116
|
|
|
114
117
|
const canFit: ColumnDef[] = []
|
|
115
118
|
let theOverflow: ColumnDef[] = []
|
|
@@ -120,28 +123,43 @@ export function useComputeTableResize({
|
|
|
120
123
|
|
|
121
124
|
if (col.width != null) {
|
|
122
125
|
// If the developer explicitly set a width, forcibly add to canFit
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
126
|
+
// only if we have enough space with our buffer
|
|
127
|
+
if (
|
|
128
|
+
usedWidth +
|
|
129
|
+
needed +
|
|
130
|
+
overflowReservedWidth +
|
|
131
|
+
COLUMN_TRANSITION_BUFFER <=
|
|
132
|
+
containerWidth
|
|
133
|
+
) {
|
|
134
|
+
canFit.push(col)
|
|
135
|
+
usedWidth += needed
|
|
136
|
+
} else {
|
|
137
|
+
// Not enough space, all remaining columns go to overflow
|
|
138
|
+
theOverflow = visibleCols.slice(i)
|
|
139
|
+
break
|
|
140
|
+
}
|
|
132
141
|
} else {
|
|
133
|
-
//
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
// Standard "does it fit?" check with buffer to prevent flickering/jumping
|
|
143
|
+
if (
|
|
144
|
+
usedWidth + needed + overflowReservedWidth <=
|
|
145
|
+
containerWidth - COLUMN_TRANSITION_BUFFER
|
|
146
|
+
) {
|
|
147
|
+
canFit.push(col)
|
|
148
|
+
usedWidth += needed
|
|
149
|
+
} else {
|
|
150
|
+
// everything else is overflow
|
|
151
|
+
theOverflow = visibleCols.slice(i)
|
|
152
|
+
|
|
153
|
+
// If we can't fit i-th column, let's see if we can also move
|
|
154
|
+
// the last fitted column to overflow to make more room
|
|
155
|
+
if (theOverflow.length > 0 && canFit.length > 1) {
|
|
156
|
+
const lastFitted = canFit.pop()
|
|
157
|
+
if (lastFitted) {
|
|
158
|
+
theOverflow = [lastFitted, ...theOverflow]
|
|
159
|
+
}
|
|
142
160
|
}
|
|
161
|
+
break
|
|
143
162
|
}
|
|
144
|
-
break
|
|
145
163
|
}
|
|
146
164
|
}
|
|
147
165
|
|
|
@@ -17,6 +17,11 @@ export interface DropdownOption {
|
|
|
17
17
|
value: string
|
|
18
18
|
attribute1?: string
|
|
19
19
|
attribute2?: string
|
|
20
|
+
attribute3?: string // New attribute for complex variant
|
|
21
|
+
attribute4?: string // New attribute for complex variant
|
|
22
|
+
attribute5?: string // Additional attribute for complex variant
|
|
23
|
+
attribute6?: string // Additional attribute for complex variant
|
|
24
|
+
uniqueKey?: string // Add uniqueKey for React key usage
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
export interface SearchableDropdownProps {
|
|
@@ -41,6 +46,8 @@ export interface SearchableDropdownProps {
|
|
|
41
46
|
width?: string
|
|
42
47
|
// Added style property to allow additional styling (e.g., marginBottom)
|
|
43
48
|
style?: React.CSSProperties
|
|
49
|
+
// New variant property to determine display style
|
|
50
|
+
variant?: 'simple' | 'complex'
|
|
44
51
|
}
|
|
45
52
|
|
|
46
53
|
const StyledFormControl = styled(FormControl)<{ width?: string }>(
|
|
@@ -89,6 +96,7 @@ interface StyledAutocompleteProps {
|
|
|
89
96
|
placeholdercolor?: string
|
|
90
97
|
shrunklabelposition?: 'onNotch' | 'aboveNotch'
|
|
91
98
|
disabled?: boolean
|
|
99
|
+
variant?: 'simple' | 'complex'
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
const StyledAutocomplete = styled(
|
|
@@ -102,6 +110,7 @@ const StyledAutocomplete = styled(
|
|
|
102
110
|
placeholdercolor,
|
|
103
111
|
shrunklabelposition,
|
|
104
112
|
disabled,
|
|
113
|
+
variant,
|
|
105
114
|
} = props
|
|
106
115
|
|
|
107
116
|
return {
|
|
@@ -162,16 +171,22 @@ const StyledAutocomplete = styled(
|
|
|
162
171
|
'& .MuiAutocomplete-input': {
|
|
163
172
|
padding: '8px 14px',
|
|
164
173
|
},
|
|
174
|
+
// Improve dropdown menu positioning and styling
|
|
165
175
|
'& .MuiAutocomplete-popper': {
|
|
166
176
|
width: '100% !important',
|
|
177
|
+
zIndex: 9999, // Ensure high z-index for the popup
|
|
167
178
|
'& .MuiPaper-root': {
|
|
168
179
|
width: '100%',
|
|
169
180
|
marginTop: '4px',
|
|
181
|
+
maxHeight: '300px', // Increase max height for better usability
|
|
182
|
+
overflowY: 'auto',
|
|
183
|
+
boxShadow: '0px 5px 15px rgba(0, 0, 0, 0.2)', // Enhanced shadow
|
|
184
|
+
border: `1px solid ${black.light}`, // Add border to dropdown container
|
|
170
185
|
},
|
|
171
186
|
'& .MuiAutocomplete-listbox': {
|
|
172
|
-
padding: '
|
|
187
|
+
padding: '0', // Remove default padding for cleaner lines
|
|
173
188
|
'& .MuiAutocomplete-option': {
|
|
174
|
-
padding: '8px 14px',
|
|
189
|
+
padding: variant === 'complex' ? '10px 14px' : '8px 14px',
|
|
175
190
|
display: 'flex',
|
|
176
191
|
flexDirection: 'column',
|
|
177
192
|
alignItems: 'flex-start',
|
|
@@ -180,6 +195,15 @@ const StyledAutocomplete = styled(
|
|
|
180
195
|
width: '100%',
|
|
181
196
|
textAlign: 'left',
|
|
182
197
|
},
|
|
198
|
+
'&:last-child': {
|
|
199
|
+
borderBottom: 'none', // Remove border from last item to avoid double borders
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
'& .MuiAutocomplete-option[aria-selected="true"]': {
|
|
203
|
+
backgroundColor: `${black.main}08`,
|
|
204
|
+
},
|
|
205
|
+
'& .MuiAutocomplete-option:hover': {
|
|
206
|
+
backgroundColor: `${black.main}15`, // Slightly darker hover state
|
|
183
207
|
},
|
|
184
208
|
},
|
|
185
209
|
},
|
|
@@ -210,7 +234,8 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
|
|
|
210
234
|
placeholder,
|
|
211
235
|
disabled = false,
|
|
212
236
|
width,
|
|
213
|
-
style,
|
|
237
|
+
style,
|
|
238
|
+
variant = 'simple', // Default to simple variant
|
|
214
239
|
}) => {
|
|
215
240
|
const [value, setValue] = useState<DropdownOption | string | null>(null)
|
|
216
241
|
const [inputValue, setInputValue] = useState('')
|
|
@@ -267,7 +292,7 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
|
|
|
267
292
|
error={error}
|
|
268
293
|
disabled={disabled}
|
|
269
294
|
width={width}
|
|
270
|
-
style={style}
|
|
295
|
+
style={style}
|
|
271
296
|
>
|
|
272
297
|
<StyledInputLabel
|
|
273
298
|
id={labelId}
|
|
@@ -302,12 +327,16 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
|
|
|
302
327
|
/>
|
|
303
328
|
}
|
|
304
329
|
disablePortal={false}
|
|
330
|
+
ListboxProps={{
|
|
331
|
+
style: { maxHeight: '300px', overflowY: 'auto' },
|
|
332
|
+
}}
|
|
305
333
|
disabled={disabled}
|
|
306
334
|
backgroundcolor={backgroundcolor}
|
|
307
335
|
outlinecolor={outlinecolor}
|
|
308
336
|
fontcolor={fontcolor}
|
|
309
337
|
inputfontcolor={inputfontcolor}
|
|
310
338
|
placeholdercolor={placeholdercolor}
|
|
339
|
+
variant={variant}
|
|
311
340
|
filterOptions={(opts, state) => {
|
|
312
341
|
const input = state.inputValue.toLowerCase()
|
|
313
342
|
return opts.filter(o => o.value.toLowerCase().includes(input))
|
|
@@ -328,45 +357,110 @@ const SearchableDropdown: React.FC<SearchableDropdownProps> = ({
|
|
|
328
357
|
const { key, ...restLiProps } = liProps as {
|
|
329
358
|
key: string
|
|
330
359
|
} & React.HTMLAttributes<HTMLLIElement>
|
|
360
|
+
|
|
361
|
+
// Common styles for both variants
|
|
362
|
+
const liStyle = {
|
|
363
|
+
color: black.main,
|
|
364
|
+
padding: variant === 'complex' ? '10px 14px' : '8px 14px',
|
|
365
|
+
display: 'flex',
|
|
366
|
+
flexDirection: 'column' as const,
|
|
367
|
+
alignItems: 'flex-start' as const,
|
|
368
|
+
gap: variant === 'complex' ? '4px' : '2px',
|
|
369
|
+
width: '100%',
|
|
370
|
+
borderBottom: `1px solid ${black.light}`,
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Use the uniqueKey prop if available, otherwise fall back to the provided key
|
|
374
|
+
const optionKey = option.uniqueKey || key
|
|
375
|
+
|
|
331
376
|
return (
|
|
332
|
-
<li
|
|
333
|
-
|
|
334
|
-
{...restLiProps}
|
|
335
|
-
style={{
|
|
336
|
-
color: black.main,
|
|
337
|
-
padding: '8px 14px',
|
|
338
|
-
display: 'flex',
|
|
339
|
-
flexDirection: 'column',
|
|
340
|
-
alignItems: 'flex-start',
|
|
341
|
-
gap: '2px',
|
|
342
|
-
width: '100%',
|
|
343
|
-
}}
|
|
344
|
-
>
|
|
377
|
+
<li key={optionKey} {...restLiProps} style={liStyle}>
|
|
378
|
+
{/* Main value - both variants */}
|
|
345
379
|
<Typography
|
|
346
380
|
fontvariant="merriparagraph"
|
|
347
381
|
text={option.value.replace(/_/g, ' ')}
|
|
348
382
|
fontcolor={black.main}
|
|
349
383
|
sx={{
|
|
350
384
|
fontSize: '14px',
|
|
385
|
+
fontWeight: variant === 'complex' ? '500' : 'normal',
|
|
351
386
|
lineHeight: '20px',
|
|
352
387
|
width: '100%',
|
|
353
388
|
textAlign: 'left',
|
|
354
389
|
}}
|
|
355
390
|
/>
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
391
|
+
|
|
392
|
+
{/* For simple variant - show attribute1 and attribute2 on one line */}
|
|
393
|
+
{variant === 'simple' &&
|
|
394
|
+
(option.attribute1 || option.attribute2) && (
|
|
395
|
+
<Typography
|
|
396
|
+
fontvariant="merriparagraph"
|
|
397
|
+
text={[option.attribute1, option.attribute2]
|
|
398
|
+
.filter(Boolean)
|
|
399
|
+
.join(' | ')}
|
|
400
|
+
fontcolor="rgba(0, 0, 0, 0.6)"
|
|
401
|
+
sx={{
|
|
402
|
+
fontSize: '12px',
|
|
403
|
+
lineHeight: '16px',
|
|
404
|
+
width: '100%',
|
|
405
|
+
textAlign: 'left',
|
|
406
|
+
}}
|
|
407
|
+
/>
|
|
408
|
+
)}
|
|
409
|
+
|
|
410
|
+
{/* For complex variant - show attributes on separate lines */}
|
|
411
|
+
{variant === 'complex' && (
|
|
412
|
+
<>
|
|
413
|
+
{/* First line of attributes */}
|
|
414
|
+
{(option.attribute1 || option.attribute2) && (
|
|
415
|
+
<Typography
|
|
416
|
+
fontvariant="merriparagraph"
|
|
417
|
+
text={[option.attribute1, option.attribute2]
|
|
418
|
+
.filter(Boolean)
|
|
419
|
+
.join(' | ')}
|
|
420
|
+
fontcolor="rgba(0, 0, 0, 0.6)"
|
|
421
|
+
sx={{
|
|
422
|
+
fontSize: '12px',
|
|
423
|
+
lineHeight: '16px',
|
|
424
|
+
width: '100%',
|
|
425
|
+
textAlign: 'left',
|
|
426
|
+
}}
|
|
427
|
+
/>
|
|
428
|
+
)}
|
|
429
|
+
|
|
430
|
+
{/* Second line of attributes */}
|
|
431
|
+
{(option.attribute3 || option.attribute4) && (
|
|
432
|
+
<Typography
|
|
433
|
+
fontvariant="merriparagraph"
|
|
434
|
+
text={[option.attribute3, option.attribute4]
|
|
435
|
+
.filter(Boolean)
|
|
436
|
+
.join(' | ')}
|
|
437
|
+
fontcolor="rgba(0, 0, 0, 0.6)"
|
|
438
|
+
sx={{
|
|
439
|
+
fontSize: '12px',
|
|
440
|
+
lineHeight: '16px',
|
|
441
|
+
width: '100%',
|
|
442
|
+
textAlign: 'left',
|
|
443
|
+
}}
|
|
444
|
+
/>
|
|
445
|
+
)}
|
|
446
|
+
|
|
447
|
+
{/* Third line of attributes */}
|
|
448
|
+
{(option.attribute5 || option.attribute6) && (
|
|
449
|
+
<Typography
|
|
450
|
+
fontvariant="merriparagraph"
|
|
451
|
+
text={[option.attribute5, option.attribute6]
|
|
452
|
+
.filter(Boolean)
|
|
453
|
+
.join(' | ')}
|
|
454
|
+
fontcolor="rgba(0, 0, 0, 0.6)"
|
|
455
|
+
sx={{
|
|
456
|
+
fontSize: '12px',
|
|
457
|
+
lineHeight: '16px',
|
|
458
|
+
width: '100%',
|
|
459
|
+
textAlign: 'left',
|
|
460
|
+
}}
|
|
461
|
+
/>
|
|
462
|
+
)}
|
|
463
|
+
</>
|
|
370
464
|
)}
|
|
371
465
|
</li>
|
|
372
466
|
)
|
|
@@ -16,6 +16,79 @@ const sampleOptions = [
|
|
|
16
16
|
{ value: 'broccoli', attribute1: 'Vegetable', attribute2: 'Green' },
|
|
17
17
|
]
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Sample options with the complex variant attributes
|
|
21
|
+
*/
|
|
22
|
+
const complexSampleOptions = [
|
|
23
|
+
{
|
|
24
|
+
value: 'apple',
|
|
25
|
+
attribute1: 'Fruit',
|
|
26
|
+
attribute2: 'Green or Red',
|
|
27
|
+
attribute3: 'High Fiber',
|
|
28
|
+
attribute4: 'Seasonal: Fall',
|
|
29
|
+
attribute5: 'Origin: Worldwide',
|
|
30
|
+
attribute6: 'Storage: Cool, Dry',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
value: 'banana',
|
|
34
|
+
attribute1: 'Fruit',
|
|
35
|
+
attribute2: 'Yellow',
|
|
36
|
+
attribute3: 'High Potassium',
|
|
37
|
+
attribute4: 'Year-round',
|
|
38
|
+
attribute5: 'Origin: Tropical',
|
|
39
|
+
attribute6: 'Storage: Room Temp',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
value: 'carrot',
|
|
43
|
+
attribute1: 'Vegetable',
|
|
44
|
+
attribute2: 'Orange',
|
|
45
|
+
attribute3: 'High Vitamin A',
|
|
46
|
+
attribute4: 'Year-round',
|
|
47
|
+
attribute5: 'Origin: Middle East',
|
|
48
|
+
attribute6: 'Storage: Refrigerated',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
value: 'potato',
|
|
52
|
+
attribute1: 'Vegetable',
|
|
53
|
+
attribute2: 'Brown',
|
|
54
|
+
attribute3: 'High Starch',
|
|
55
|
+
attribute4: 'Year-round',
|
|
56
|
+
attribute5: 'Origin: South America',
|
|
57
|
+
attribute6: 'Storage: Dark, Cool',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
value: 'avocado',
|
|
61
|
+
attribute1: 'Fruit',
|
|
62
|
+
attribute2: 'Green',
|
|
63
|
+
attribute3: 'Healthy Fats',
|
|
64
|
+
attribute4: 'Seasonal: Spring',
|
|
65
|
+
attribute5: 'Origin: Mexico',
|
|
66
|
+
attribute6: 'Storage: Room Temp',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
value: 'broccoli',
|
|
70
|
+
attribute1: 'Vegetable',
|
|
71
|
+
attribute2: 'Green',
|
|
72
|
+
attribute3: 'High Vitamin K',
|
|
73
|
+
attribute4: 'Seasonal: Winter',
|
|
74
|
+
attribute5: 'Origin: Mediterranean',
|
|
75
|
+
attribute6: 'Storage: Refrigerated',
|
|
76
|
+
},
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Helper function to safely access dropdown options in tests
|
|
81
|
+
*/
|
|
82
|
+
const getDropdownOptions = () => {
|
|
83
|
+
const listboxElement = document.querySelector('[role="listbox"]')
|
|
84
|
+
if (listboxElement && listboxElement instanceof HTMLElement) {
|
|
85
|
+
const listbox = within(listboxElement)
|
|
86
|
+
return listbox.getAllByRole('option')
|
|
87
|
+
}
|
|
88
|
+
console.log('Listbox not found')
|
|
89
|
+
return []
|
|
90
|
+
}
|
|
91
|
+
|
|
19
92
|
/**
|
|
20
93
|
* Storybook metadata
|
|
21
94
|
*/
|
|
@@ -34,6 +107,11 @@ const meta: Meta<typeof SearchableDropdown> = {
|
|
|
34
107
|
control: 'select',
|
|
35
108
|
options: ['onNotch', 'aboveNotch'],
|
|
36
109
|
},
|
|
110
|
+
variant: {
|
|
111
|
+
control: 'select',
|
|
112
|
+
options: ['simple', 'complex'],
|
|
113
|
+
description: 'Dropdown display variant',
|
|
114
|
+
},
|
|
37
115
|
},
|
|
38
116
|
}
|
|
39
117
|
export default meta
|
|
@@ -49,12 +127,15 @@ export const Basic: Story = {
|
|
|
49
127
|
label: 'Basic SearchableDropdown',
|
|
50
128
|
options: sampleOptions,
|
|
51
129
|
placeholder: 'Start typing...',
|
|
130
|
+
variant: 'simple',
|
|
52
131
|
},
|
|
53
132
|
play: async ({ canvasElement }) => {
|
|
54
133
|
const canvas = within(canvasElement)
|
|
55
134
|
|
|
56
|
-
// Check for the label
|
|
57
|
-
expect(
|
|
135
|
+
// Check for the label using a more specific query
|
|
136
|
+
expect(
|
|
137
|
+
canvas.getByRole('combobox', { name: 'Basic SearchableDropdown' })
|
|
138
|
+
).toBeInTheDocument()
|
|
58
139
|
|
|
59
140
|
// Click into the input
|
|
60
141
|
const input = canvas.getByRole('combobox')
|
|
@@ -62,8 +143,15 @@ export const Basic: Story = {
|
|
|
62
143
|
|
|
63
144
|
// Type a partial match
|
|
64
145
|
await userEvent.type(input, 'car')
|
|
65
|
-
|
|
66
|
-
|
|
146
|
+
|
|
147
|
+
// Get dropdown options
|
|
148
|
+
const listboxItems = getDropdownOptions()
|
|
149
|
+
|
|
150
|
+
// Check if any option contains "carrot"
|
|
151
|
+
const hasCarrot = listboxItems.some(
|
|
152
|
+
item => item.textContent && item.textContent.includes('carrot')
|
|
153
|
+
)
|
|
154
|
+
expect(hasCarrot).toBe(true)
|
|
67
155
|
},
|
|
68
156
|
}
|
|
69
157
|
|
|
@@ -76,22 +164,23 @@ export const WithDefaultValue: Story = {
|
|
|
76
164
|
label: 'Dropdown with Default Value',
|
|
77
165
|
options: sampleOptions,
|
|
78
166
|
defaultValue: 'banana',
|
|
167
|
+
variant: 'simple',
|
|
79
168
|
},
|
|
80
169
|
play: ({ canvasElement }) => {
|
|
81
170
|
const canvas = within(canvasElement)
|
|
82
|
-
// Expect the combobox to show the default item
|
|
171
|
+
// Expect the combobox to show the default item with first letter capitalized
|
|
83
172
|
const input = canvas.getByRole('combobox')
|
|
84
|
-
expect(input).toHaveValue('
|
|
173
|
+
expect(input).toHaveValue('Banana')
|
|
85
174
|
},
|
|
86
175
|
}
|
|
87
176
|
|
|
88
177
|
/**
|
|
89
|
-
* 3)
|
|
178
|
+
* 3) Simple Variant with Attributes
|
|
90
179
|
* Uses userEvent => keep `async`.
|
|
91
180
|
*/
|
|
92
|
-
export const
|
|
181
|
+
export const SimpleVariant: Story = {
|
|
93
182
|
args: {
|
|
94
|
-
label: '
|
|
183
|
+
label: 'Simple Variant',
|
|
95
184
|
options: [
|
|
96
185
|
{
|
|
97
186
|
value: 'item1',
|
|
@@ -110,20 +199,151 @@ export const ComplexAttributes: Story = {
|
|
|
110
199
|
},
|
|
111
200
|
],
|
|
112
201
|
placeholder: 'Search items...',
|
|
202
|
+
variant: 'simple',
|
|
203
|
+
},
|
|
204
|
+
play: async ({ canvasElement }) => {
|
|
205
|
+
const canvas = within(canvasElement)
|
|
206
|
+
// Open the dropdown
|
|
207
|
+
const input = canvas.getByRole('combobox')
|
|
208
|
+
await userEvent.click(input)
|
|
209
|
+
|
|
210
|
+
// Get dropdown options
|
|
211
|
+
const listboxItems = getDropdownOptions()
|
|
212
|
+
|
|
213
|
+
// Check if the items we're looking for exist
|
|
214
|
+
const hasItem1 = listboxItems.some(
|
|
215
|
+
item => item.textContent && item.textContent.includes('item1')
|
|
216
|
+
)
|
|
217
|
+
const hasItem3 = listboxItems.some(
|
|
218
|
+
item => item.textContent && item.textContent.includes('item3')
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
expect(hasItem1).toBe(true)
|
|
222
|
+
expect(hasItem3).toBe(true)
|
|
223
|
+
},
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 4) Complex Variant with Additional Attributes
|
|
228
|
+
* Uses userEvent => keep `async`.
|
|
229
|
+
*/
|
|
230
|
+
export const ComplexVariant: Story = {
|
|
231
|
+
args: {
|
|
232
|
+
label: 'Complex Variant',
|
|
233
|
+
options: [
|
|
234
|
+
{
|
|
235
|
+
value: 'item1',
|
|
236
|
+
attribute1: 'Primary attribute #1',
|
|
237
|
+
attribute2: 'Secondary attribute #1',
|
|
238
|
+
attribute3: 'Tertiary attribute #1',
|
|
239
|
+
attribute4: 'Additional info #1',
|
|
240
|
+
attribute5: 'Extended data #1',
|
|
241
|
+
attribute6: 'Final info #1',
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
value: 'item2',
|
|
245
|
+
attribute1: 'Primary attribute #2',
|
|
246
|
+
attribute2: 'Secondary attribute #2',
|
|
247
|
+
attribute3: 'Tertiary attribute #2',
|
|
248
|
+
attribute4: 'Additional info #2',
|
|
249
|
+
attribute5: 'Extended data #2',
|
|
250
|
+
attribute6: 'Final info #2',
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
value: 'item3',
|
|
254
|
+
attribute1: 'Primary attribute #3',
|
|
255
|
+
attribute2: 'Secondary attribute #3',
|
|
256
|
+
attribute3: 'Tertiary attribute #3',
|
|
257
|
+
attribute4: 'Additional info #3',
|
|
258
|
+
attribute5: 'Extended data #3',
|
|
259
|
+
attribute6: 'Final info #3',
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
placeholder: 'Search complex items...',
|
|
263
|
+
variant: 'complex',
|
|
113
264
|
},
|
|
114
265
|
play: async ({ canvasElement }) => {
|
|
115
266
|
const canvas = within(canvasElement)
|
|
116
267
|
// Open the dropdown
|
|
117
268
|
const input = canvas.getByRole('combobox')
|
|
118
269
|
await userEvent.click(input)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
270
|
+
|
|
271
|
+
// Get dropdown options
|
|
272
|
+
const listboxItems = getDropdownOptions()
|
|
273
|
+
|
|
274
|
+
// Check if the items and their attributes are present in any of the option items
|
|
275
|
+
const hasItem1 = listboxItems.some(
|
|
276
|
+
item => item.textContent && item.textContent.includes('item1')
|
|
277
|
+
)
|
|
278
|
+
const hasAttributes = listboxItems.some(
|
|
279
|
+
item =>
|
|
280
|
+
item.textContent &&
|
|
281
|
+
item.textContent.includes('Primary attribute') &&
|
|
282
|
+
item.textContent.includes('Secondary attribute')
|
|
283
|
+
)
|
|
284
|
+
const hasExtendedAttributes = listboxItems.some(
|
|
285
|
+
item =>
|
|
286
|
+
item.textContent &&
|
|
287
|
+
item.textContent.includes('Extended data') &&
|
|
288
|
+
item.textContent.includes('Final info')
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
expect(hasItem1).toBe(true)
|
|
292
|
+
expect(hasAttributes).toBe(true)
|
|
293
|
+
expect(hasExtendedAttributes).toBe(true)
|
|
122
294
|
},
|
|
123
295
|
}
|
|
124
296
|
|
|
125
297
|
/**
|
|
126
|
-
*
|
|
298
|
+
* 5) Complex Data Example
|
|
299
|
+
* Uses userEvent => keep `async`.
|
|
300
|
+
*/
|
|
301
|
+
export const ComplexDataExample: Story = {
|
|
302
|
+
args: {
|
|
303
|
+
label: 'Food Items',
|
|
304
|
+
options: complexSampleOptions,
|
|
305
|
+
placeholder: 'Search foods...',
|
|
306
|
+
variant: 'complex',
|
|
307
|
+
},
|
|
308
|
+
play: async ({ canvasElement }) => {
|
|
309
|
+
const canvas = within(canvasElement)
|
|
310
|
+
// Open the dropdown
|
|
311
|
+
const input = canvas.getByRole('combobox')
|
|
312
|
+
await userEvent.click(input)
|
|
313
|
+
await userEvent.type(input, 'a')
|
|
314
|
+
|
|
315
|
+
// Get dropdown options
|
|
316
|
+
const listboxItems = getDropdownOptions()
|
|
317
|
+
|
|
318
|
+
// Check if avocado and its attributes are present
|
|
319
|
+
const hasAvocado = listboxItems.some(
|
|
320
|
+
item => item.textContent && item.textContent.includes('avocado')
|
|
321
|
+
)
|
|
322
|
+
const hasFruitGreen = listboxItems.some(
|
|
323
|
+
item =>
|
|
324
|
+
item.textContent &&
|
|
325
|
+
item.textContent.includes('Fruit') &&
|
|
326
|
+
item.textContent.includes('Green')
|
|
327
|
+
)
|
|
328
|
+
const hasHealthyFats = listboxItems.some(
|
|
329
|
+
item => item.textContent && item.textContent.includes('Healthy Fats')
|
|
330
|
+
)
|
|
331
|
+
const hasOriginStorage = listboxItems.some(
|
|
332
|
+
item =>
|
|
333
|
+
item.textContent &&
|
|
334
|
+
(item.textContent.includes('Origin: Mexico') ||
|
|
335
|
+
item.textContent.includes('Storage: Room Temp'))
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
expect(hasAvocado).toBe(true)
|
|
339
|
+
expect(hasFruitGreen).toBe(true)
|
|
340
|
+
expect(hasHealthyFats).toBe(true)
|
|
341
|
+
expect(hasOriginStorage).toBe(true)
|
|
342
|
+
},
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* 6) Error State
|
|
127
347
|
* No user interactions => remove `async`.
|
|
128
348
|
*/
|
|
129
349
|
export const ErrorState: Story = {
|
|
@@ -132,6 +352,7 @@ export const ErrorState: Story = {
|
|
|
132
352
|
options: sampleOptions,
|
|
133
353
|
error: true,
|
|
134
354
|
helperText: 'Something went wrong!',
|
|
355
|
+
variant: 'simple',
|
|
135
356
|
},
|
|
136
357
|
play: ({ canvasElement }) => {
|
|
137
358
|
const canvas = within(canvasElement)
|
|
@@ -141,7 +362,7 @@ export const ErrorState: Story = {
|
|
|
141
362
|
}
|
|
142
363
|
|
|
143
364
|
/**
|
|
144
|
-
*
|
|
365
|
+
* 7) Required Dropdown
|
|
145
366
|
* No user interactions => remove `async`.
|
|
146
367
|
*/
|
|
147
368
|
export const RequiredField: Story = {
|
|
@@ -149,16 +370,19 @@ export const RequiredField: Story = {
|
|
|
149
370
|
label: 'Required Dropdown',
|
|
150
371
|
options: sampleOptions,
|
|
151
372
|
required: true,
|
|
373
|
+
variant: 'simple',
|
|
152
374
|
},
|
|
153
375
|
play: ({ canvasElement }) => {
|
|
154
376
|
const canvas = within(canvasElement)
|
|
155
|
-
// Check that label is present
|
|
156
|
-
expect(
|
|
377
|
+
// Check that label is present with a more specific query
|
|
378
|
+
expect(
|
|
379
|
+
canvas.getByRole('combobox', { name: 'Required Dropdown' })
|
|
380
|
+
).toBeInTheDocument()
|
|
157
381
|
},
|
|
158
382
|
}
|
|
159
383
|
|
|
160
384
|
/**
|
|
161
|
-
*
|
|
385
|
+
* 8) Custom Colors
|
|
162
386
|
* Uses userEvent => keep `async`.
|
|
163
387
|
*/
|
|
164
388
|
export const CustomColors: Story = {
|
|
@@ -174,6 +398,35 @@ export const CustomColors: Story = {
|
|
|
174
398
|
shrunklabelposition: 'onNotch',
|
|
175
399
|
placeholdercolor: '#42a5f5',
|
|
176
400
|
placeholder: 'Enter something...',
|
|
401
|
+
variant: 'simple',
|
|
402
|
+
},
|
|
403
|
+
play: async ({ canvasElement }) => {
|
|
404
|
+
const canvas = within(canvasElement)
|
|
405
|
+
// Interact just to ensure no errors
|
|
406
|
+
const input = canvas.getByRole('combobox')
|
|
407
|
+
await userEvent.click(input)
|
|
408
|
+
expect(input).toBeInTheDocument()
|
|
409
|
+
},
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 9) Custom Colors - Complex Variant
|
|
414
|
+
* Uses userEvent => keep `async`.
|
|
415
|
+
*/
|
|
416
|
+
export const CustomColorsComplex: Story = {
|
|
417
|
+
args: {
|
|
418
|
+
label: 'Custom Colors - Complex',
|
|
419
|
+
options: complexSampleOptions,
|
|
420
|
+
backgroundcolor: '#f0f8ff',
|
|
421
|
+
outlinecolor: '#ff5722',
|
|
422
|
+
fontcolor: '#4caf50',
|
|
423
|
+
inputfontcolor: '#e91e63',
|
|
424
|
+
shrunkfontcolor: '#673ab7',
|
|
425
|
+
unshrunkfontcolor: '#9c27b0',
|
|
426
|
+
shrunklabelposition: 'onNotch',
|
|
427
|
+
placeholdercolor: '#42a5f5',
|
|
428
|
+
placeholder: 'Enter something...',
|
|
429
|
+
variant: 'complex',
|
|
177
430
|
},
|
|
178
431
|
play: async ({ canvasElement }) => {
|
|
179
432
|
const canvas = within(canvasElement)
|
|
@@ -185,7 +438,7 @@ export const CustomColors: Story = {
|
|
|
185
438
|
}
|
|
186
439
|
|
|
187
440
|
/**
|
|
188
|
-
*
|
|
441
|
+
* 10) Searching & Selecting
|
|
189
442
|
* Uses userEvent => keep `async`.
|
|
190
443
|
*/
|
|
191
444
|
export const SearchAndSelect: Story = {
|
|
@@ -193,6 +446,7 @@ export const SearchAndSelect: Story = {
|
|
|
193
446
|
label: 'Search & Select',
|
|
194
447
|
options: sampleOptions,
|
|
195
448
|
placeholder: 'Find an item...',
|
|
449
|
+
variant: 'simple',
|
|
196
450
|
},
|
|
197
451
|
play: async ({ canvasElement }) => {
|
|
198
452
|
const canvas = within(canvasElement)
|
|
@@ -202,20 +456,28 @@ export const SearchAndSelect: Story = {
|
|
|
202
456
|
await userEvent.click(input)
|
|
203
457
|
await userEvent.type(input, 'avo')
|
|
204
458
|
|
|
205
|
-
//
|
|
206
|
-
const
|
|
207
|
-
expect(avocadoOption).toBeInTheDocument()
|
|
459
|
+
// Get dropdown options
|
|
460
|
+
const listboxItems = getDropdownOptions()
|
|
208
461
|
|
|
209
|
-
//
|
|
210
|
-
|
|
462
|
+
// Find the avocado option
|
|
463
|
+
const avocadoOption = listboxItems.find(
|
|
464
|
+
item => item.textContent && item.textContent.includes('avocado')
|
|
465
|
+
)
|
|
211
466
|
|
|
212
|
-
|
|
213
|
-
|
|
467
|
+
expect(avocadoOption).toBeTruthy()
|
|
468
|
+
|
|
469
|
+
// Click the option if found
|
|
470
|
+
if (avocadoOption) {
|
|
471
|
+
await userEvent.click(avocadoOption)
|
|
472
|
+
|
|
473
|
+
// Now the combobox value should be "Avocado" (with capitalization)
|
|
474
|
+
expect(input).toHaveValue('Avocado')
|
|
475
|
+
}
|
|
214
476
|
},
|
|
215
477
|
}
|
|
216
478
|
|
|
217
479
|
/**
|
|
218
|
-
*
|
|
480
|
+
* 11) No Options scenario
|
|
219
481
|
* Uses userEvent => keep `async`.
|
|
220
482
|
*/
|
|
221
483
|
export const NoOptions: Story = {
|
|
@@ -223,6 +485,7 @@ export const NoOptions: Story = {
|
|
|
223
485
|
label: 'Empty Dropdown',
|
|
224
486
|
options: [],
|
|
225
487
|
placeholder: 'No items available...',
|
|
488
|
+
variant: 'simple',
|
|
226
489
|
},
|
|
227
490
|
play: async ({ canvasElement }) => {
|
|
228
491
|
const canvas = within(canvasElement)
|
|
@@ -233,3 +496,87 @@ export const NoOptions: Story = {
|
|
|
233
496
|
expect(canvas.queryByText('apple')).not.toBeInTheDocument()
|
|
234
497
|
},
|
|
235
498
|
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* 12) Disabled State
|
|
502
|
+
* No user interactions => remove `async`.
|
|
503
|
+
*/
|
|
504
|
+
export const DisabledState: Story = {
|
|
505
|
+
args: {
|
|
506
|
+
label: 'Disabled Dropdown',
|
|
507
|
+
options: sampleOptions,
|
|
508
|
+
placeholder: 'Cannot select',
|
|
509
|
+
disabled: true,
|
|
510
|
+
variant: 'simple',
|
|
511
|
+
},
|
|
512
|
+
play: ({ canvasElement }) => {
|
|
513
|
+
const canvas = within(canvasElement)
|
|
514
|
+
const input = canvas.getByRole('combobox')
|
|
515
|
+
expect(input).toBeDisabled()
|
|
516
|
+
},
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* 13) Complex Variant Disabled
|
|
521
|
+
* No user interactions => remove `async`.
|
|
522
|
+
*/
|
|
523
|
+
export const ComplexVariantDisabled: Story = {
|
|
524
|
+
args: {
|
|
525
|
+
label: 'Complex Variant Disabled',
|
|
526
|
+
options: complexSampleOptions,
|
|
527
|
+
placeholder: 'Cannot select',
|
|
528
|
+
disabled: true,
|
|
529
|
+
variant: 'complex',
|
|
530
|
+
},
|
|
531
|
+
play: ({ canvasElement }) => {
|
|
532
|
+
const canvas = within(canvasElement)
|
|
533
|
+
const input = canvas.getByRole('combobox')
|
|
534
|
+
expect(input).toBeDisabled()
|
|
535
|
+
},
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* 14) All Attributes Display
|
|
540
|
+
* Uses userEvent => keep `async`.
|
|
541
|
+
*/
|
|
542
|
+
export const AllAttributesDisplay: Story = {
|
|
543
|
+
args: {
|
|
544
|
+
label: 'All Attributes Display',
|
|
545
|
+
options: [
|
|
546
|
+
{
|
|
547
|
+
value: 'complete item',
|
|
548
|
+
attribute1: 'First level',
|
|
549
|
+
attribute2: 'Second level',
|
|
550
|
+
attribute3: 'Third level',
|
|
551
|
+
attribute4: 'Fourth level',
|
|
552
|
+
attribute5: 'Fifth level',
|
|
553
|
+
attribute6: 'Sixth level',
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
placeholder: 'View all attributes...',
|
|
557
|
+
variant: 'complex',
|
|
558
|
+
},
|
|
559
|
+
play: async ({ canvasElement }) => {
|
|
560
|
+
const canvas = within(canvasElement)
|
|
561
|
+
// Open the dropdown
|
|
562
|
+
const input = canvas.getByRole('combobox')
|
|
563
|
+
await userEvent.click(input)
|
|
564
|
+
|
|
565
|
+
// Get dropdown options
|
|
566
|
+
const listboxItems = getDropdownOptions()
|
|
567
|
+
|
|
568
|
+
// Check if all attribute levels are displayed
|
|
569
|
+
const hasAllLevels = listboxItems.some(
|
|
570
|
+
item =>
|
|
571
|
+
item.textContent &&
|
|
572
|
+
item.textContent.includes('First level') &&
|
|
573
|
+
item.textContent.includes('Second level') &&
|
|
574
|
+
item.textContent.includes('Third level') &&
|
|
575
|
+
item.textContent.includes('Fourth level') &&
|
|
576
|
+
item.textContent.includes('Fifth level') &&
|
|
577
|
+
item.textContent.includes('Sixth level')
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
expect(hasAllLevels).toBe(true)
|
|
581
|
+
},
|
|
582
|
+
}
|
|
@@ -48,7 +48,6 @@ const CustomToolbar: FC<CustomToolbarProps> = ({
|
|
|
48
48
|
flexWrap: 'wrap',
|
|
49
49
|
gap: 2,
|
|
50
50
|
width: '100%',
|
|
51
|
-
mb: 2,
|
|
52
51
|
}}
|
|
53
52
|
>
|
|
54
53
|
{/* Left half: Buttons + Searchbar */}
|
|
@@ -92,7 +91,6 @@ const CustomToolbar: FC<CustomToolbarProps> = ({
|
|
|
92
91
|
flexWrap: 'wrap',
|
|
93
92
|
gap: 2,
|
|
94
93
|
width: '100%',
|
|
95
|
-
mb: 2,
|
|
96
94
|
}}
|
|
97
95
|
>
|
|
98
96
|
{/* Left half: Buttons */}
|
|
@@ -31,7 +31,7 @@ const LeftCenter: FC<Partial<SearchbarProps>> = props => {
|
|
|
31
31
|
height: '55px',
|
|
32
32
|
}}
|
|
33
33
|
>
|
|
34
|
-
<Box sx={{ marginBottom: '
|
|
34
|
+
<Box sx={{ marginBottom: '7px' }}>
|
|
35
35
|
<Searchbar
|
|
36
36
|
shrunklabelposition={shrunklabelposition}
|
|
37
37
|
shrunkfontcolor={shrunkfontcolor}
|
package/src/index.ts
CHANGED
|
@@ -57,14 +57,14 @@ import PasswordField, { PasswordFieldProps } from './components/Field/Password'
|
|
|
57
57
|
import PhoneNumberField from './components/Field/PhoneNumber'
|
|
58
58
|
import Searchbar, { SearchbarProps } from './components/Field/Search'
|
|
59
59
|
import TextField, { TextFieldProps } from './components/Field/Text'
|
|
60
|
+
import SearchableDropdown, {
|
|
61
|
+
SearchableDropdownProps,
|
|
62
|
+
DropdownOption,
|
|
63
|
+
} from './components/Field/Dropdown/Searchable'
|
|
60
64
|
|
|
61
65
|
// Add FormDataGrid import
|
|
62
66
|
import FormDataGrid from './components/Form/DataGrid'
|
|
63
67
|
import type { FormDataGridProps } from './components/Form/DataGrid'
|
|
64
|
-
import type {
|
|
65
|
-
SearchableDropdownProps,
|
|
66
|
-
DropdownOption,
|
|
67
|
-
} from './components/Field/Dropdown/Searchable'
|
|
68
68
|
|
|
69
69
|
// Animations
|
|
70
70
|
import { Animation } from './components/Content/Structure/animations'
|
|
@@ -199,6 +199,7 @@ export type { RawCustomer }
|
|
|
199
199
|
/* Named Type Exports */
|
|
200
200
|
/* -------------------------------------------------------------------------- */
|
|
201
201
|
|
|
202
|
+
export { SearchableDropdown }
|
|
202
203
|
// 1) Form DataGrid
|
|
203
204
|
export type { FormDataGridProps }
|
|
204
205
|
export type { CustomDialogProps }
|