jfs-components 0.0.77 → 0.0.79
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/CHANGELOG.md +28 -0
- package/lib/commonjs/components/Accordion/Accordion.js +55 -55
- package/lib/commonjs/components/ActionFooter/ActionFooter.js +48 -2
- package/lib/commonjs/components/Attached/Attached.js +144 -0
- package/lib/commonjs/components/Card/Card.js +25 -2
- package/lib/commonjs/components/Checkbox/Checkbox.js +21 -9
- package/lib/commonjs/components/DropdownInput/DropdownInput.js +30 -16
- package/lib/commonjs/components/ExpandableCheckbox/ExpandableCheckbox.js +167 -0
- package/lib/commonjs/components/FormField/FormField.js +14 -1
- package/lib/commonjs/components/FullscreenModal/FullscreenModal.js +353 -0
- package/lib/commonjs/components/ListItem/ListItem.js +46 -24
- package/lib/commonjs/components/MessageField/MessageField.js +318 -0
- package/lib/commonjs/components/NavArrow/NavArrow.js +58 -17
- package/lib/commonjs/components/PlanComparisonCard/PlanComparisonCard.js +328 -0
- package/lib/commonjs/components/Slot/Slot.js +73 -0
- package/lib/commonjs/components/Stepper/Step.js +47 -60
- package/lib/commonjs/components/Stepper/StepLabel.js +40 -10
- package/lib/commonjs/components/Stepper/Stepper.js +15 -17
- package/lib/commonjs/components/SuggestiveSearch/SuggestiveSearch.js +487 -0
- package/lib/commonjs/components/TextInput/TextInput.js +16 -1
- package/lib/commonjs/components/Title/Title.js +10 -2
- package/lib/commonjs/components/index.js +49 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Accordion/Accordion.js +56 -56
- package/lib/module/components/ActionFooter/ActionFooter.js +50 -4
- package/lib/module/components/Attached/Attached.js +139 -0
- package/lib/module/components/Card/Card.js +25 -2
- package/lib/module/components/Checkbox/Checkbox.js +22 -10
- package/lib/module/components/DropdownInput/DropdownInput.js +30 -16
- package/lib/module/components/ExpandableCheckbox/ExpandableCheckbox.js +161 -0
- package/lib/module/components/FormField/FormField.js +16 -3
- package/lib/module/components/FullscreenModal/FullscreenModal.js +348 -0
- package/lib/module/components/ListItem/ListItem.js +46 -24
- package/lib/module/components/MessageField/MessageField.js +313 -0
- package/lib/module/components/NavArrow/NavArrow.js +59 -18
- package/lib/module/components/PlanComparisonCard/PlanComparisonCard.js +322 -0
- package/lib/module/components/Slot/Slot.js +68 -0
- package/lib/module/components/Stepper/Step.js +48 -61
- package/lib/module/components/Stepper/StepLabel.js +40 -10
- package/lib/module/components/Stepper/Stepper.js +15 -17
- package/lib/module/components/SuggestiveSearch/SuggestiveSearch.js +481 -0
- package/lib/module/components/TextInput/TextInput.js +17 -2
- package/lib/module/components/Title/Title.js +10 -2
- package/lib/module/components/index.js +7 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Accordion/Accordion.d.ts +14 -20
- package/lib/typescript/src/components/Attached/Attached.d.ts +61 -0
- package/lib/typescript/src/components/Card/Card.d.ts +9 -2
- package/lib/typescript/src/components/ExpandableCheckbox/ExpandableCheckbox.d.ts +63 -0
- package/lib/typescript/src/components/FullscreenModal/FullscreenModal.d.ts +99 -0
- package/lib/typescript/src/components/ListItem/ListItem.d.ts +15 -5
- package/lib/typescript/src/components/MessageField/MessageField.d.ts +81 -0
- package/lib/typescript/src/components/NavArrow/NavArrow.d.ts +10 -5
- package/lib/typescript/src/components/PlanComparisonCard/PlanComparisonCard.d.ts +64 -0
- package/lib/typescript/src/components/Slot/Slot.d.ts +52 -0
- package/lib/typescript/src/components/Stepper/Step.d.ts +4 -1
- package/lib/typescript/src/components/Stepper/StepLabel.d.ts +4 -1
- package/lib/typescript/src/components/Stepper/Stepper.d.ts +3 -1
- package/lib/typescript/src/components/SuggestiveSearch/SuggestiveSearch.d.ts +123 -0
- package/lib/typescript/src/components/index.d.ts +10 -3
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Accordion/Accordion.tsx +113 -73
- package/src/components/ActionFooter/ActionFooter.tsx +56 -4
- package/src/components/Attached/Attached.tsx +181 -0
- package/src/components/Card/Card.tsx +28 -1
- package/src/components/Checkbox/Checkbox.tsx +22 -9
- package/src/components/DropdownInput/DropdownInput.tsx +67 -39
- package/src/components/ExpandableCheckbox/ExpandableCheckbox.tsx +237 -0
- package/src/components/FormField/FormField.tsx +19 -3
- package/src/components/FullscreenModal/FullscreenModal.tsx +414 -0
- package/src/components/ListItem/ListItem.tsx +55 -25
- package/src/components/MessageField/MessageField.tsx +543 -0
- package/src/components/NavArrow/NavArrow.tsx +81 -17
- package/src/components/PlanComparisonCard/PlanComparisonCard.tsx +426 -0
- package/src/components/Slot/Slot.tsx +91 -0
- package/src/components/Stepper/Step.tsx +52 -51
- package/src/components/Stepper/StepLabel.tsx +46 -9
- package/src/components/Stepper/Stepper.tsx +20 -15
- package/src/components/SuggestiveSearch/SuggestiveSearch.tsx +756 -0
- package/src/components/TextInput/TextInput.tsx +14 -1
- package/src/components/Title/Title.tsx +13 -2
- package/src/components/index.ts +10 -3
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type StyleProp, type TextInputProps as RNTextInputProps, type TextStyle, type ViewStyle } from 'react-native';
|
|
3
|
+
export type SuggestiveSearchOptionValue = string | number;
|
|
4
|
+
export type SuggestiveSearchOption = {
|
|
5
|
+
/** Stable, unique value used to identify the suggestion. */
|
|
6
|
+
value: SuggestiveSearchOptionValue;
|
|
7
|
+
/** Human-readable label shown in the suggestion list and the input. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Whether the suggestion is non-selectable. */
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Suggestions accept either a bare string (used as both value and label) or a
|
|
14
|
+
* full `{ value, label }` option object for richer data.
|
|
15
|
+
*/
|
|
16
|
+
export type SuggestiveSearchItem = string | SuggestiveSearchOption;
|
|
17
|
+
export type SuggestiveSearchProps = {
|
|
18
|
+
/** Label rendered above the input. */
|
|
19
|
+
label?: string;
|
|
20
|
+
/** Placeholder text shown when the query is empty. */
|
|
21
|
+
placeholder?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Suggestions to filter against the current query. May be bare strings or
|
|
24
|
+
* `{ value, label }` objects.
|
|
25
|
+
*/
|
|
26
|
+
items?: SuggestiveSearchItem[];
|
|
27
|
+
/**
|
|
28
|
+
* Current query text (controlled). When `undefined` the component manages
|
|
29
|
+
* its own query state internally.
|
|
30
|
+
*/
|
|
31
|
+
inputValue?: string;
|
|
32
|
+
/** Initial query text for uncontrolled mode. */
|
|
33
|
+
defaultInputValue?: string;
|
|
34
|
+
/** Called whenever the query text changes (typing or selection). */
|
|
35
|
+
onInputChange?: (text: string) => void;
|
|
36
|
+
/**
|
|
37
|
+
* Currently selected suggestion value (controlled). When `undefined` the
|
|
38
|
+
* component tracks the selection internally.
|
|
39
|
+
*/
|
|
40
|
+
value?: SuggestiveSearchOptionValue | null;
|
|
41
|
+
/** Initial selected value for uncontrolled mode. */
|
|
42
|
+
defaultValue?: SuggestiveSearchOptionValue | null;
|
|
43
|
+
/** Called when a suggestion is chosen. */
|
|
44
|
+
onValueChange?: (value: SuggestiveSearchOptionValue | null, option?: SuggestiveSearchOption) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Custom predicate deciding whether an option matches the current query.
|
|
47
|
+
* Defaults to a case-insensitive substring match on the label.
|
|
48
|
+
*/
|
|
49
|
+
filter?: (query: string, option: SuggestiveSearchOption) => boolean;
|
|
50
|
+
/**
|
|
51
|
+
* Minimum number of characters required before suggestions are shown.
|
|
52
|
+
* @default 1
|
|
53
|
+
*/
|
|
54
|
+
minChars?: number;
|
|
55
|
+
/** Caps the number of suggestions rendered. Defaults to no limit. */
|
|
56
|
+
maxResults?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Highlights the matched substring of each suggestion in bold.
|
|
59
|
+
* @default true
|
|
60
|
+
*/
|
|
61
|
+
highlightMatch?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Message shown when the query has matched no suggestions. When omitted,
|
|
64
|
+
* the dropdown simply stays hidden on an empty result set.
|
|
65
|
+
*/
|
|
66
|
+
emptyMessage?: string;
|
|
67
|
+
/** Custom renderer for a suggestion row (overrides the default label). */
|
|
68
|
+
renderItem?: (option: SuggestiveSearchOption, meta: {
|
|
69
|
+
query: string;
|
|
70
|
+
isSelected: boolean;
|
|
71
|
+
}) => React.ReactNode;
|
|
72
|
+
/** Controlled open state of the suggestion dropdown. */
|
|
73
|
+
open?: boolean;
|
|
74
|
+
/** Initial open state for uncontrolled mode. */
|
|
75
|
+
defaultOpen?: boolean;
|
|
76
|
+
/** Called whenever the open state changes. */
|
|
77
|
+
onOpenChange?: (open: boolean) => void;
|
|
78
|
+
/**
|
|
79
|
+
* Maximum height of the suggestion list before it becomes scrollable.
|
|
80
|
+
* @default 240
|
|
81
|
+
*/
|
|
82
|
+
menuMaxHeight?: number;
|
|
83
|
+
/**
|
|
84
|
+
* Vertical gap between the input and the suggestion dropdown.
|
|
85
|
+
* @default 6
|
|
86
|
+
*/
|
|
87
|
+
menuOffset?: number;
|
|
88
|
+
/** Renders a required asterisk next to the label. */
|
|
89
|
+
isRequired?: boolean;
|
|
90
|
+
/** Disables interaction and dims the field. */
|
|
91
|
+
isDisabled?: boolean;
|
|
92
|
+
/** Marks the field as invalid and shows `errorMessage`. */
|
|
93
|
+
isInvalid?: boolean;
|
|
94
|
+
/** Renders the field as read-only (non-interactive, not dimmed). */
|
|
95
|
+
isReadOnly?: boolean;
|
|
96
|
+
/** Helper text displayed below the input. */
|
|
97
|
+
supportText?: string;
|
|
98
|
+
/** Replaces `supportText` when `isInvalid` is true. */
|
|
99
|
+
errorMessage?: string;
|
|
100
|
+
/** Modes for design token resolution. */
|
|
101
|
+
modes?: Record<string, any>;
|
|
102
|
+
/** Style overrides for the outermost wrapper. */
|
|
103
|
+
style?: StyleProp<ViewStyle>;
|
|
104
|
+
/** Style overrides for the input row. */
|
|
105
|
+
inputStyle?: StyleProp<ViewStyle>;
|
|
106
|
+
/** Style overrides for the input text. */
|
|
107
|
+
inputTextStyle?: StyleProp<TextStyle>;
|
|
108
|
+
/** Style overrides for the suggestion dropdown container. */
|
|
109
|
+
menuStyle?: StyleProp<ViewStyle>;
|
|
110
|
+
/** Accessibility label. Defaults to the visible label / placeholder. */
|
|
111
|
+
accessibilityLabel?: string;
|
|
112
|
+
/** Accessibility hint. */
|
|
113
|
+
accessibilityHint?: string;
|
|
114
|
+
/** Called when the input receives focus. */
|
|
115
|
+
onFocus?: RNTextInputProps['onFocus'];
|
|
116
|
+
/** Called when the input loses focus. */
|
|
117
|
+
onBlur?: RNTextInputProps['onBlur'];
|
|
118
|
+
/** Test identifier. */
|
|
119
|
+
testID?: string;
|
|
120
|
+
};
|
|
121
|
+
declare function SuggestiveSearch({ label, placeholder, items, inputValue, defaultInputValue, onInputChange, value, defaultValue, onValueChange, filter, minChars, maxResults, highlightMatch, emptyMessage, renderItem, open, defaultOpen, onOpenChange, menuMaxHeight, menuOffset, isRequired, isDisabled, isInvalid, isReadOnly, supportText, errorMessage, modes: propModes, style, inputStyle, inputTextStyle, menuStyle, accessibilityLabel, accessibilityHint, onFocus, onBlur, testID, }: SuggestiveSearchProps): import("react/jsx-runtime").JSX.Element;
|
|
122
|
+
export default SuggestiveSearch;
|
|
123
|
+
//# sourceMappingURL=SuggestiveSearch.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { default as AccountCard, type AccountCardProps, type AccountCardState } from './AccountCard/AccountCard';
|
|
2
2
|
export { default as ActionFooter, type ActionFooterProps } from './ActionFooter/ActionFooter';
|
|
3
|
+
export { default as Attached, type AttachedProps, type AttachedPosition } from './Attached/Attached';
|
|
3
4
|
export { default as AppBar } from './AppBar/AppBar';
|
|
4
5
|
export { default as Avatar, type AvatarProps } from './Avatar/Avatar';
|
|
5
6
|
export { default as AvatarGroup } from './AvatarGroup/AvatarGroup';
|
|
@@ -24,9 +25,11 @@ export { default as Divider, type DividerProps, type DividerDirection } from './
|
|
|
24
25
|
export { default as Drawer } from './Drawer/Drawer';
|
|
25
26
|
export { default as Dropdown, DropdownItem, type DropdownProps, type DropdownItemProps } from './Dropdown/Dropdown';
|
|
26
27
|
export { default as DropdownInput, type DropdownInputProps, type DropdownInputOption, type DropdownInputOptionValue } from './DropdownInput/DropdownInput';
|
|
28
|
+
export { default as SuggestiveSearch, type SuggestiveSearchProps, type SuggestiveSearchOption, type SuggestiveSearchOptionValue, type SuggestiveSearchItem } from './SuggestiveSearch/SuggestiveSearch';
|
|
27
29
|
export { default as CardCTA, type CardCTAProps, type CardCTAType } from './CardCTA/CardCTA';
|
|
28
30
|
export { default as DebitCard, type DebitCardProps } from './DebitCard/DebitCard';
|
|
29
31
|
export { default as FilterBar } from './FilterBar/FilterBar';
|
|
32
|
+
export { default as FullscreenModal, type FullscreenModalProps } from './FullscreenModal/FullscreenModal';
|
|
30
33
|
export { default as Form, type FormProps } from './Form/Form';
|
|
31
34
|
export { useFormContext } from './Form/Form';
|
|
32
35
|
export { default as FormField, type FormFieldProps, type FormFieldType } from './FormField/FormField';
|
|
@@ -51,6 +54,7 @@ export { default as LottiePlayer, type LottiePlayerProps, type LottieAnimationSo
|
|
|
51
54
|
export { default as ListItem } from './ListItem/ListItem';
|
|
52
55
|
export { default as MediaCard, type MediaCardProps } from './MediaCard/MediaCard';
|
|
53
56
|
export { default as MerchantProfile, type MerchantProfileProps } from './MerchantProfile/MerchantProfile';
|
|
57
|
+
export { default as MessageField, type MessageFieldProps, type MessageFieldState } from './MessageField/MessageField';
|
|
54
58
|
export { default as MetricLegendItem, type MetricLegendItemProps } from './MetricLegendItem/MetricLegendItem';
|
|
55
59
|
export { default as MoneyValue } from './MoneyValue/MoneyValue';
|
|
56
60
|
export { default as NoteInput, type NoteInputProps } from './NoteInput/NoteInput';
|
|
@@ -60,9 +64,10 @@ export { default as Numpad, type NumpadProps, type NumpadKeyValue } from './Nump
|
|
|
60
64
|
export { default as Title, type TitleProps } from './Title/Title';
|
|
61
65
|
export { default as Screen, type ScreenProps } from './Screen/Screen';
|
|
62
66
|
export { default as Section } from './Section/Section';
|
|
63
|
-
export { default as
|
|
64
|
-
export {
|
|
65
|
-
export {
|
|
67
|
+
export { default as Slot, type SlotProps, type SlotLayoutDirection } from './Slot/Slot';
|
|
68
|
+
export { default as Stepper, type StepperProps } from './Stepper/Stepper';
|
|
69
|
+
export { Step, type StepProps, type StepStatus } from './Stepper/Step';
|
|
70
|
+
export { StepLabel, type StepLabelProps } from './Stepper/StepLabel';
|
|
66
71
|
export { default as TextInput } from './TextInput/TextInput';
|
|
67
72
|
export { default as StatusHero, type StatusHeroProps } from './StatusHero/StatusHero';
|
|
68
73
|
export { default as ThreadHero, type ThreadHeroProps } from './ThreadHero/ThreadHero';
|
|
@@ -74,6 +79,7 @@ export { default as UpiHandle } from './UpiHandle/UpiHandle';
|
|
|
74
79
|
export { default as VStack, type VStackProps } from './VStack/VStack';
|
|
75
80
|
export { default as ChipGroup, type ChipGroupProps } from './ChipGroup/ChipGroup';
|
|
76
81
|
export { default as EmptyState, type EmptyStateProps } from './EmptyState/EmptyState';
|
|
82
|
+
export { default as ExpandableCheckbox, type ExpandableCheckboxProps } from './ExpandableCheckbox/ExpandableCheckbox';
|
|
77
83
|
export { default as Accordion, type AccordionProps } from './Accordion/Accordion';
|
|
78
84
|
export { default as AccordionCheckbox, type AccordionCheckboxProps } from './AccordionCheckbox/AccordionCheckbox';
|
|
79
85
|
export { default as ActionTile, type ActionTileProps } from './ActionTile/ActionTile';
|
|
@@ -106,6 +112,7 @@ export { default as AmountInput, type AmountInputProps } from './AmountInput/Amo
|
|
|
106
112
|
export { default as PageHero, type PageHeroProps } from './PageHero/PageHero';
|
|
107
113
|
export { default as Popup, type PopupProps, type PopupRef } from './Popup/Popup';
|
|
108
114
|
export { default as PortfolioHero, type PortfolioHeroProps } from './PortfolioHero/PortfolioHero';
|
|
115
|
+
export { default as PlanComparisonCard, type PlanComparisonCardProps, type PlanComparisonColumn, type PlanComparisonRow, type PlanComparisonCellValue } from './PlanComparisonCard/PlanComparisonCard';
|
|
109
116
|
export { default as PoweredByLabel, type PoweredByLabelProps } from './PoweredByLabel/PoweredByLabel';
|
|
110
117
|
export { default as ProductLabel, type ProductLabelProps } from './ProductLabel/ProductLabel';
|
|
111
118
|
export { default as ProductOverview, type ProductOverviewProps, type ProductOverviewStat } from './ProductOverview/ProductOverview';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Auto-generated from SVG files in src/icons/
|
|
5
5
|
* DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
|
|
6
6
|
*
|
|
7
|
-
* Generated: 2026-05-
|
|
7
|
+
* Generated: 2026-05-29T17:01:15.629Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
1
|
+
import React, { useMemo, useState } from 'react'
|
|
2
2
|
import {
|
|
3
3
|
View,
|
|
4
4
|
Text,
|
|
@@ -21,9 +21,49 @@ if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental
|
|
|
21
21
|
UIManager.setLayoutAnimationEnabledExperimental(true)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
type AccordionStateMode = 'Idle' | 'Hover' | 'Open' | 'Open Hover' | 'Disabled'
|
|
25
|
+
|
|
26
|
+
function resolveAccordionStateMode(
|
|
27
|
+
disabled: boolean,
|
|
28
|
+
isExpanded: boolean,
|
|
29
|
+
isHovered: boolean,
|
|
30
|
+
contained: boolean,
|
|
31
|
+
): AccordionStateMode {
|
|
32
|
+
if (disabled) return 'Disabled'
|
|
33
|
+
|
|
34
|
+
if (contained) {
|
|
35
|
+
return isExpanded ? 'Open Hover' : 'Hover'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (isExpanded) {
|
|
39
|
+
return isHovered ? 'Open Hover' : 'Open'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return isHovered ? 'Hover' : 'Idle'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function toFontWeight(value: unknown, fallback: TextStyle['fontWeight']): TextStyle['fontWeight'] {
|
|
46
|
+
if (typeof value === 'number') return String(value) as TextStyle['fontWeight']
|
|
47
|
+
if (typeof value === 'string') {
|
|
48
|
+
const normalized = value.trim().toLowerCase()
|
|
49
|
+
if (normalized === 'bold') return '700'
|
|
50
|
+
if (normalized === 'medium') return '500'
|
|
51
|
+
if (normalized === 'regular' || normalized === 'normal') return '400'
|
|
52
|
+
if (/^\d+$/.test(normalized)) return normalized as TextStyle['fontWeight']
|
|
53
|
+
return value as TextStyle['fontWeight']
|
|
54
|
+
}
|
|
55
|
+
return fallback
|
|
56
|
+
}
|
|
57
|
+
|
|
24
58
|
export type AccordionProps = {
|
|
25
59
|
/** The accordion header title */
|
|
26
60
|
title?: string;
|
|
61
|
+
/**
|
|
62
|
+
* When `true`, the header always uses the filled background treatment
|
|
63
|
+
* (Figma Hover / Open Hover visuals). Defaults to `false` (transparent at
|
|
64
|
+
* rest, filled only while hovered or pressed).
|
|
65
|
+
*/
|
|
66
|
+
contained?: boolean;
|
|
27
67
|
/** Initial expanded state. Defaults to false (collapsed) */
|
|
28
68
|
defaultExpanded?: boolean;
|
|
29
69
|
/** Controlled expanded state. When provided, the component becomes controlled */
|
|
@@ -51,31 +91,20 @@ export type AccordionProps = {
|
|
|
51
91
|
/**
|
|
52
92
|
* Accordion component that mirrors the Figma "Accordion" component.
|
|
53
93
|
*
|
|
54
|
-
*
|
|
55
|
-
* -
|
|
56
|
-
*
|
|
57
|
-
* -
|
|
58
|
-
* - **Design-token driven styling** via `getVariableByName` and `modes`
|
|
94
|
+
* Supports two visual treatments via the `contained` prop:
|
|
95
|
+
* - **`contained={false}`** (default) — transparent header at rest; filled
|
|
96
|
+
* background on hover / press.
|
|
97
|
+
* - **`contained={true}`** — header always uses the filled background.
|
|
59
98
|
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
99
|
+
* Interaction states (Idle, Hover, Open, Disabled) are resolved automatically
|
|
100
|
+
* from `expanded`, `disabled`, hover, and `contained` — consumers should not
|
|
101
|
+
* pass `'Accordion States'` in `modes`.
|
|
63
102
|
*
|
|
64
103
|
* @component
|
|
65
|
-
* @param {Object} props
|
|
66
|
-
* @param {string} [props.title='Accordion title'] - The accordion header title
|
|
67
|
-
* @param {boolean} [props.defaultExpanded=false] - Initial expanded state
|
|
68
|
-
* @param {boolean} [props.expanded] - Controlled expanded state
|
|
69
|
-
* @param {Function} [props.onExpandedChange] - Callback fired when expanded state changes
|
|
70
|
-
* @param {boolean} [props.disabled=false] - Whether the accordion is disabled
|
|
71
|
-
* @param {React.ReactNode} [props.children] - Content to display when expanded
|
|
72
|
-
* @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens
|
|
73
|
-
* @param {Object} [props.style] - Optional container style overrides
|
|
74
|
-
* @param {string} [props.accessibilityLabel] - Accessibility label for the accordion. If not provided, uses title
|
|
75
|
-
* @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
|
|
76
104
|
*/
|
|
77
105
|
function Accordion({
|
|
78
106
|
title = 'Accordion title',
|
|
107
|
+
contained = false,
|
|
79
108
|
defaultExpanded = false,
|
|
80
109
|
expanded: controlledExpanded,
|
|
81
110
|
onExpandedChange,
|
|
@@ -89,23 +118,31 @@ function Accordion({
|
|
|
89
118
|
webAccessibilityProps,
|
|
90
119
|
...rest
|
|
91
120
|
}: AccordionProps) {
|
|
92
|
-
// Internal state for uncontrolled mode
|
|
93
121
|
const [internalExpanded, setInternalExpanded] = useState(defaultExpanded)
|
|
94
|
-
|
|
95
|
-
|
|
122
|
+
const [isHovered, setIsHovered] = useState(false)
|
|
123
|
+
|
|
96
124
|
const isControlled = controlledExpanded !== undefined
|
|
97
125
|
const isExpanded = isControlled ? controlledExpanded : internalExpanded
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
126
|
+
|
|
127
|
+
const resolvedModes = useMemo(() => {
|
|
128
|
+
const accordionState = resolveAccordionStateMode(
|
|
129
|
+
disabled,
|
|
130
|
+
isExpanded,
|
|
131
|
+
isHovered,
|
|
132
|
+
contained,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
...modes,
|
|
137
|
+
'Accordion States': accordionState,
|
|
138
|
+
}
|
|
139
|
+
}, [contained, disabled, isExpanded, isHovered, modes])
|
|
140
|
+
|
|
103
141
|
const handleToggle = () => {
|
|
104
142
|
if (disabled) return
|
|
105
|
-
|
|
106
|
-
// Animate the layout change
|
|
143
|
+
|
|
107
144
|
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
|
108
|
-
|
|
145
|
+
|
|
109
146
|
if (isControlled) {
|
|
110
147
|
onExpandedChange?.(!isExpanded)
|
|
111
148
|
} else {
|
|
@@ -113,38 +150,45 @@ function Accordion({
|
|
|
113
150
|
onExpandedChange?.(!isExpanded)
|
|
114
151
|
}
|
|
115
152
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
const titleFontFamily =
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
|
|
154
|
+
const titleColor =
|
|
155
|
+
(getVariableByName('accordion/title/color', resolvedModes) as string | null) ?? '#0d0d0d'
|
|
156
|
+
const titleFontSize =
|
|
157
|
+
(getVariableByName('accordion/title/fontSize', resolvedModes) as number | null) ?? 14
|
|
158
|
+
const titleLineHeight =
|
|
159
|
+
(getVariableByName('accordion/title/lineHeight', resolvedModes) as number | null) ?? 20
|
|
160
|
+
const titleFontFamily =
|
|
161
|
+
(getVariableByName('accordion/title/fontFamily', resolvedModes) as string | null) ?? 'System'
|
|
162
|
+
const titleFontWeight = toFontWeight(
|
|
163
|
+
getVariableByName('accordion/title/fontWeight', resolvedModes),
|
|
164
|
+
'700',
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
const iconColor =
|
|
168
|
+
(getVariableByName('accordion/icon/color', resolvedModes) as string | null) ?? '#141414'
|
|
169
|
+
const iconSize = (getVariableByName('accordion/icon/size', resolvedModes) as number | null) ?? 24
|
|
170
|
+
|
|
171
|
+
const headerGap = (getVariableByName('accordion/header/gap', resolvedModes) as number | null) ?? 12
|
|
172
|
+
const headerPaddingVertical =
|
|
173
|
+
(getVariableByName('accordion/header/padding/vertical', resolvedModes) as number | null) ?? 8
|
|
174
|
+
const headerBackground =
|
|
175
|
+
(getVariableByName('accordion/header/background', resolvedModes) as string | null) ??
|
|
176
|
+
'transparent'
|
|
177
|
+
|
|
178
|
+
const contentGap = (getVariableByName('accordion/content/gap', resolvedModes) as number | null) ?? 12
|
|
179
|
+
const contentPaddingTop =
|
|
180
|
+
(getVariableByName('accordion/content/padding/top', resolvedModes) as number | null) ?? 8
|
|
181
|
+
const contentPaddingBottom =
|
|
182
|
+
(getVariableByName('accordion/content/padding/bottom', resolvedModes) as number | null) ?? 8
|
|
183
|
+
|
|
184
|
+
const borderColor =
|
|
185
|
+
(getVariableByName('accordion/border/color', resolvedModes) as string | null) ?? '#e6e6e6'
|
|
186
|
+
|
|
143
187
|
const containerStyle: ViewStyle = {
|
|
144
188
|
borderBottomWidth: 1,
|
|
145
189
|
borderBottomColor: borderColor,
|
|
146
190
|
}
|
|
147
|
-
|
|
191
|
+
|
|
148
192
|
const headerStyle: ViewStyle = {
|
|
149
193
|
flexDirection: 'row',
|
|
150
194
|
alignItems: 'center',
|
|
@@ -154,16 +198,16 @@ function Accordion({
|
|
|
154
198
|
backgroundColor: headerBackground,
|
|
155
199
|
overflow: 'hidden',
|
|
156
200
|
}
|
|
157
|
-
|
|
201
|
+
|
|
158
202
|
const titleStyle: TextStyle = {
|
|
159
203
|
flex: 1,
|
|
160
204
|
color: titleColor,
|
|
161
205
|
fontSize: titleFontSize,
|
|
162
206
|
lineHeight: titleLineHeight,
|
|
163
207
|
fontFamily: titleFontFamily,
|
|
164
|
-
fontWeight:
|
|
208
|
+
fontWeight: titleFontWeight,
|
|
165
209
|
}
|
|
166
|
-
|
|
210
|
+
|
|
167
211
|
const contentStyle: ViewStyle = {
|
|
168
212
|
backgroundColor: 'transparent',
|
|
169
213
|
gap: contentGap,
|
|
@@ -172,11 +216,9 @@ function Accordion({
|
|
|
172
216
|
paddingHorizontal: 0,
|
|
173
217
|
overflow: 'hidden',
|
|
174
218
|
}
|
|
175
|
-
|
|
176
|
-
// Generate default accessibility label
|
|
219
|
+
|
|
177
220
|
const defaultAccessibilityLabel = accessibilityLabel || title
|
|
178
|
-
|
|
179
|
-
// Web platform support
|
|
221
|
+
|
|
180
222
|
const webProps = usePressableWebSupport({
|
|
181
223
|
restProps: {},
|
|
182
224
|
onPress: handleToggle,
|
|
@@ -184,12 +226,11 @@ function Accordion({
|
|
|
184
226
|
accessibilityLabel: defaultAccessibilityLabel,
|
|
185
227
|
webAccessibilityProps,
|
|
186
228
|
})
|
|
187
|
-
|
|
188
|
-
// Process children to pass modes
|
|
229
|
+
|
|
189
230
|
const processedChildren = children
|
|
190
|
-
? cloneChildrenWithModes(React.Children.toArray(children),
|
|
231
|
+
? cloneChildrenWithModes(React.Children.toArray(children), resolvedModes)
|
|
191
232
|
: null
|
|
192
|
-
|
|
233
|
+
|
|
193
234
|
return (
|
|
194
235
|
<View style={[containerStyle, style]} {...rest}>
|
|
195
236
|
<Pressable
|
|
@@ -217,12 +258,12 @@ function Accordion({
|
|
|
217
258
|
<Icon
|
|
218
259
|
name={isExpanded ? 'ic_minus' : 'ic_add'}
|
|
219
260
|
size={iconSize}
|
|
220
|
-
color={
|
|
261
|
+
color={iconColor}
|
|
221
262
|
accessibilityElementsHidden={true}
|
|
222
263
|
importantForAccessibility="no"
|
|
223
264
|
/>
|
|
224
265
|
</Pressable>
|
|
225
|
-
|
|
266
|
+
|
|
226
267
|
{isExpanded && processedChildren && (
|
|
227
268
|
<View style={contentStyle}>
|
|
228
269
|
{processedChildren}
|
|
@@ -233,4 +274,3 @@ function Accordion({
|
|
|
233
274
|
}
|
|
234
275
|
|
|
235
276
|
export default Accordion
|
|
236
|
-
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import React, { useMemo } from 'react'
|
|
1
|
+
import React, { useEffect, useMemo, useRef } from 'react'
|
|
2
2
|
import {
|
|
3
|
+
Animated,
|
|
4
|
+
Keyboard,
|
|
3
5
|
View,
|
|
4
6
|
Platform,
|
|
7
|
+
type KeyboardEvent,
|
|
5
8
|
type ViewStyle,
|
|
6
9
|
type StyleProp,
|
|
7
10
|
} from 'react-native'
|
|
@@ -133,6 +136,47 @@ function ActionFooter({
|
|
|
133
136
|
style,
|
|
134
137
|
accessibilityLabel,
|
|
135
138
|
}: ActionFooterProps) {
|
|
139
|
+
// -------------------------------------------------------------------------
|
|
140
|
+
// Keep the footer locked in place behind the software keyboard (Android).
|
|
141
|
+
// -------------------------------------------------------------------------
|
|
142
|
+
//
|
|
143
|
+
// The Android activity is configured with `windowSoftInputMode="adjustResize"`,
|
|
144
|
+
// which shrinks the app window by the keyboard height when the keyboard
|
|
145
|
+
// opens. A bottom-anchored footer therefore gets lifted UP by the keyboard
|
|
146
|
+
// height — exactly the jump the design does not want.
|
|
147
|
+
//
|
|
148
|
+
// To counteract that, we translate the footer back DOWN by the same keyboard
|
|
149
|
+
// height so it visually stays exactly where it was (now sitting behind the
|
|
150
|
+
// keyboard). iOS does not resize the window for the keyboard, so the footer
|
|
151
|
+
// already stays put there; we only run this on Android to avoid pushing the
|
|
152
|
+
// footer off-screen on platforms that don't lift it in the first place.
|
|
153
|
+
const keyboardOffset = useRef(new Animated.Value(0)).current
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (Platform.OS !== 'android') return undefined
|
|
156
|
+
|
|
157
|
+
const animateTo = (toValue: number, duration?: number) => {
|
|
158
|
+
Animated.timing(keyboardOffset, {
|
|
159
|
+
toValue,
|
|
160
|
+
// Match the OS keyboard animation so the resize and our counter-shift
|
|
161
|
+
// cancel out smoothly with no visible footer movement.
|
|
162
|
+
duration: typeof duration === 'number' && duration > 0 ? duration : 150,
|
|
163
|
+
useNativeDriver: true,
|
|
164
|
+
}).start()
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const showSub = Keyboard.addListener('keyboardDidShow', (e: KeyboardEvent) => {
|
|
168
|
+
animateTo(e?.endCoordinates?.height ?? 0, e?.duration)
|
|
169
|
+
})
|
|
170
|
+
const hideSub = Keyboard.addListener('keyboardDidHide', (e: KeyboardEvent) => {
|
|
171
|
+
animateTo(0, e?.duration)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
return () => {
|
|
175
|
+
showSub.remove()
|
|
176
|
+
hideSub.remove()
|
|
177
|
+
}
|
|
178
|
+
}, [keyboardOffset])
|
|
179
|
+
|
|
136
180
|
// All token reads collapsed into a single useMemo keyed on `modes`. With
|
|
137
181
|
// the shared `EMPTY_MODES` default this resolves once for the common path
|
|
138
182
|
// and never re-allocates the container/slot style objects between renders.
|
|
@@ -192,13 +236,21 @@ function ActionFooter({
|
|
|
192
236
|
}, [children, modes])
|
|
193
237
|
|
|
194
238
|
return (
|
|
195
|
-
<View
|
|
196
|
-
style={[
|
|
239
|
+
<Animated.View
|
|
240
|
+
style={[
|
|
241
|
+
containerStyle,
|
|
242
|
+
WEB_SHADOW,
|
|
243
|
+
style,
|
|
244
|
+
// Counter-translate by the keyboard height on Android so `adjustResize`
|
|
245
|
+
// can't lift the footer above the keyboard (no-op on iOS/web where the
|
|
246
|
+
// value stays at 0).
|
|
247
|
+
{ transform: [{ translateY: keyboardOffset }] },
|
|
248
|
+
]}
|
|
197
249
|
accessibilityRole="toolbar"
|
|
198
250
|
accessibilityLabel={accessibilityLabel}
|
|
199
251
|
>
|
|
200
252
|
<View style={slotStyle}>{enhancedChildren}</View>
|
|
201
|
-
</View>
|
|
253
|
+
</Animated.View>
|
|
202
254
|
)
|
|
203
255
|
}
|
|
204
256
|
|