jfs-components 0.0.1 → 0.0.2
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/lib/commonjs/components/ActionFooter/ActionFooter.js +124 -0
- package/lib/commonjs/components/ActionFooter/ActionFooter.js.map +1 -0
- package/lib/commonjs/components/ActionFooter/ActionFooter.mdx +101 -0
- package/lib/commonjs/components/Button/Button.js +1 -1
- package/lib/commonjs/components/Button/Button.js.map +1 -1
- package/lib/commonjs/components/Button/Button.mdx +2 -2
- package/lib/commonjs/components/CardFeedback/CardFeedback.js +47 -11
- package/lib/commonjs/components/CardFeedback/CardFeedback.js.map +1 -1
- package/lib/commonjs/components/CardFeedback/CardFeedback.mdx +1 -0
- package/lib/commonjs/components/Divider/Divider.js +63 -0
- package/lib/commonjs/components/Divider/Divider.js.map +1 -0
- package/lib/commonjs/components/Divider/Divider.mdx +91 -0
- package/lib/commonjs/components/ListItem/ListItem.js +24 -13
- package/lib/commonjs/components/ListItem/ListItem.js.map +1 -1
- package/lib/commonjs/components/ListItem/ListItem.mdx +46 -5
- package/lib/commonjs/components/MerchantProfile/MerchantProfile.js +133 -0
- package/lib/commonjs/components/MerchantProfile/MerchantProfile.js.map +1 -0
- package/lib/commonjs/components/MerchantProfile/MerchantProfile.mdx +139 -0
- package/lib/commonjs/components/MoneyValue/MoneyValue.mdx +4 -0
- package/lib/commonjs/components/NavArrow/NavArrow.js +90 -0
- package/lib/commonjs/components/NavArrow/NavArrow.js.map +1 -0
- package/lib/commonjs/components/NavArrow/NavArrow.mdx +123 -0
- package/lib/commonjs/components/Section/Section.mdx +4 -4
- package/lib/commonjs/components/Stepper/Step.mdx +1 -1
- package/lib/commonjs/components/index.js +28 -0
- package/lib/commonjs/components/index.js.map +1 -1
- package/lib/commonjs/design-tokens/JFS Variables-variables-full.json +18633 -1
- package/lib/commonjs/design-tokens/figma-variables-resolver.js.map +1 -2
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/ActionFooter/ActionFooter.js +119 -0
- package/lib/module/components/ActionFooter/ActionFooter.js.map +1 -0
- package/lib/module/components/ActionFooter/ActionFooter.mdx +101 -0
- package/lib/module/components/Button/Button.js +1 -1
- package/lib/module/components/Button/Button.js.map +1 -1
- package/lib/module/components/Button/Button.mdx +2 -2
- package/lib/module/components/CardFeedback/CardFeedback.js +46 -11
- package/lib/module/components/CardFeedback/CardFeedback.js.map +1 -1
- package/lib/module/components/CardFeedback/CardFeedback.mdx +1 -0
- package/lib/module/components/Divider/Divider.js +58 -0
- package/lib/module/components/Divider/Divider.js.map +1 -0
- package/lib/module/components/Divider/Divider.mdx +91 -0
- package/lib/module/components/ListItem/ListItem.js +24 -13
- package/lib/module/components/ListItem/ListItem.js.map +1 -1
- package/lib/module/components/ListItem/ListItem.mdx +46 -5
- package/lib/module/components/MerchantProfile/MerchantProfile.js +128 -0
- package/lib/module/components/MerchantProfile/MerchantProfile.js.map +1 -0
- package/lib/module/components/MerchantProfile/MerchantProfile.mdx +139 -0
- package/lib/module/components/MoneyValue/MoneyValue.mdx +4 -0
- package/lib/module/components/NavArrow/NavArrow.js +84 -0
- package/lib/module/components/NavArrow/NavArrow.js.map +1 -0
- package/lib/module/components/NavArrow/NavArrow.mdx +123 -0
- package/lib/module/components/Section/Section.mdx +4 -4
- package/lib/module/components/Stepper/Step.mdx +1 -1
- package/lib/module/components/index.js +4 -0
- package/lib/module/components/index.js.map +1 -1
- package/lib/module/design-tokens/JFS Variables-variables-full.json +18633 -1
- package/lib/module/design-tokens/figma-variables-resolver.js +2 -2
- package/lib/module/design-tokens/figma-variables-resolver.js.map +2 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/components/ActionFooter/ActionFooter.d.ts +58 -0
- package/lib/typescript/components/ActionFooter/ActionFooter.d.ts.map +1 -0
- package/lib/typescript/components/CardFeedback/CardFeedback.d.ts +4 -3
- package/lib/typescript/components/CardFeedback/CardFeedback.d.ts.map +1 -1
- package/lib/typescript/components/Divider/Divider.d.ts +50 -0
- package/lib/typescript/components/Divider/Divider.d.ts.map +1 -0
- package/lib/typescript/components/ListItem/ListItem.d.ts +4 -3
- package/lib/typescript/components/ListItem/ListItem.d.ts.map +1 -1
- package/lib/typescript/components/MerchantProfile/MerchantProfile.d.ts +68 -0
- package/lib/typescript/components/MerchantProfile/MerchantProfile.d.ts.map +1 -0
- package/lib/typescript/components/NavArrow/NavArrow.d.ts +35 -0
- package/lib/typescript/components/NavArrow/NavArrow.d.ts.map +1 -0
- package/lib/typescript/components/index.d.ts +4 -0
- package/lib/typescript/components/index.d.ts.map +1 -1
- package/lib/typescript/icons/registry.d.ts +1 -1
- package/package.json +2 -2
- package/src/components/.token-metadata.json +99 -11
- package/src/components/ActionFooter/ActionFooter.mdx +101 -0
- package/src/components/ActionFooter/ActionFooter.tsx +142 -0
- package/src/components/Button/Button.mdx +2 -2
- package/src/components/Button/Button.tsx +1 -1
- package/src/components/CardFeedback/CardFeedback.mdx +1 -0
- package/src/components/CardFeedback/CardFeedback.tsx +37 -12
- package/src/components/Divider/Divider.mdx +91 -0
- package/src/components/Divider/Divider.tsx +91 -0
- package/src/components/ListItem/ListItem.mdx +46 -5
- package/src/components/ListItem/ListItem.tsx +22 -11
- package/src/components/MerchantProfile/MerchantProfile.mdx +139 -0
- package/src/components/MerchantProfile/MerchantProfile.tsx +174 -0
- package/src/components/MoneyValue/MoneyValue.mdx +4 -0
- package/src/components/NavArrow/NavArrow.mdx +123 -0
- package/src/components/NavArrow/NavArrow.tsx +108 -0
- package/src/components/Section/Section.mdx +4 -4
- package/src/components/Stepper/Step.mdx +1 -1
- package/src/components/index.ts +4 -0
- package/src/design-tokens/JFS Variables-variables-full.json +18633 -1
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
type ViewStyle,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
Platform,
|
|
7
|
+
} from 'react-native'
|
|
8
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
9
|
+
|
|
10
|
+
export type ActionFooterProps = {
|
|
11
|
+
/**
|
|
12
|
+
* Content to render inside the action footer slot.
|
|
13
|
+
* Typically includes IconButton and Button components.
|
|
14
|
+
*/
|
|
15
|
+
children?: React.ReactNode
|
|
16
|
+
/**
|
|
17
|
+
* Mode configuration passed to the token resolver.
|
|
18
|
+
* Pass the same modes to children components for consistent theming.
|
|
19
|
+
*/
|
|
20
|
+
modes?: Record<string, any>
|
|
21
|
+
/**
|
|
22
|
+
* Optional style overrides for the container
|
|
23
|
+
*/
|
|
24
|
+
style?: StyleProp<ViewStyle>
|
|
25
|
+
/**
|
|
26
|
+
* Accessibility label for the footer region
|
|
27
|
+
*/
|
|
28
|
+
accessibilityLabel?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* ActionFooter component that provides a fixed footer container for action buttons.
|
|
33
|
+
*
|
|
34
|
+
* This component is designed to hold action items like IconButton and Button components
|
|
35
|
+
* at the bottom of a screen. It includes a shadow for visual separation from content above.
|
|
36
|
+
*
|
|
37
|
+
* The `modes` prop is automatically passed to all slot children. If a child has its own
|
|
38
|
+
* `modes` prop, it will be merged with the parent's modes (child modes take precedence).
|
|
39
|
+
*
|
|
40
|
+
* @component
|
|
41
|
+
* @param {Object} props - Component props
|
|
42
|
+
* @param {React.ReactNode} [props.children] - Action elements to display (e.g., IconButton, Button)
|
|
43
|
+
* @param {Object} [props.modes={}] - Mode configuration for design tokens (automatically passed to children)
|
|
44
|
+
* @param {Object} [props.style] - Optional style overrides
|
|
45
|
+
* @param {string} [props.accessibilityLabel] - Accessibility label for the footer region
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```tsx
|
|
49
|
+
* // Basic usage - modes are automatically passed to all children
|
|
50
|
+
* <ActionFooter modes={modes}>
|
|
51
|
+
* <IconButton iconName="ic_split" />
|
|
52
|
+
* <Button label="Request" style={{ flex: 1 }} />
|
|
53
|
+
* <Button label="Pay" style={{ flex: 1 }} />
|
|
54
|
+
* </ActionFooter>
|
|
55
|
+
*
|
|
56
|
+
* // Children can override with their own modes (merged with parent)
|
|
57
|
+
* <ActionFooter modes={modes}>
|
|
58
|
+
* <IconButton iconName="ic_split" />
|
|
59
|
+
* <Button label="Request" modes={{ Appearance: 'secondary' }} style={{ flex: 1 }} />
|
|
60
|
+
* <Button label="Pay" modes={{ Appearance: 'primary' }} style={{ flex: 1 }} />
|
|
61
|
+
* </ActionFooter>
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
function ActionFooter({
|
|
65
|
+
children,
|
|
66
|
+
modes = {},
|
|
67
|
+
style,
|
|
68
|
+
accessibilityLabel = 'Action footer',
|
|
69
|
+
}: ActionFooterProps) {
|
|
70
|
+
// Resolve design tokens
|
|
71
|
+
const backgroundColor = getVariableByName('actionFooter/background', modes) ?? '#ffffff'
|
|
72
|
+
const gap = getVariableByName('actionFooter/gap', modes) ?? 8
|
|
73
|
+
const paddingHorizontal = getVariableByName('actionFooter/padding/horizontal', modes) ?? 16
|
|
74
|
+
const paddingTop = getVariableByName('actionFooter/padding/top', modes) ?? 10
|
|
75
|
+
const paddingBottom = getVariableByName('actionFooter/padding/bottom', modes) ?? 41
|
|
76
|
+
|
|
77
|
+
// Shadow styles - cross-platform
|
|
78
|
+
const shadowStyle: ViewStyle = Platform.select({
|
|
79
|
+
ios: {
|
|
80
|
+
shadowColor: 'rgba(12, 13, 16, 1)',
|
|
81
|
+
shadowOffset: { width: 0, height: -12 },
|
|
82
|
+
shadowOpacity: 0.16,
|
|
83
|
+
shadowRadius: 24,
|
|
84
|
+
},
|
|
85
|
+
android: {
|
|
86
|
+
elevation: 16,
|
|
87
|
+
},
|
|
88
|
+
default: {
|
|
89
|
+
// Web shadow using boxShadow (RNW supports this)
|
|
90
|
+
},
|
|
91
|
+
}) as ViewStyle
|
|
92
|
+
|
|
93
|
+
const containerStyle: ViewStyle = {
|
|
94
|
+
backgroundColor,
|
|
95
|
+
paddingLeft: paddingHorizontal,
|
|
96
|
+
paddingRight: paddingHorizontal,
|
|
97
|
+
paddingTop,
|
|
98
|
+
paddingBottom,
|
|
99
|
+
...shadowStyle,
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Slot container style for horizontal layout of action items
|
|
103
|
+
const slotStyle: ViewStyle = {
|
|
104
|
+
flexDirection: 'row',
|
|
105
|
+
alignItems: 'flex-start',
|
|
106
|
+
gap,
|
|
107
|
+
flex: 1,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Web-specific box-shadow
|
|
111
|
+
const webShadow = Platform.OS === 'web'
|
|
112
|
+
? { boxShadow: '0px -12px 24px 0px rgba(12, 13, 16, 0.12), 0px -16px 48px 0px rgba(12, 13, 16, 0.16)' } as any
|
|
113
|
+
: {}
|
|
114
|
+
|
|
115
|
+
// Pass modes prop to all slot children
|
|
116
|
+
const childrenWithModes = React.Children.map(children, (child) => {
|
|
117
|
+
if (React.isValidElement(child)) {
|
|
118
|
+
// Merge modes: child's modes take precedence over parent's modes
|
|
119
|
+
const childModes = (child.props as any).modes
|
|
120
|
+
const mergedModes = childModes ? { ...modes, ...childModes } : modes
|
|
121
|
+
return React.cloneElement(child as React.ReactElement<any>, {
|
|
122
|
+
modes: mergedModes,
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
return child
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<View
|
|
130
|
+
style={[containerStyle, webShadow, style]}
|
|
131
|
+
accessibilityRole="toolbar"
|
|
132
|
+
accessibilityLabel={accessibilityLabel}
|
|
133
|
+
>
|
|
134
|
+
<View style={slotStyle}>
|
|
135
|
+
{childrenWithModes}
|
|
136
|
+
</View>
|
|
137
|
+
</View>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export default ActionFooter
|
|
142
|
+
|
|
@@ -34,7 +34,7 @@ This component uses the following design token collections. Each collection supp
|
|
|
34
34
|
- **Default:** positive
|
|
35
35
|
|
|
36
36
|
### Context
|
|
37
|
-
- **Modes:** Default | Nudge&Alert
|
|
37
|
+
- **Modes:** Default | Nudge&Alert | CTACard | ListItem
|
|
38
38
|
- **Default:** Default
|
|
39
39
|
|
|
40
40
|
### Button / Size
|
|
@@ -90,7 +90,7 @@ This component uses the following design tokens, resolved through `getVariableBy
|
|
|
90
90
|
- **`button/fontFamily`**
|
|
91
91
|
- **`button/fontSize`**
|
|
92
92
|
- **`button/fontWeight`**
|
|
93
|
-
- **`button/
|
|
93
|
+
- **`button/foreground`**
|
|
94
94
|
- **`button/gap`**
|
|
95
95
|
- **`button/lineHeight`**
|
|
96
96
|
- **`button/padding/horizontal`**
|
|
@@ -89,7 +89,7 @@ function Button({
|
|
|
89
89
|
const fontWeight = typeof fontWeightValue === 'number' ? fontWeightValue.toString() : fontWeightValue
|
|
90
90
|
const lineHeight = getVariableByName('button/lineHeight', modes) || 19
|
|
91
91
|
const fontSize = getVariableByName('button/fontSize', modes) || 16
|
|
92
|
-
const textColor = getVariableByName('button/
|
|
92
|
+
const textColor = getVariableByName('button/foreground', modes) || '#0f0d0a'
|
|
93
93
|
|
|
94
94
|
const baseLabelTextStyle: TextStyle = {
|
|
95
95
|
color: textColor,
|
|
@@ -62,6 +62,7 @@ This component uses the following design token collections. Each collection supp
|
|
|
62
62
|
|
|
63
63
|
This component uses the following design tokens, resolved through `getVariableByName`:
|
|
64
64
|
|
|
65
|
+
- **`appearanceFeedback/cardFeedback/icon/color`**
|
|
65
66
|
- **`cardFeedback/actionSlot/gap`**
|
|
66
67
|
- **`cardFeedback/background`**
|
|
67
68
|
- **`cardFeedback/body/color`**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React, { createContext, useContext, isValidElement, cloneElement } from 'react';
|
|
2
2
|
import { View, Text, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, Image } from 'react-native';
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver';
|
|
4
|
+
import IconComponent from '../../icons/Icon';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Context to share 'modes' with child components.
|
|
@@ -29,9 +30,15 @@ export interface CardFeedbackProps {
|
|
|
29
30
|
*/
|
|
30
31
|
export function CardFeedback({
|
|
31
32
|
children,
|
|
32
|
-
modes
|
|
33
|
+
modes: propModes,
|
|
33
34
|
style,
|
|
34
35
|
}: CardFeedbackProps) {
|
|
36
|
+
const modes = {
|
|
37
|
+
'Appearance.System': 'positive',
|
|
38
|
+
'Context': 'Nudge&Alert',
|
|
39
|
+
'Semantic Intent': 'System',
|
|
40
|
+
...propModes,
|
|
41
|
+
};
|
|
35
42
|
// Container Tokens
|
|
36
43
|
const backgroundColor = getVariableByName('cardFeedback/background', modes) || '#dbf0d9';
|
|
37
44
|
const radius = parseFloat(getVariableByName('cardFeedback/radius', modes) || '12');
|
|
@@ -66,16 +73,16 @@ export function CardFeedback({
|
|
|
66
73
|
|
|
67
74
|
/**
|
|
68
75
|
* Icon Wrapper
|
|
69
|
-
* Tokens: cardFeedback/icon/size, cardFeedback/icon/color
|
|
76
|
+
* Tokens: cardFeedback/icon/size, appearanceFeedback/cardFeedback/icon/color
|
|
70
77
|
*/
|
|
71
|
-
export function Icon({ children, style, modes: propModes }: { children?: React.ReactNode; style?: StyleProp<ViewStyle>; modes?: Record<string, any
|
|
78
|
+
export function Icon({ children, style, modes: propModes, icon }: { children?: React.ReactNode; style?: StyleProp<ViewStyle>; modes?: Record<string, any>; icon?: string }) {
|
|
72
79
|
const context = useContext(CardFeedbackContext);
|
|
73
80
|
const modes = propModes || context.modes || {};
|
|
74
81
|
|
|
75
82
|
const size = parseFloat(getVariableByName('cardFeedback/icon/size', modes) || '20');
|
|
76
|
-
// Color
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
// Color for the icon itself
|
|
84
|
+
const color = getVariableByName('appearanceFeedback/cardFeedback/icon/color', modes);
|
|
85
|
+
console.log(color);
|
|
79
86
|
const containerStyle: ViewStyle = {
|
|
80
87
|
width: size,
|
|
81
88
|
height: size,
|
|
@@ -84,11 +91,21 @@ export function Icon({ children, style, modes: propModes }: { children?: React.R
|
|
|
84
91
|
overflow: 'hidden',
|
|
85
92
|
};
|
|
86
93
|
|
|
87
|
-
//
|
|
88
|
-
|
|
94
|
+
// Pass modes to children (e.g., icon components that accept modes)
|
|
95
|
+
const childrenWithModes = React.Children.map(children, child => {
|
|
96
|
+
if (isValidElement(child)) {
|
|
97
|
+
return cloneElement(child as React.ReactElement<{ modes?: Record<string, any> }>, { modes: { ...(child.props as any).modes, ...modes } });
|
|
98
|
+
}
|
|
99
|
+
return child;
|
|
100
|
+
});
|
|
101
|
+
|
|
89
102
|
return (
|
|
90
103
|
<View style={[containerStyle, style]}>
|
|
91
|
-
{
|
|
104
|
+
{icon ? (
|
|
105
|
+
<IconComponent name={icon} size={size} color={color} />
|
|
106
|
+
) : (
|
|
107
|
+
childrenWithModes
|
|
108
|
+
)}
|
|
92
109
|
</View>
|
|
93
110
|
);
|
|
94
111
|
}
|
|
@@ -104,9 +121,17 @@ export function Content({ children, style, modes: propModes }: { children?: Reac
|
|
|
104
121
|
|
|
105
122
|
const gap = parseFloat(getVariableByName('cardFeedback/text/gap', modes) || '8');
|
|
106
123
|
|
|
124
|
+
// Pass modes to children (Title, Body, etc.)
|
|
125
|
+
const childrenWithModes = React.Children.map(children, child => {
|
|
126
|
+
if (isValidElement(child)) {
|
|
127
|
+
return cloneElement(child as React.ReactElement<{ modes?: Record<string, any> }>, { modes: { ...(child.props as any).modes, ...modes } });
|
|
128
|
+
}
|
|
129
|
+
return child;
|
|
130
|
+
});
|
|
131
|
+
|
|
107
132
|
return (
|
|
108
133
|
<View style={[{ gap, width: '100%' }, style]}>
|
|
109
|
-
{
|
|
134
|
+
{childrenWithModes}
|
|
110
135
|
</View>
|
|
111
136
|
);
|
|
112
137
|
}
|
|
@@ -169,12 +194,12 @@ export function Action({ children, style, modes: propModes }: { children?: React
|
|
|
169
194
|
const context = useContext(CardFeedbackContext);
|
|
170
195
|
const modes = propModes || context.modes || {};
|
|
171
196
|
|
|
172
|
-
const gap = parseFloat(getVariableByName('cardFeedback/actionSlot/gap', modes) || '
|
|
197
|
+
const gap = parseFloat(getVariableByName('cardFeedback/actionSlot/gap', modes) || '0');
|
|
173
198
|
|
|
174
199
|
// "Maximise existing component usage" -> Pass modes to children (Buttons)
|
|
175
200
|
const childrenWithModes = React.Children.map(children, child => {
|
|
176
201
|
if (isValidElement(child)) {
|
|
177
|
-
return cloneElement(child as
|
|
202
|
+
return cloneElement(child as React.ReactElement<{ modes?: Record<string, any> }>, { modes: { ...(child.props as any).modes, ...modes } });
|
|
178
203
|
}
|
|
179
204
|
return child;
|
|
180
205
|
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Meta, Story, Canvas, PureArgsTable as ArgsTable } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as DividerStories from './Divider.stories';
|
|
3
|
+
import Divider from './Divider';
|
|
4
|
+
|
|
5
|
+
<Meta of={DividerStories} />
|
|
6
|
+
|
|
7
|
+
# Divider
|
|
8
|
+
|
|
9
|
+
A simple divider component that renders a horizontal or vertical separator line. Uses design tokens for consistent sizing and color.
|
|
10
|
+
|
|
11
|
+
## Props
|
|
12
|
+
|
|
13
|
+
<ArgsTable of={Divider} />
|
|
14
|
+
|
|
15
|
+
## Available Collections and Modes
|
|
16
|
+
|
|
17
|
+
This component uses the following design token collections. Each collection supports multiple modes that can be configured via the `modes` prop.
|
|
18
|
+
|
|
19
|
+
### Color Mode
|
|
20
|
+
- **Modes:** Light | Dark
|
|
21
|
+
- **Default:** Light
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Horizontal Divider
|
|
25
|
+
|
|
26
|
+
The default horizontal divider stretches to fill its container width:
|
|
27
|
+
|
|
28
|
+
<Canvas>
|
|
29
|
+
<Story of={DividerStories.Horizontal} />
|
|
30
|
+
</Canvas>
|
|
31
|
+
|
|
32
|
+
### Vertical Divider
|
|
33
|
+
|
|
34
|
+
Vertical dividers stretch to fill their container height:
|
|
35
|
+
|
|
36
|
+
<Canvas>
|
|
37
|
+
<Story of={DividerStories.Vertical} />
|
|
38
|
+
</Canvas>
|
|
39
|
+
|
|
40
|
+
### In Content Layout
|
|
41
|
+
|
|
42
|
+
Use horizontal dividers to separate content sections:
|
|
43
|
+
|
|
44
|
+
<Canvas>
|
|
45
|
+
<Story of={DividerStories.InContentLayout} />
|
|
46
|
+
</Canvas>
|
|
47
|
+
|
|
48
|
+
### In Row Layout
|
|
49
|
+
|
|
50
|
+
Use vertical dividers to separate items in a horizontal row:
|
|
51
|
+
|
|
52
|
+
<Canvas>
|
|
53
|
+
<Story of={DividerStories.InRowLayout} />
|
|
54
|
+
</Canvas>
|
|
55
|
+
|
|
56
|
+
## Component Usage
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { Divider } from 'jsf-components';
|
|
60
|
+
|
|
61
|
+
// Horizontal divider (default)
|
|
62
|
+
<Divider />
|
|
63
|
+
|
|
64
|
+
// Vertical divider
|
|
65
|
+
<Divider direction="vertical" />
|
|
66
|
+
|
|
67
|
+
// In a vertical layout
|
|
68
|
+
<View>
|
|
69
|
+
<Text>Section 1</Text>
|
|
70
|
+
<Divider />
|
|
71
|
+
<Text>Section 2</Text>
|
|
72
|
+
</View>
|
|
73
|
+
|
|
74
|
+
// In a horizontal layout
|
|
75
|
+
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
|
76
|
+
<Text>Item 1</Text>
|
|
77
|
+
<View style={{ marginHorizontal: 8 }}>
|
|
78
|
+
<Divider direction="vertical" />
|
|
79
|
+
</View>
|
|
80
|
+
<Text>Item 2</Text>
|
|
81
|
+
</View>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Design Tokens
|
|
85
|
+
|
|
86
|
+
This component uses the following design tokens, resolved through `getVariableByName`:
|
|
87
|
+
|
|
88
|
+
- **`divider/color`**
|
|
89
|
+
- **`divider/size`**
|
|
90
|
+
|
|
91
|
+
All tokens support mode-based theming through the `modes` prop.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
type ViewStyle,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
} from 'react-native'
|
|
7
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
8
|
+
|
|
9
|
+
export type DividerDirection = 'horizontal' | 'vertical'
|
|
10
|
+
|
|
11
|
+
export type DividerProps = {
|
|
12
|
+
/**
|
|
13
|
+
* Direction of the divider line.
|
|
14
|
+
* - 'horizontal': renders a horizontal line (default)
|
|
15
|
+
* - 'vertical': renders a vertical line
|
|
16
|
+
*/
|
|
17
|
+
direction?: DividerDirection
|
|
18
|
+
/**
|
|
19
|
+
* Mode configuration passed to the token resolver.
|
|
20
|
+
*/
|
|
21
|
+
modes?: Record<string, any>
|
|
22
|
+
/**
|
|
23
|
+
* Optional style overrides for the divider
|
|
24
|
+
*/
|
|
25
|
+
style?: StyleProp<ViewStyle>
|
|
26
|
+
/**
|
|
27
|
+
* Accessibility label for the divider
|
|
28
|
+
*/
|
|
29
|
+
accessibilityLabel?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Divider component that renders a horizontal or vertical separator line.
|
|
34
|
+
*
|
|
35
|
+
* This component is used to visually separate content sections. It supports
|
|
36
|
+
* both horizontal and vertical orientations based on the `direction` prop.
|
|
37
|
+
*
|
|
38
|
+
* @component
|
|
39
|
+
* @param {Object} props - Component props
|
|
40
|
+
* @param {'horizontal' | 'vertical'} [props.direction='horizontal'] - Direction of the divider
|
|
41
|
+
* @param {Object} [props.modes={}] - Mode configuration for design tokens
|
|
42
|
+
* @param {Object} [props.style] - Optional style overrides
|
|
43
|
+
* @param {string} [props.accessibilityLabel] - Accessibility label for the divider
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```tsx
|
|
47
|
+
* // Horizontal divider (default)
|
|
48
|
+
* <Divider />
|
|
49
|
+
*
|
|
50
|
+
* // Vertical divider
|
|
51
|
+
* <Divider direction="vertical" />
|
|
52
|
+
*
|
|
53
|
+
* // With custom modes
|
|
54
|
+
* <Divider modes={{ Appearance: 'dark' }} />
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
function Divider({
|
|
58
|
+
direction = 'horizontal',
|
|
59
|
+
modes = {},
|
|
60
|
+
style,
|
|
61
|
+
accessibilityLabel = 'Divider',
|
|
62
|
+
}: DividerProps) {
|
|
63
|
+
// Resolve design tokens
|
|
64
|
+
const size = getVariableByName('divider/size', modes) ?? 1
|
|
65
|
+
const color = getVariableByName('divider/color', modes) ?? '#1a1c1f'
|
|
66
|
+
|
|
67
|
+
const isVertical = direction === 'vertical'
|
|
68
|
+
|
|
69
|
+
const dividerStyle: ViewStyle = isVertical
|
|
70
|
+
? {
|
|
71
|
+
width: size,
|
|
72
|
+
backgroundColor: color,
|
|
73
|
+
alignSelf: 'stretch',
|
|
74
|
+
}
|
|
75
|
+
: {
|
|
76
|
+
height: size,
|
|
77
|
+
backgroundColor: color,
|
|
78
|
+
alignSelf: 'stretch',
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<View
|
|
83
|
+
style={[dividerStyle, style]}
|
|
84
|
+
accessibilityRole="none"
|
|
85
|
+
accessibilityLabel={accessibilityLabel}
|
|
86
|
+
/>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default Divider
|
|
91
|
+
|
|
@@ -7,7 +7,9 @@ import { AccessibilitySection, AnatomySection, UsageConstraintsSection } from '.
|
|
|
7
7
|
|
|
8
8
|
# ListItem
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
A token-driven list item component supporting vertical and horizontal layouts, with optional navigation behaviour and slots for leading, support text and trailing content.
|
|
11
|
+
|
|
12
|
+
The horizontal layout includes a **NavArrow** on the far right by default, which can be hidden via the `navArrow` prop.
|
|
11
13
|
|
|
12
14
|
## Available Collections and Modes
|
|
13
15
|
|
|
@@ -26,9 +28,47 @@ This component uses the following design token collections. Each collection supp
|
|
|
26
28
|
- **Default:** Light
|
|
27
29
|
## Usage
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
### Horizontal Layout with NavArrow
|
|
32
|
+
|
|
33
|
+
<Canvas of={ListItemStories.HorizontalNavigation} />
|
|
34
|
+
|
|
35
|
+
### Horizontal Layout (NavArrow only, no endSlot)
|
|
36
|
+
|
|
37
|
+
<Canvas of={ListItemStories.HorizontalWithNavArrowOnly} />
|
|
38
|
+
|
|
39
|
+
### Vertical Layout
|
|
40
|
+
|
|
41
|
+
<Canvas of={ListItemStories.VerticalStatic} />
|
|
42
|
+
|
|
43
|
+
### Usage Example
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
import ListItem from './ListItem';
|
|
47
|
+
import Button from '../Button/Button';
|
|
48
|
+
|
|
49
|
+
// Horizontal with NavArrow (default)
|
|
50
|
+
<ListItem
|
|
51
|
+
layout="Horizontal"
|
|
52
|
+
title="Navigate to Details"
|
|
53
|
+
supportText="Tap to view more"
|
|
54
|
+
navArrow={true}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
// Horizontal without NavArrow
|
|
58
|
+
<ListItem
|
|
59
|
+
layout="Horizontal"
|
|
60
|
+
title="Balance"
|
|
61
|
+
supportText="₹500"
|
|
62
|
+
navArrow={false}
|
|
63
|
+
endSlot={<Button label="Add Money" />}
|
|
64
|
+
/>
|
|
65
|
+
|
|
66
|
+
// Vertical layout (NavArrow not applicable)
|
|
67
|
+
<ListItem
|
|
68
|
+
layout="Vertical"
|
|
69
|
+
supportText="Payments"
|
|
70
|
+
/>
|
|
71
|
+
```
|
|
32
72
|
|
|
33
73
|
|
|
34
74
|
<AccessibilitySection
|
|
@@ -43,7 +83,8 @@ This component uses the following design token collections. Each collection supp
|
|
|
43
83
|
<ul>
|
|
44
84
|
<li><strong>Leading slot</strong> — optional icon/avatar.</li>
|
|
45
85
|
<li><strong>Title & support</strong> — primary and secondary text.</li>
|
|
46
|
-
<li><strong>Trailing slot</strong> — optional meta info or action affordance.</li>
|
|
86
|
+
<li><strong>Trailing slot (endSlot)</strong> — optional meta info or action affordance. <strong>Note:</strong> This slot strictly enforces <code>Context: 'ListItem'</code> for its children, overriding any conflicting context passed via the <code>modes</code> prop.</li>
|
|
87
|
+
<li><strong>NavArrow</strong> — navigation chevron on the far right (Horizontal layout only). Shown by default, can be hidden via <code>navArrow={false}</code>.</li>
|
|
47
88
|
</ul>
|
|
48
89
|
</AnatomySection>
|
|
49
90
|
|
|
@@ -2,16 +2,20 @@ import React, { useMemo } from 'react'
|
|
|
2
2
|
import { View, Text, Pressable, type StyleProp, type ViewStyle, type TextStyle, type AccessibilityState } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import IconCapsule from '../IconCapsule/IconCapsule'
|
|
5
|
-
import
|
|
5
|
+
import NavArrow from '../NavArrow/NavArrow'
|
|
6
6
|
import { usePressableWebSupport, type WebAccessibilityProps } from '../../utils/web-platform-utils'
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Helper function to recursively clone children and pass modes prop to components that accept it.
|
|
10
10
|
* This ensures that all child components in slots receive the modes prop from the parent.
|
|
11
|
+
*
|
|
12
|
+
* @param forcedModes - Optional modes that will ALWAYS be applied last, overriding any other modes.
|
|
13
|
+
* Useful for slots that need to enforce specific context values.
|
|
11
14
|
*/
|
|
12
15
|
function cloneChildrenWithModes(
|
|
13
16
|
children: React.ReactNode,
|
|
14
|
-
modes: Record<string, any
|
|
17
|
+
modes: Record<string, any>,
|
|
18
|
+
forcedModes?: Record<string, any>
|
|
15
19
|
): React.ReactNode[] {
|
|
16
20
|
const result = React.Children.map(children, (child) => {
|
|
17
21
|
if (!React.isValidElement(child)) {
|
|
@@ -24,15 +28,21 @@ function cloneChildrenWithModes(
|
|
|
24
28
|
|
|
25
29
|
// Clone the child with modes prop if it doesn't already have one
|
|
26
30
|
// or merge with existing modes if it does
|
|
27
|
-
// Merge order: parent modes first, then child's explicit modes override them
|
|
31
|
+
// Merge order: parent modes first, then child's explicit modes override them,
|
|
32
|
+
// then forcedModes (if provided) are applied last and can never be overridden
|
|
28
33
|
const existingModes = (child.props as any)?.modes
|
|
29
|
-
const mergedModes =
|
|
34
|
+
const mergedModes = forcedModes
|
|
35
|
+
? { ...modes, ...(existingModes || {}), ...forcedModes }
|
|
36
|
+
: existingModes
|
|
37
|
+
? { ...modes, ...existingModes }
|
|
38
|
+
: modes
|
|
30
39
|
|
|
31
40
|
// Recursively process children if they exist
|
|
32
41
|
const processedChildren: React.ReactNode | undefined = hasChildren
|
|
33
42
|
? cloneChildrenWithModes(
|
|
34
43
|
React.Children.toArray(childChildren),
|
|
35
|
-
modes
|
|
44
|
+
modes,
|
|
45
|
+
forcedModes
|
|
36
46
|
)
|
|
37
47
|
: undefined
|
|
38
48
|
|
|
@@ -57,7 +67,8 @@ type ListItemProps = {
|
|
|
57
67
|
leading?: React.ReactNode;
|
|
58
68
|
supportSlot?: React.ReactNode;
|
|
59
69
|
endSlot?: React.ReactNode;
|
|
60
|
-
|
|
70
|
+
/** Whether to show the NavArrow on the far right (Horizontal layout only). Defaults to true. */
|
|
71
|
+
navArrow?: boolean;
|
|
61
72
|
modes?: Record<string, any>;
|
|
62
73
|
onPress?: () => void;
|
|
63
74
|
style?: StyleProp<ViewStyle>;
|
|
@@ -94,7 +105,7 @@ type ListItemProps = {
|
|
|
94
105
|
* @param {React.ReactNode} [props.leading] - Optional leading element. Defaults to `IconCapsule`.
|
|
95
106
|
* @param {React.ReactNode} [props.supportSlot] - Optional custom slot used instead of the default support text block.
|
|
96
107
|
* @param {React.ReactNode} [props.endSlot] - Optional custom trailing slot (Figma Slot "end").
|
|
97
|
-
* @param {boolean} [props.
|
|
108
|
+
* @param {boolean} [props.navArrow=true] - Whether to show NavArrow on the far right (Horizontal layout only).
|
|
98
109
|
* @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens.
|
|
99
110
|
* @param {Function} [props.onPress] - When provided, the entire item becomes pressable (navigation variant).
|
|
100
111
|
* @param {Object} [props.style] - Optional container style overrides.
|
|
@@ -111,7 +122,7 @@ function ListItem({
|
|
|
111
122
|
leading,
|
|
112
123
|
supportSlot,
|
|
113
124
|
endSlot,
|
|
114
|
-
|
|
125
|
+
navArrow = true,
|
|
115
126
|
modes = {},
|
|
116
127
|
onPress,
|
|
117
128
|
style,
|
|
@@ -275,8 +286,9 @@ function ListItem({
|
|
|
275
286
|
|
|
276
287
|
if (layout === 'Horizontal') {
|
|
277
288
|
// Process endSlot to pass modes to children
|
|
289
|
+
// Force Context: 'ListItem' - this value can never be overridden by external modes
|
|
278
290
|
const processedEndSlot = endSlot
|
|
279
|
-
? cloneChildrenWithModes(React.Children.toArray(endSlot), modes)
|
|
291
|
+
? cloneChildrenWithModes(React.Children.toArray(endSlot), modes, { "Context": 'ListItem' })
|
|
280
292
|
: []
|
|
281
293
|
// Extract single element if array has one element, otherwise use array
|
|
282
294
|
const trailingContent =
|
|
@@ -284,8 +296,6 @@ function ListItem({
|
|
|
284
296
|
? processedEndSlot.length === 1
|
|
285
297
|
? processedEndSlot[0]
|
|
286
298
|
: processedEndSlot
|
|
287
|
-
: showTrailing
|
|
288
|
-
? <Button label="Button" modes={modes} {...({} as any)} />
|
|
289
299
|
: null
|
|
290
300
|
|
|
291
301
|
const innerContent = (
|
|
@@ -310,6 +320,7 @@ function ListItem({
|
|
|
310
320
|
{trailingContent ? (
|
|
311
321
|
<View style={trailingWrapperStyle}>{trailingContent}</View>
|
|
312
322
|
) : null}
|
|
323
|
+
{navArrow && <NavArrow direction="Forward" modes={modes} />}
|
|
313
324
|
</View>
|
|
314
325
|
)
|
|
315
326
|
|