@butternutbox/pawprint-native 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +32 -0
- package/dist/index.cjs +111 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +111 -98
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
- package/src/components/molecules/DatePicker/DatePicker.tsx +17 -6
- package/src/components/molecules/Drawer/DrawerContent.tsx +1 -2
- package/src/components/molecules/SelectField/SelectField.stories.tsx +13 -0
- package/src/components/molecules/SelectField/SelectField.test.tsx +41 -0
- package/src/components/molecules/SelectField/SelectField.tsx +37 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@butternutbox/pawprint-native",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "ButternutBox Pawprint Design System - React Native Components",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -54,10 +54,10 @@
|
|
|
54
54
|
}
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@butternutbox/pawprint-icons": "
|
|
58
|
-
"@butternutbox/pawprint-illustrations": "
|
|
59
|
-
"@repo/eslint-config": "
|
|
60
|
-
"@repo/typescript-config": "
|
|
57
|
+
"@butternutbox/pawprint-icons": "1.8.1",
|
|
58
|
+
"@butternutbox/pawprint-illustrations": "3.1.1",
|
|
59
|
+
"@repo/eslint-config": "0.0.0",
|
|
60
|
+
"@repo/typescript-config": "0.0.0",
|
|
61
61
|
"@testing-library/dom": "^10.4.1",
|
|
62
62
|
"@testing-library/jest-dom": "^6.6.3",
|
|
63
63
|
"@testing-library/react": "^16.3.0",
|
|
@@ -80,4 +80,4 @@
|
|
|
80
80
|
"publishConfig": {
|
|
81
81
|
"access": "public"
|
|
82
82
|
}
|
|
83
|
-
}
|
|
83
|
+
}
|
|
@@ -121,6 +121,11 @@ type DatePickerOwnProps = {
|
|
|
121
121
|
getMonthLabel?: (year: number, month: number, locale?: Locale) => string
|
|
122
122
|
weekDayLabels?: [string, string, string, string, string, string, string]
|
|
123
123
|
getDateState?: (date: Date) => DateItemState
|
|
124
|
+
getDateAccessibilityLabel?: (
|
|
125
|
+
date: Date,
|
|
126
|
+
state: DateItemState,
|
|
127
|
+
monthLabel: string
|
|
128
|
+
) => string
|
|
124
129
|
isDateDisabled?: (date: Date) => boolean
|
|
125
130
|
minDate?: Date
|
|
126
131
|
maxDate?: Date
|
|
@@ -229,6 +234,8 @@ const StyledIndicator = styled(View)<{
|
|
|
229
234
|
* @param {[string,string,string,string,string,string,string]} [weekDayLabels] - Day-column labels Mon→Sun.
|
|
230
235
|
* @param {(date: Date) => DateItemState} [getDateState] - Returns the visual state for a date.
|
|
231
236
|
* Defaults to: past="previous", today="current", future="default".
|
|
237
|
+
* @param {(date: Date, state: DateItemState, monthLabel: string) => string} [getDateAccessibilityLabel] - Returns the screen-reader label for a date,
|
|
238
|
+
* e.g. to announce delivery states. Defaults to "{day} {monthLabel}".
|
|
232
239
|
* @param {(date: Date) => boolean} [isDateDisabled] - Returns true to disable a date.
|
|
233
240
|
* @param {Date} [minDate] - Dates before this (exclusive of time) will be disabled.
|
|
234
241
|
* @param {Date} [maxDate] - Dates after this (exclusive of time) will be disabled.
|
|
@@ -270,6 +277,7 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
|
|
|
270
277
|
getMonthLabel,
|
|
271
278
|
weekDayLabels = DEFAULT_WEEK_LABELS,
|
|
272
279
|
getDateState,
|
|
280
|
+
getDateAccessibilityLabel,
|
|
273
281
|
isDateDisabled,
|
|
274
282
|
minDate,
|
|
275
283
|
maxDate,
|
|
@@ -470,6 +478,7 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
|
|
|
470
478
|
token={datePicker.month.typography}
|
|
471
479
|
align="center"
|
|
472
480
|
color={datePicker.month.colour.text.default}
|
|
481
|
+
accessibilityRole="header"
|
|
473
482
|
>
|
|
474
483
|
{resolvedMonthLabel}
|
|
475
484
|
</Typography>
|
|
@@ -493,11 +502,7 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
|
|
|
493
502
|
entering={MONTH_SLIDE_ENTERING[slideDir]}
|
|
494
503
|
exiting={MONTH_SLIDE_EXITING[slideDir]}
|
|
495
504
|
>
|
|
496
|
-
<StyledDatesGrid
|
|
497
|
-
rowGap={rowGap}
|
|
498
|
-
accessible
|
|
499
|
-
accessibilityLabel={resolvedMonthLabel}
|
|
500
|
-
>
|
|
505
|
+
<StyledDatesGrid rowGap={rowGap}>
|
|
501
506
|
{calendarRows.map((row, rowIdx) => (
|
|
502
507
|
<StyledDateRow key={rowIdx}>
|
|
503
508
|
{row.map((date, colIdx) =>
|
|
@@ -547,7 +552,13 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
|
|
|
547
552
|
disabled={isDisabled}
|
|
548
553
|
accessible
|
|
549
554
|
accessibilityRole="button"
|
|
550
|
-
accessibilityLabel={
|
|
555
|
+
accessibilityLabel={
|
|
556
|
+
getDateAccessibilityLabel?.(
|
|
557
|
+
date,
|
|
558
|
+
state,
|
|
559
|
+
resolvedMonthLabel
|
|
560
|
+
) ?? `${date.getDate()} ${resolvedMonthLabel}`
|
|
561
|
+
}
|
|
551
562
|
accessibilityState={{
|
|
552
563
|
selected: isSelected,
|
|
553
564
|
disabled: isDisabled
|
|
@@ -433,9 +433,8 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
|
|
|
433
433
|
panelShadowColor={modal.shadow.color}
|
|
434
434
|
panelShadowOffsetY={parseTokenValue(modal.shadow.offsetY)}
|
|
435
435
|
panelShadowBlur={parseTokenValue(modal.shadow.blur)}
|
|
436
|
-
accessible
|
|
437
|
-
accessibilityRole="none"
|
|
438
436
|
accessibilityViewIsModal
|
|
437
|
+
onAccessibilityEscape={closeDrawer}
|
|
439
438
|
{...props}
|
|
440
439
|
>
|
|
441
440
|
<DrawerBodyMaxContext.Provider value={bodyMax}>
|
|
@@ -151,6 +151,19 @@ export const WithOptionalText = () => (
|
|
|
151
151
|
</StoryWrapper>
|
|
152
152
|
)
|
|
153
153
|
|
|
154
|
+
export const NotSearchable = () => (
|
|
155
|
+
<StoryWrapper>
|
|
156
|
+
<Section title="Pick from the list only">
|
|
157
|
+
<SelectField
|
|
158
|
+
label="Country"
|
|
159
|
+
placeholder="Select a country"
|
|
160
|
+
searchable={false}
|
|
161
|
+
options={COUNTRIES}
|
|
162
|
+
/>
|
|
163
|
+
</Section>
|
|
164
|
+
</StoryWrapper>
|
|
165
|
+
)
|
|
166
|
+
|
|
154
167
|
export const Controlled = () => {
|
|
155
168
|
const [country, setCountry] = useState<string | null>("ca")
|
|
156
169
|
|
|
@@ -293,6 +293,47 @@ describe("SelectField", () => {
|
|
|
293
293
|
})
|
|
294
294
|
})
|
|
295
295
|
|
|
296
|
+
describe("when not searchable", () => {
|
|
297
|
+
it("can't be typed into", () => {
|
|
298
|
+
const input = renderField({ searchable: false })
|
|
299
|
+
|
|
300
|
+
expect(input).toBeDisabled()
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it("stays closed until the field is pressed", () => {
|
|
304
|
+
renderField({ searchable: false })
|
|
305
|
+
|
|
306
|
+
expect(screen.queryByText("United Kingdom")).not.toBeInTheDocument()
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it("shows every option when the field is pressed", () => {
|
|
310
|
+
const input = renderField({ searchable: false })
|
|
311
|
+
fireEvent.click(input)
|
|
312
|
+
|
|
313
|
+
COUNTRIES.forEach((option) => {
|
|
314
|
+
expect(screen.getByText(option.label)).toBeInTheDocument()
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
|
|
318
|
+
it("selects the pressed option and closes", () => {
|
|
319
|
+
const onValueChange = vi.fn()
|
|
320
|
+
const input = renderField({ searchable: false, onValueChange })
|
|
321
|
+
fireEvent.click(input)
|
|
322
|
+
fireEvent.click(screen.getByText("Canada"))
|
|
323
|
+
|
|
324
|
+
expect(onValueChange).toHaveBeenCalledWith("ca")
|
|
325
|
+
expect(input).toHaveValue("Canada")
|
|
326
|
+
expect(screen.queryByText("Australia")).not.toBeInTheDocument()
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
it("does not open when disabled", () => {
|
|
330
|
+
const input = renderField({ searchable: false, disabled: true })
|
|
331
|
+
fireEvent.click(input)
|
|
332
|
+
|
|
333
|
+
expect(screen.queryByText("United Kingdom")).not.toBeInTheDocument()
|
|
334
|
+
})
|
|
335
|
+
})
|
|
336
|
+
|
|
296
337
|
describe("controlled mode", () => {
|
|
297
338
|
it("shows the label for the controlled value", () => {
|
|
298
339
|
const input = renderField({ value: "au" })
|
|
@@ -2,6 +2,7 @@ import React from "react"
|
|
|
2
2
|
import {
|
|
3
3
|
Keyboard,
|
|
4
4
|
Pressable,
|
|
5
|
+
StyleSheet,
|
|
5
6
|
TextInput,
|
|
6
7
|
useWindowDimensions,
|
|
7
8
|
View,
|
|
@@ -40,6 +41,7 @@ type SelectFieldOwnProps = {
|
|
|
40
41
|
optionalText?: string
|
|
41
42
|
placeholder?: string
|
|
42
43
|
leadingIcon?: React.ReactNode
|
|
44
|
+
searchable?: boolean
|
|
43
45
|
noResultsText?: string
|
|
44
46
|
errorText?: string
|
|
45
47
|
onSearchQuery?: SelectOptionsResolver
|
|
@@ -73,6 +75,7 @@ export const SelectField = React.forwardRef<View, SelectFieldProps>(
|
|
|
73
75
|
optionalText,
|
|
74
76
|
placeholder,
|
|
75
77
|
leadingIcon,
|
|
78
|
+
searchable = true,
|
|
76
79
|
noResultsText = "No results found",
|
|
77
80
|
errorText = "Something went wrong",
|
|
78
81
|
onSearchQuery,
|
|
@@ -244,7 +247,7 @@ export const SelectField = React.forwardRef<View, SelectFieldProps>(
|
|
|
244
247
|
setInputText("")
|
|
245
248
|
setFilterQuery("")
|
|
246
249
|
commitValue(null)
|
|
247
|
-
inputRef.current?.focus()
|
|
250
|
+
if (searchable) inputRef.current?.focus()
|
|
248
251
|
}
|
|
249
252
|
|
|
250
253
|
const fieldState: InputState = error ? "error" : state
|
|
@@ -272,24 +275,34 @@ export const SelectField = React.forwardRef<View, SelectFieldProps>(
|
|
|
272
275
|
if (!isOpen || !anchorRect) return null
|
|
273
276
|
|
|
274
277
|
return (
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
278
|
+
<>
|
|
279
|
+
{!searchable && (
|
|
280
|
+
<Pressable
|
|
281
|
+
style={StyleSheet.absoluteFill}
|
|
282
|
+
onPress={() => setIsFocused(false)}
|
|
283
|
+
accessible={false}
|
|
284
|
+
/>
|
|
285
|
+
)}
|
|
286
|
+
<SelectFieldDropdown
|
|
287
|
+
anchor={anchorRect}
|
|
288
|
+
placement={placement.direction}
|
|
289
|
+
maxHeight={placement.maxHeight}
|
|
290
|
+
screenHeight={windowHeight}
|
|
291
|
+
accessibilityLiveRegion="polite"
|
|
292
|
+
>
|
|
293
|
+
<SelectFieldList
|
|
294
|
+
state={optionsState}
|
|
295
|
+
selectedValue={selectedValue}
|
|
296
|
+
onSelect={onOptionPress}
|
|
297
|
+
noResultsText={noResultsText}
|
|
298
|
+
errorText={errorText}
|
|
299
|
+
searchHintText={searchHintText}
|
|
300
|
+
/>
|
|
301
|
+
</SelectFieldDropdown>
|
|
302
|
+
</>
|
|
291
303
|
)
|
|
292
304
|
}, [
|
|
305
|
+
searchable,
|
|
293
306
|
isOpen,
|
|
294
307
|
anchorRect,
|
|
295
308
|
placement,
|
|
@@ -313,11 +326,14 @@ export const SelectField = React.forwardRef<View, SelectFieldProps>(
|
|
|
313
326
|
<InputField
|
|
314
327
|
ref={inputRef}
|
|
315
328
|
value={inputText}
|
|
316
|
-
onChangeText={handleChangeText}
|
|
317
|
-
onFocus={handleFocus}
|
|
318
|
-
onBlur={handleBlur}
|
|
329
|
+
onChangeText={searchable ? handleChangeText : undefined}
|
|
330
|
+
onFocus={searchable ? handleFocus : undefined}
|
|
331
|
+
onBlur={searchable ? handleBlur : undefined}
|
|
332
|
+
onPress={
|
|
333
|
+
!searchable && !disabled ? () => setIsFocused(true) : undefined
|
|
334
|
+
}
|
|
319
335
|
placeholder={placeholder}
|
|
320
|
-
editable={!disabled}
|
|
336
|
+
editable={searchable && !disabled}
|
|
321
337
|
state={fieldState}
|
|
322
338
|
hideStateIcons={Boolean(clearAction)}
|
|
323
339
|
leadingIcon={leadingIcon}
|