-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Component Implementation - Button #34
Changes from 33 commits
bdde4f5
4c5dca3
73615cb
6e4cac8
60208d3
1265e91
4c6cb6a
20e4377
f8e311a
cee8a51
3c414b1
fba51b8
279daaf
96971bf
754f314
1662034
a1cd6ff
83ccad2
9dfcb9d
ea6e190
3534b11
5644693
80aaac4
daaf163
488e51c
92aafe4
dd60e3e
36014a9
69a34b2
2cdfa2c
e3e9f4d
f599981
3aa460d
5bee594
2dcccdd
47d78c5
4b59999
6bac3e2
3718e21
3bdd553
ac99554
b0fd79c
a37d505
a76a7fe
3b9b7b8
d91c2cc
87e8d98
a36f94e
2902e3e
5b9ecde
1dffeeb
d9ec113
39a42a4
5479bb3
41697b2
0efee64
51b03a2
1feef56
76b1a9b
f0b50d1
76e3cee
252de4a
68b132b
747e61c
9f6a783
ce5d3a5
c1ccd01
fe1be63
72ce74c
13fa90c
c78fb9c
e27de2d
3e74b2e
6e53452
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { Meta, StoryObj } from '@storybook/react-native' | ||
import { VAButton, VAButtonProps, VAButtonVariants } from './VAButton' | ||
import { View } from 'react-native' | ||
import React from 'react' | ||
|
||
const meta: Meta<VAButtonProps> = { | ||
title: 'VAButton', | ||
component: VAButton, | ||
argTypes: { | ||
onPress: { | ||
action: 'onPress event', | ||
}, | ||
}, | ||
|
||
decorators: [ | ||
(Story) => ( | ||
<View | ||
style={{ | ||
flex: 1, | ||
justifyContent: 'center', | ||
alignItems: 'center', | ||
}} | ||
> | ||
<Story /> | ||
</View> | ||
), | ||
], | ||
} | ||
|
||
export default meta | ||
|
||
type Story = StoryObj<VAButtonProps> | ||
|
||
export const Primary: Story = { | ||
storyName: 'Primary', | ||
args: { | ||
narin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
label: 'Button text', | ||
a11yHint: 'My hint', | ||
}, | ||
} | ||
|
||
export const Secondary: Story = { | ||
storyName: 'Secondary', | ||
args: { | ||
buttonType: VAButtonVariants.Secondary, | ||
label: 'Button text', | ||
}, | ||
} | ||
|
||
narin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
export const Destructive: Story = { | ||
storyName: 'Destructive', | ||
args: { | ||
buttonType: VAButtonVariants.Destructive, | ||
label: 'Button text', | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import 'react-native' | ||
import { RenderAPI, fireEvent, render } from '@testing-library/react-native' | ||
import React from 'react' | ||
// Note: test renderer must be required after react-native. | ||
import 'jest-styled-components' | ||
import { ReactTestInstance } from 'react-test-renderer' | ||
|
||
import { VAButton } from './VAButton' | ||
|
||
describe('VAButton', () => { | ||
narin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let component: RenderAPI | ||
let testInstance: ReactTestInstance | ||
|
||
const onPressSpy = jest.fn() | ||
|
||
const initializeTestInstance = (): void => { | ||
component = render(<VAButton label="Button text" onPress={onPressSpy} />) | ||
|
||
testInstance = component.UNSAFE_root | ||
} | ||
|
||
beforeEach(() => { | ||
initializeTestInstance() | ||
}) | ||
|
||
it('initializes correctly', async () => { | ||
expect(component).toBeTruthy() | ||
}) | ||
|
||
it('should call onChange', async () => { | ||
fireEvent.press(testInstance) | ||
expect(onPressSpy).toBeCalled() | ||
}) | ||
|
||
it('should render label', async () => { | ||
expect(component.findByText('Button text')).toBeTruthy() | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
import * as DesignTokens from '@department-of-veterans-affairs/mobile-tokens' | ||
import { | ||
AccessibilityState, | ||
Pressable, | ||
PressableStateCallbackType, | ||
Text, | ||
TextStyle, | ||
ViewStyle, | ||
useColorScheme, | ||
} from 'react-native' | ||
import React from 'react' | ||
|
||
import { webStorybookColorScheme } from '../../utils' | ||
|
||
export enum VAButtonVariants { | ||
Primary, | ||
Secondary, | ||
Destructive, | ||
White, | ||
} | ||
|
||
export type VAButtonProps = { | ||
/** text appearing in the button */ | ||
label: string | ||
/** function called when button is pressed */ | ||
onPress: () => void | ||
/** optional accessibility state */ | ||
accessibilityState?: AccessibilityState | ||
/** text to use as the accessibility hint */ | ||
a11yHint?: string | ||
/** specifies button styling type. defaults to primary if none specified */ | ||
buttonType?: VAButtonVariants | ||
/** a string value used to set the buttons testID/accessibility label */ | ||
narin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
testID?: string | ||
} | ||
|
||
export const VAButton: React.FC<VAButtonProps> = ({ | ||
narin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
label, | ||
onPress, | ||
accessibilityState, | ||
a11yHint, | ||
buttonType, | ||
testID, | ||
}) => { | ||
const colorScheme = webStorybookColorScheme() || useColorScheme() | ||
const isDestructive = buttonType === VAButtonVariants.Destructive | ||
const isSecondary = buttonType === VAButtonVariants.Secondary | ||
const isWhite = buttonType === VAButtonVariants.White | ||
|
||
let bgColor: string, | ||
bgColorPressed: string, | ||
textColor: string, | ||
textColorPressed: string, | ||
borderColor: string = 'none', | ||
borderColorPressed: string = 'none' | ||
|
||
if (isWhite) { | ||
narin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// This is a one-off, mobile app only variant. Colors are not tokenized | ||
bgColor = DesignTokens.colorWhite | ||
bgColorPressed = '#ffffffb3' | ||
textColor = '#003e73' | ||
} else if (colorScheme === 'light') { | ||
bgColor = DesignTokens.colorUswdsSystemColorBlueVivid60 | ||
bgColorPressed = DesignTokens.colorUswdsSystemColorBlueWarmVivid80 | ||
textColor = DesignTokens.colorGrayLightest | ||
textColorPressed = DesignTokens.colorGrayLightest | ||
|
||
if (isDestructive) { | ||
bgColor = DesignTokens.colorUswdsSystemColorRedVivid60 | ||
bgColorPressed = DesignTokens.colorUswdsSystemColorRedVivid80 | ||
} else if (isSecondary) { | ||
bgColor = DesignTokens.colorWhite | ||
bgColorPressed = DesignTokens.colorWhite | ||
borderColor = DesignTokens.colorUswdsSystemColorBlueVivid60 | ||
borderColorPressed = DesignTokens.colorUswdsSystemColorBlueWarmVivid80 | ||
textColor = DesignTokens.colorUswdsSystemColorBlueVivid60 | ||
textColorPressed = DesignTokens.colorUswdsSystemColorBlueWarmVivid80 | ||
} | ||
} else { | ||
bgColor = DesignTokens.colorUswdsSystemColorBlueVivid30 | ||
bgColorPressed = DesignTokens.colorPrimaryAltLightest | ||
textColor = DesignTokens.colorBlack | ||
textColorPressed = DesignTokens.colorBlack | ||
|
||
if (isDestructive) { | ||
bgColor = DesignTokens.colorUswdsSystemColorRedVivid40 | ||
bgColorPressed = DesignTokens.colorSecondaryLightest | ||
} else if (isSecondary) { | ||
bgColor = DesignTokens.colorBlack | ||
bgColorPressed = DesignTokens.colorBlack | ||
borderColor = DesignTokens.colorUswdsSystemColorBlueVivid60 | ||
borderColorPressed = DesignTokens.colorWhite | ||
textColor = DesignTokens.colorUswdsSystemColorBlueVivid30 | ||
textColorPressed = DesignTokens.colorWhite | ||
} | ||
} | ||
|
||
/** | ||
* Get button styling based on pressed state | ||
* @param pressed - boolean for pressed state | ||
* @returns ViewStyle for background | ||
*/ | ||
const getBackgroundStyle = ({ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe fine to leave it be for now, but wonder if we should make a more generalized function to handle this behavior. Could also be used by the util function Basically a function that takes:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't have time to implement something like this with me leaving for vacation, but I agree it could be useful as we're likely to be styling Pressable's in the future. I've created a ticket to work on this util function: https://github.com/department-of-veterans-affairs/va-mobile-app/issues/7240 |
||
pressed, | ||
}: PressableStateCallbackType): ViewStyle => ({ | ||
alignSelf: 'stretch', | ||
alignItems: 'center', | ||
padding: 10, | ||
backgroundColor: pressed ? bgColorPressed : bgColor, | ||
borderRadius: 4, | ||
borderWidth: isSecondary ? 2 : 0, | ||
borderColor: isSecondary | ||
? pressed | ||
? borderColorPressed | ||
: borderColor | ||
: 'none', | ||
}) | ||
|
||
/** | ||
* Get text styling based on pressed state | ||
* @param pressed - boolean for pressed state | ||
* @returns TextStyle for text | ||
*/ | ||
const getTextStyle = (pressed: boolean): TextStyle => { | ||
// TODO: Replace with typography tokens | ||
const font: TextStyle = { | ||
fontFamily: 'SourceSansPro-Bold', | ||
fontSize: 20, | ||
lineHeight: 30, | ||
} | ||
|
||
return { | ||
...font, | ||
color: pressed ? textColorPressed : textColor, | ||
} | ||
} | ||
|
||
return ( | ||
<Pressable | ||
style={getBackgroundStyle} | ||
onPress={onPress} | ||
accessibilityHint={a11yHint} | ||
accessibilityRole="button" | ||
narin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
accessible={true} | ||
accessibilityState={accessibilityState || {}} | ||
narin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
testID={testID || label} | ||
> | ||
{({ pressed }: PressableStateCallbackType) => ( | ||
<Text style={getTextStyle(pressed)}>{label}</Text> | ||
)} | ||
</Pressable> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2707,7 +2707,7 @@ __metadata: | |
"@babel/plugin-transform-react-jsx": ^7.22.15 | ||
"@babel/preset-env": ^7.22.15 | ||
"@babel/preset-typescript": ^7.22.15 | ||
"@department-of-veterans-affairs/mobile-tokens": 0.0.10 | ||
"@department-of-veterans-affairs/mobile-tokens": 0.1.0 | ||
"@expo/webpack-config": ^19.0.0 | ||
"@os-team/i18next-react-native-language-detector": ^1.0.28 | ||
"@react-native-async-storage/async-storage": 1.18.2 | ||
|
@@ -2766,14 +2766,7 @@ __metadata: | |
languageName: unknown | ||
linkType: soft | ||
|
||
"@department-of-veterans-affairs/mobile-tokens@npm:0.0.10": | ||
version: 0.0.10 | ||
resolution: "@department-of-veterans-affairs/mobile-tokens@npm:0.0.10" | ||
checksum: 7590c87fb87e72891fe270f9dc53be8af3d0471d43cd448ead234b40d3f1fd5612740f049c121062c2beffd2597e81fa4e705e808bf3dc3110c33fbc1213f928 | ||
languageName: node | ||
linkType: hard | ||
|
||
"@department-of-veterans-affairs/mobile-tokens@workspace:packages/tokens": | ||
"@department-of-veterans-affairs/[email protected], @department-of-veterans-affairs/mobile-tokens@workspace:packages/tokens": | ||
version: 0.0.0-use.local | ||
resolution: "@department-of-veterans-affairs/mobile-tokens@workspace:packages/tokens" | ||
dependencies: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just adding a preemptive note that should bump to 0.2.0 in the pre-merge publish.